4. Calling Convention#

PISA provides high-level C-like abstractions for calling and returning from a function.

4.1. Kernel#

A kernel is an entry point for execution and is defined using the .kernel directive. Kernel cannot have a return value, but may have zero or more arguments. All kernel arguments are read-only and have per-kernel scope, i.e. all work-items receive the same value of each kernel argument.

.kernel @foo() {
   // kernel body
}

Note

Kernel can only be invoked from the host program.

4.1.1. Attributes#

Several optional attributes may be specified for the kernels, using .attribute_name(value) or .attribute_name syntax. They are declared between .kernel and its name, and are separated by spaces.

.kernel .reqd_work_group_size(256, 2, 1) .vec_type_hint(int2) @test() {
   // kernel body
}
.reqd_work_group_size(X, Y, Z)

Specifies required work-group size. The value X, Y, and Z specify the size of each dimension.

.vec_type_hint(TYPE)

Specifies a hint of the computation width of the decorated kernel. The value TYPE may be int, int2, or any other scalar or built-in vector type defined by the OpenCL specification.

4.2. Kernel Parameters#

Kernel parameters are decorated using mandatory .param directive. Each argument is modeled as a byte array stored in an abstract location pointed to by corresponding parameter. Size of the argument in bytes must be specified in corresponding parameter declaration.

Argument data is loaded into registers using special ld.param instruction.

.kernel @foo(.param[4] %arg0, .param[8] %arg1) {
   .reg .32b %larg0, %larg1;

   // load contents of %arg0
   ld.param.32b %larg0, [%arg0];
   // load contents of %arg1[4]
   ld.param.32b %larg1, [%arg1 + 4];
}

Important

Kernel parameters can only be referenced by ld.param instruction.

It is the responsibility of the PISA producer to define how to access struct members via the ld.param instruction. Consider the following naturally aligned struct:

structure declaration in host program#
// | x | - | y | y | z | - | - | - | w | w | w | w |
struct {
   unsigned char x;
   unsigned short y;
   unsigned char z;
   unsigned int w;
}
structure access within kernel#
.kernel @foo(.param[12] %arg) {
   .reg .8b %local_x, %local_z;
   .reg .16b %local_y;
   .reg .32b %local_w;

   // x is byte-aligned at offset 0
   ld.param.8b %local_x, [%arg];
   // y is aligned to 2 bytes at offset 2
   ld.param.16b %local_y, [%arg + 2];
   // z is byte-aligned at offset 4
   ld.param.8b %local_z, [%arg + 4];
   // w is aligned to 4 bytes at offset 8
   ld.param.32b %local_w, [%arg + 8];
}

4.2.1. Parameter Attributes#

Several optional attributes may be specified for kernel parameters, using .attribute_name(value) or .attribute_name syntax. They are declared between .param and the parameter name, and are separated by spaces.

.kernel @foo(.param[8] .align(4) .addrspace(global) %arg0) {
   // kernel body
}
.addrspace(ADDRSPACE)

Specifies address space memory object points to. ADDRSPACE can be global, shared, or const.

.align(BYTE)

Specifies the alignment of the kernel parameter in the kernel argument buffer. The default alignment is 8 bytes; BYTE must be a power of two, and specifies the alignment in bytes.

.ptr_align(BYTE)

Specifies the alignment of the memory pointed to by the kernel parameter. BYTE must be a power of two, and specifies alignment of the pointee in bytes.

4.3. Function#

Functions are declared/defined using the .function directive. Functions may have zero or more arguments, and may return a value to the caller. The return type void indicates that the function does not return a value.

.function void @foo() {
   // function body
}

Note

Functions can only be invoked from kernels or other functions.

4.3.1. Linkage#

By default, functions 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 function across different modules. Exported functions can be invoked by other kernels and/or functions defined in another module.

// allow access to function @foo from another module
.export .function void @foo() {
   // function body
}

An .import directive is used to indicate that a function is defined outside the current module. Imported functions must not have a function body defined in the current module, and are only declared with their signature.

.import .function .32b @foo(.reg .32b, .reg .32b);

4.4. Function Arguments#

Function arguments are passed from caller to callee as actual arguments in a call instruction. Each actual argument must be a register variable, and must match the type specified in the function declaration, as no implicit conversion is allowed.

.function void @foo(.reg .32b %arg0, .reg .v2.32b %arg1) {
   // function body
}
.function void @caller() {
   .reg .32b %a;
   .reg .v2.32b %b;

   // ... code ...
   call void, @foo(%a, %b);
   // ... code ...
}

Note

Predicate variables cannot be used as function arguments.

4.5. Function Return#

Function may return a single value to a caller using the return instruction. The return value may be an immediate or a register variable matching the return type specified in the function declaration, as no implicit conversion is allowed. If no return value is needed, void must be specified as the return type.

// function returning no value
.function void @foo() {
   // function body
   return;
}
// function returning a 32-bit value
.function .32b @foo() {
   .reg .32b %rv;

   // ... code ...
   return %rv;
}

Note

Predicate variables cannot be used as a function return value.

Important

The return instruction must be the last instruction in the body of a non-void function.