Code Definition

angavrilov edited this page Sep 13, 2010 · 4 revisions

This page describes how GPU code and variables can be defined.

Note: see the Definer Syntax page for an explanation of the def macro.

GPU compiler macros

  • (def (gpu-macro e) name (args…) …)

Defines a GPU-specific macro. The behavior requirements are similar to normal compiler macros.

GPU type expanders

  • (def (gpu-type e :gpu-only t) name (args…) …)

This definition is equivalent to deftype, but expanders defined this way are also accessible to the GPU code translator.

If :gpu-only is true, an ordinary deftype is not performed, so effectively the type is defined only for GPU code.

GPU function definitions

  • (def (gpu-function e :gpu-only t) name (args…) …)

This defines a function that can be used in GPU code. If :gpu-only is not specified, the same exact body is also used to define an ordinary lisp function.

The functions are unconditionally inlined into all GPU kernels that access them; until that happens no detailed GPU-specific analysis is applied to the function body. In a way similar to ordinary inlined lisp functions, modifying a gpu-function requires recompiling all GPU modules that use it.

Note that due to differences in behavior between some GPU built-in functions and the lisp standard library, the function can produce different results when called on the GPU compared to the plain lisp version.

For REPL debugging convenience, the library provides a function that can be used to directly invoke such definitions on the GPU:

  • (test-gpu-function function-name &rest args)

It works by guessing the missing argument type information from the supplied values and automatically defining an appropriate GPU module. This makes such calls somewhat expensive and unsuitable for non-debugging non-REPL usage. Compiled modules are cached, but properly invalidated if any of the referenced functions is changed.

GPU module definitions

GPU code is packaged in modules, which can contain a number of variables and functions that are compiled together. A module can be defined in the following way:

(def (gpu-module eas) name

The following item specifications are supported:

  • (:conc-name prefix)
    Explicitly sets a prefix to prepend to variable and kernel names in order to generate accessor wrapper names. The default is module name + “-”, as with standard structure accessors.
  • (:variable name type)
  • (:global name type)
    Defines a module variable. It can be written from the GPU code.

Variables can either have a primitive scalar type, or point to a specialized array of primitive items. If all dimensions of an array are specified as constants, it is statically allocated; otherwise you have to allocate an appropriately typed buffer and assign it to the variable from the host.

  • (:variables type names…)
  • (:globals type names…)
    A shortcut for defining many globals of the same type.
  • (:constant name type)
  • (:constants type names…)
    Define module variables that can only be read from GPU code. They can still be modified from lisp.

Appropriately marking variables constant allows their values to be cached by the GPU hardware, thus boosting the read performance tremendously. Note that since GPU code cannot allocate memory, and therefore has no need to change internal pointers used to implement dynamic array globals, they are automatically declared constant.

  • (:function name (args…) body…)
    Similar to (def gpu-function …), but the defined function is local to the module, and thus has access to its variables. The function can be accessed by subsequent function and kernel clauses.
  • (:kernel name (args…) body…)
    Defines a kernel, i.e. a GPU function callable from ordinary lisp code.

All kernel argument types must be declared. Optional and rest arguments are not allowed. Keyword and &aux arguments are supported, but they are evaluated in lisp code before the real GPU function is invoked. This causes &aux arguments to behave differently from top-level let bindings, thus making them actually useful.

Kernels accept additional pre-defined keyword arguments that specify things like the number of threads to launch.

On the lisp side, a GPU module definition creates wrapper symbol macros and functions for all listed variables and kernels. Their names are derived in a way similar to struct field accessors. If the export-accessor-names flag is passed to the definer, all of their names are exported.
The export-slot-names flag controls whether the short names are exported for use inside with-gpu-module.

The wrappers implicitly depend on the active GPU context of the current thread; each context has its own copy of the module variables.

Exposing a GPU module in lexical scope

The following macro can be used to make module variable and kernel names accessible in the lexical context in a way similar to with-slots:

(with-gpu-module name

Where the name parameter is the symbol used previously in a global module definition (the gpu-module definer defines it as a symbol macro that expands to the internal module object). The code within the scope of this macro can access the GPU module items via the names used in their specifications.

It is also possible to define an ad-hoc module by passing a specification list instead of the name:

    ((:variable foo ...)
     (:kernel bar () ...))

Such modules are anonymous and can only be accessed by the code within the scope of the macro. Recompiling or reloading the containing function will cause a new module instance to be created, thus leaking the old one and losing access to its variable contents.

This can be avoided by specifying an explicit name via a (:name name) clause. When it is used, the resulting module is global like the ones declared at the file scope (and can conflict on the name with them, or other named ad-hoc modules), but has no global accessors or symbol-macro bindings for its name. Such modules can also be accessed via (with-gpu-module name …), but unlike normal global modules it won’t work from within the same compilation unit.