Type Safe PPU and SPU pointers
Offload KB - features
Old Content Alert
Please note that this is a old document archive and the will most likely be out-dated or superseded by various other products and is purely here for historical purposes.
Inside offload scopes there are two types of addresses – local addresses and outer addresses. All addresses of data defined outside the offload scope become outer addresses. Addresses of data defined inside an offload block point to local SPU memory whereas outer addresses point to PPU memory. In order to be able to process outer addresses on the SPU, the __outer pointer keyword has been added to allow pointer declarations for outer addresses.
#include <liboffload>
int gx;
int * gptr;
int main()
{
__blockingoffload
{
int lx;
int * lptr; // pointer to local data
lptr = gptr; // incompatible types: int * = __outer int *
gptr = & lx; // incompatible types: __outer int * = int *
gptr = & gx; // ok, both are outer
lptr = & lx; // ok, both are local
__outer int * optr; // pointer to hold outer address
optr = & gx; // ok, outer pointer gets outer address
optr = gptr; // ok, outer pointer initialised with outer address
}
}
This __outer keyword is used as a pointer modifier (just as const and volatile are in C/C++) and only required in an offload context, which includes pointer declarations inside offload blocks and in offload functions. In all other contexts it will be ignored.
As with the const and volatile qualifiers in C++, the __outer modifier can appear anywhere in the base type for a pointer declaration. For example __outer int * is equivalent to int __outer * , but not to int * __outer as the latter declares a local pointer type that is __outer qualified.
Inside offload contexts pointers declared with __outer (i.e. point to PPU memory) are incompatible with those declared without __outer (i.e. point to SPU memory) which has implications for conversions, overload resolution and name mangling.
#include <liboffload>
T * x;
__blockingoffload
{
// in this scope the PPU pointer x automatically becomes an __outer int *
__outer T * z = x; // outer pointer variable on the SPU
T y = * z; // dereferencing z causes an SPU software cache read
* z = y; // writing SPU value to PPU variable
}
The __outer qualifier allows the programmer to distinguish between two types of pointers within an offload scope: pointers to PPU memory and pointers to SPU memory:
#include <liboffload>
__blockingoffload
{
int * p; // pointer to local store (SPU memory)
__outer int * k; // pointer to PPU memory
int v1 = * p; // dereferencing SPU pointer
int v2 = * k; // dereferencing PPU pointer – DMA into SPU
p = k; // illegal – SPU and PPU pointers are incompatible
}
In the above example, variable p has type int *. Since this declaration appears in an offload scope, and since the __outer qualifier has not been used, the pointer p is restricted to point to memory on the local store of the SPU on which the enclosing offload block runs.
On the other hand, variable k has type __outer int *. The use of the __outer qualifier restricts k to point to shared PPU memory – memory outside the local store of the SPU on which the enclosing offload block runs.
Inside the offload block, trying to assign p to k (or vice versa) results in a compiler error. This strong type checking prevents dereferencing a PPU address in SPU memory (and vice versa). Of course, it is legal to dereference p and assign the result to the memory location pointed to by k (and vice-versa). Codeplay Offload automatically transforms this into appropriate DMA and/or software cache operations.
A pointer declared outside an offload scope always points to data in PPU shared memory. Such pointers are automatically given the __outer qualifier.
It is desirable to reduce the number of places in the source code where the __outer keyword has to be used. The two contexts where __outer may be omitted is in initializations and casts. This language feature is designed to avoid having to explicitly type __outer in front of declarations and in casts, and is especially useful when refactoring existing source code for Codeplay Offload, and during function duplication. Function duplication also allows __outer to be omitted from parameter and return pointer/reference types.
Pointer and reference types declared without the __outer modifier in offload contexts are local pointer/reference types (pointing to SPU data). However, if a pointer variable declared without __outer is initialized with an outer pointer type, the compiler will automatically deduce this property and make the variable an outer pointer.
#include <liboffload>
T * x;
__blockingoffload
{
__outer T * z = x; // explicitly declared __outer
T * z2 = x; // silently deduces __outer from x
// z and z2 have the same type: __outer T *
}
The following example lists legal cases where __outer is deduced for references and pointers from different source types.
#include <liboffload>
int * a = new int(3);
static int b = 4;
static int c[10] = {5};
__blockingoffload
{
int * d = a; // __outer int *
int & e = b; // __outer int &
int * f = c; // __outer int * from PPU array
int * g = & b; // __outer
int & h = * a; // __outer int &
int & i = * c; // __outer int &
}
Inside an offload context, when casting a value of pointer type __outer T * to another destination pointer type which does not declare __outer, then the destination pointer type of that cast automatically receives the __outer property.
#include <liboffload>
T1 * global;
int main()
{
__blockingoffload
{
__outer T2 * var;
var = (T2 * )global;
// T2 * inside the bracket is automatically changed to __outer T2 *
}
}
Valid uses of pointers in an offload block are illustrated by the following example.
#include <liboffload>
float f_PPU;
float * ptr_PPU;
int main()
{
__blockingoffload
{
float f_SPU;
float * ptr_SPU;
__outer float * outer_ptr_SPU;
ptr_PPU = & f_PPU; // PPU address to PPU variable
outer_ptr_SPU = & f_PPU; // PPU address to SPU outer pointer
outer_ptr_SPU = ptr_PPU + 1;
ptr_PPU = outer_ptr_SPU + 1;
* ptr_SPU = * ptr_PPU; // dma into SPU
* ptr_SPU = * outer_ptr_SPU; // dma into SPU
* ptr_PPU = * outer_ptr_SPU; // outer memory assignment
* ptr_PPU = * ptr_SPU; // dma from SPU into PPU
* outer_ptr_SPU = * ptr_PPU; // outer memory assignment
* outer_ptr_SPU = * ptr_SPU;
}
}
These are cases that the compiler will not accept.
#include <liboffload>
float f_PPU;
float * ptr_PPU;
int main()
{
__blockingoffload
{
float f_SPU;
float * ptr_SPU;
__outer float * outer_ptr_SPU;
ptr_PPU = & f_SPU; // illegal to
outer_ptr_SPU = & f_SPU; // assign SPU
ptr_PPU = ptr_SPU; // address to PPU
outer_ptr_SPU = ptr_SPU; // pointer
ptr_SPU = & f_PPU; // illegal to assign
ptr_SPU = ptr_PPU; // PPU address to
ptr_SPU = outer_ptr_SPU; // SPU pointer
}
}
Pointer types can be declared using __declspec(__setoffloadlevel__(level)), where level is the offload block level which can have the value one for local pointer and zero for outer pointers. In fact __declspec(__setoffloadlevel__(0)) is equivalent to __outer and __declspec(__setoffloadlevel__(1)) is equivalent to __inner. This declspec allows declaring SPU local pointer types outside an offload block which may be useful inside structure declarations to explicitly declare structure members with local pointer types.
#include <liboffload>
const int OUTER_DEPTH = 0;
const int INNER_DEPTH = 1;
template <class T, int DEPTH = OUTER_DEPTH> struct struct_with_pointer
{
__declspec(__setoffloadlevel__(DEPTH))
T * ptr; // depending on DEPTH either a local or outer pointer
};
struct_with_pointer<int> var_outer_ptr;
int outer_int;
void f()
{
__blockingoffload
{
struct_with_pointer<int> var1_outer_ptr;
struct_with_pointer<int, __OFFLOAD_DEPTH__> offload_local;
int offload_int;
int * localptr;
offload_local.ptr = & offload_int; // ok, local address to local pointer
localptr = offload_local.ptr;
var_outer_ptr.ptr = & outer_int; // ok, outer address to outer pointer
var_outer_ptr = var1_outer_ptr; // ok, same struct types
}
}