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:
.predis 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.sharedname of the memory variable, starting with
@followed by an identifierallocated 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.64bnested 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 };