2. Variables#

PISA variable declaration specifies its address space, bitwidth, and name, along with optional attributes.

2.1. Bitwidth#

Variables have a defined bit-size (bitwidth) but are otherwise untyped. Interpretation of data contained in a variable is up to the instruction using the variable. An instruction operating on 32-bit variables may treat its source operands as either signed 32-bit integers, or single-precision floating point values, depending on the instruction opcode. Variable declarations specify the bitwidth of the variable using scalar or vector data types.

2.2. Register Variables#

Named register declarations are used to define one or more register variables with the same bitwidth. Name of the register must start with a % and is followed by an identifier.

// define a 32-bit register variable named %r0
.reg .32b %r0;
// define a 4x32-bit vector register variable named %v1
.reg .v4.32b %v1;
// define multiple 64-bit register variables named %d1, %d2, and %d3
.reg .64b %d1, %d2, %d3;

Multiple register variables can also be declared using a range-based syntax, where a half-open index range <Start~End> is appended to a base name to generate each variable name. The range includes the lower bound and excludes the upper bound.

// define multiple 16-bit register variables named %h0, %h1, %h2, %h3, %h4
.reg .16b %h<0~5>;
// define vector register variables %v4w4, %v4w5, %v4w6, %v4w7
.reg .v4.32b %v4w<4~8>;

Note

Register variables can be declared anywhere inside a function or a kernel.

2.3. Predicate Variables#

Predicate variables reside in register space and hold single-bit values produced by comparison instructions. They can control branches and select instructions, and serve as carry-in/carry-out bits for integer arithmetic overflow detection.

A predicate declaration follows the same syntax as a register variable declaration with the following modifications:

  • .pred is used instead of .reg.

  • bitwidth is not specified, as predicates are single-bit values.

// define a predicate variable named %p0
.pred %p0;
// define multiple predicate variables named %p1, %p2, %p3
.pred %p<1~4>;

Note

Predicate variables can be declared anywhere inside a function or a kernel.

2.4. Memory Variables#

Memory variable declarations define named allocations in one of a memory address space. The declaration follows the following format:

  • address space - .global, .const, .private, or .shared

  • name of the memory variable, starting with @ followed by an identifier

  • allocated size in bytes, specified within []

A memory variable may be used as a source in addrof instruction or in the address expressions of load, store, and atomic instructions.

The allowed scope depends on the address space: .private and .shared variables must be declared inside a kernel or function, while .global variables must be declared at module scope. .const variables can be declared at either module scope or inside a kernel or function.

// define a 8 byte global variable named @G
.global @G[8];

.kernel @test() {
   // define a 16 byte shared variable named @S
   .shared @S[16];
}
.function void @func() {
   // define a 1 byte private variable named @P
   .private @P[1];
}

2.4.1. Alignment#

Memory variables are aligned to byte boundary by default. If greater alignment is needed, it can be specified as part of variable declaration using .align attribute.

// define a private variable @A of 16 bytes, aligned to 4-byte boundary
.private .align 4 @A[16];

Warning

Alignment value must be a power of two. Any other alignment results in a compile-time error.

2.5. Global Variables#

Memory variables declared in module scope (.global or .const space) are considered global variables. Global variables are visible in the entire module. A global variable that has no static initializer has an undefined value at kernel entry.

2.5.1. Initializers#

Global variables support an initializer syntax to specify the variable’s initial value. Initializer value is specified via comma-separated pairs of size and immediate integer values, enclosed within { }. A floating-point immediate must be converted to hexadecimal format to be used in an initializer.

An immediate value can be specified as a decimal or a hexadecimal (prefixed with 0x or 0X). Hexadecimal literals are zero-extended, while decimal literals are sign-extended to 64 bits. Resulting value is then truncated to the size of the initializer element.

  • size of an immediate must be one of .8b, .16b, .32b, or .64b

  • nested initializer values are not permitted

  • immediate values are stored using little-endian encoding.

// 4-byte global variable initialized to 100
.global .align 4 @G1[4] = { .32b 100 };
// 8-byte constant variable initialized to -1
.const .align 4 @G2[8] = { .64b 0xFFFFFFFFFFFFFFFF };
// 4-byte global variable initialized to single-precision -1.0
.global .align 4 @G3[4] = { .32b 0xBF800000 };

Important

Number of bytes in initializer must match number of bytes in variable declaration

Note

Initializer is mandatory for .const variables

2.5.1.1. Zero-initializer#

The .zero N directive can be used to zero-initialize a global variable, where N represents the number of zero bytes.

// 4-byte global variable initialized to zero
.global @C[4] = { .zero 4 };
// 8-byte global variable initialized to alternating 0,-1 values
.global .align 4 @D[8] = { .16b 0xFFFF, .zero 2, .16b 0xFFFF, .zero 2 };

is equivalent to

.global @C[4] = { .32b 0 };
.global .align 4 @D[8] = { .16b 0xFFFF, .16b 0, .16b 0xFFFF, .16b 0 };

2.5.1.2. Symbol-initializer#

Any symbol in module scope, such as functions and global variables, may be used as an initializer value. In such cases, the value represents the 64-bit address of the symbol in its address space (equivalent to using addrof instruction). A constant offset from the symbol address can also be specified using @<symbol>+<offset> syntax.

.global .align 4 @D[8] = { .16b 0xFFFF, .16b 0, .16b 0xFFFF, .16b 0 };
.function .32b @add(.32b %a, .32b %b) {
   ...
}

// @FAddr is initialized to the address of 'add' function
.const .align 8 @FAddr = { .64b @add };
// @VAddr is initialized to the address of global variable @D
.const .align 8 @VAddr = { .64b @D };
// @GAddr is initialized to the address of the 3rd element of @D
.const .align 8 @GAddr = { .64b @D+4 };

2.5.2. Linkage#

By default, global variables have internal linkage, meaning that they are only visible within the module in which they are defined.

An .export directive is used to indicate external visibility of a variable across different modules. Exported variables can be used to resolve references to symbols with the same name during linking.

// allow access to variable @A from another module
.export .global .align 4 @A[4] = { .zero 4 };

An .import directive is used to indicate that a variable is not defined within the current module. Imported variables do not support initializers.

// import global variable @A from another module
.import .global .align 4 @A[4];

Important

A variable must not be declared more than once within a module, regardless of its linkage.

2.5.3. Attributes#

Several optional attributes may be specified for global variables.

.section("NAME") specifies a name of the section of the object file to place the variable in.

// place variable @HI in a section named ".const.data"
.const .section(".const.data") @HI[3] = { .8b 0x48, .8b 0x49, .8b 0 };

.host_access("NAME") specifies the name used to refer to the variable in the Host API.

// export global variable @G as "my_global_var" for host access
.global .align 4 .host_access("my_global_var") @G[4] = { .32b 0 };