Calling External Functions from Sequoia Code

There are three types of Sequoia tasks: inner, leaf, and external. Inner and leaf tasks are both implemented natively in Sequoia, while external tasks only have their interfaces defined in the Sequoia code, not their bodies. The purpose of external tasks is to link to an externally provided function, enabling Sequoia code to call into a non-Sequoia function.

Any external function called by Sequoia code must have a specific interface. Consider the following example external task declaration:

void task<ext> MyTask::MyVariant(in int A[T], out float B[U][V], in int x, inout float y);

Suppose that the compile-time mapping directives directed that this task should be instantiated with the instance name "MyTaskInst", and that an external function was to be used to implement this instance. The external function's signature must be:

void MyTaskInst(sqArray_t *A, sqArray_t *B, int x, float *y);

The rules for determining the type signature that the external function must have for a given external Sequoia task declaration that it will implement are simple, and are the same as for the case of calling a Sequoia task from external code:

  • All array arguments in the Sequoia task declaration become sqArray_t pointers.
  • All in scalar arguments become "by-value" C function arguments, and all out and inout scalar arguments become "by-pointer" C function arguments.
  • The function's name is the same as the task instance name specified when the Sequoia program was mapped to the target machine.

External functions are commonly used in Sequoia to provide optimized, target-specific leaf task implementations that can be called instead of generic Sequoia leaf task implementations. It's often the case that much better program performance can be obtained by optimizing leaf task code using SIMD intrinsics and other target-specific low-level operations.

Compiler-Generated API: sequoia_ext.h

The Sequoia compiler also generates the sequoia_ext.h header file containing the following macros for each Sequoia external task instance. This interface is not necessary to be able to write an external function that will interface to Sequoia, rather it is provided for convenience and efficiency. (The implementation of these macros isn't shown here). All of these macros may only be called from within the body of the external function.

// Evaluates to a pointer to a block of local scratch space that was allocated
// for the external function by the compiler (as a result of a mapping 
// directive).
#define sqLocalSpace(instNameSym)

// A macro that will compute the values of the array size paramters from the
// actual array sizes.
#define sqCalcParams(instNameSym)

// A macro that the compiler defines that will evaluate to the value of a
// parameter of the function, for each parameter "paramNameSym".    
// The sqCalcParams() macro must be called before the sqParam() macro may be
// used, and as the sqCalcParams() macro internally creates a scalar variable
// for each array size parameter, the sqParam() macro may only be used in the
// scope of the sqCalcParams() call.
// The compiler may also hard-code the sqParam() implementations for some of the
// array size parameters for an external task instance by making them evaluate
// to a preprocessor constant, if their values are known statically at compile
// time.
#define sqParam(instNameSym, paramNameSym)

// Macros that provide specialized implementations of the sqElmt*D() functions,
// for each array argument "arrayNameSym" of the external task. These can be used
// to more efficiently access array elements.
#define sqElmtSpec1D(instNameSym, arrayNameSym, i0)
#define sqElmtSpec2D(instNameSym, arrayNameSym, i0,i1)
#define sqElmtSpec3D(instNameSym, arrayNameSym, i0,i1,i2)
// ...

As an example, the above MyTaskInst external function could use these macros as follows:

void MyTaskInst(sqArray_t *A, sqArray_t *B, int x, float *y)
{
    char *localScratchSpace = sqLocalSpace(MyTaskInst);

    sqCalcParams();
    unsigned int T = sqParam(MyTaskInst, T);
    unsigned int U = sqParam(MyTaskInst, U);

    float *B_elmt = sqElmtSpec2D(MyTaskInst, B, 10, 0);
}