1 Development and Testing
dxtr edited this page 2026-10-18 19:16:53 +02:00

Development and Testing

Running the tests

The suite is clsl/test, written with Parachute. From a checkout of clsl:

stift test

In the Pyssel workspace, the suite runs with the other libraries' checkouts, as the workspace's stiftwork names them; stift --no-workspace test runs it from clsl's own pins, as CI does. asdf:test-system signals an error if any test fails, so the command exits unsuccessfully.

What the suite needs

The suite holds clsl to SPIRV-Tools and to a GPU:

Needs For On Debian or Ubuntu
spirv-val, spirv-as and spirv-dis on the PATH validating every module the tests build, and checking the builder, the reader and the disassembler against the assembler and disassembler spirv-tools
a Vulkan 1.4 device, such as Mesa's lavapipe, which needs no GPU running shaders through cl-gfx libvulkan1, mesa-vulkan-drivers
the Khronos validation layer, which must report nothing checking every use of Vulkan the tests make vulkan-validationlayers
apt install spirv-tools libvulkan1 mesa-vulkan-drivers vulkan-validationlayers

On Windows, the Vulkan SDK gives the tools and the layer. A missing tool, driver or layer fails the suite with a message saying what to install: the tests never skip.

What the suite holds clsl to

  • SPIRV-Tools. spirv-val validates the modules the tests build, and spirv-as and spirv-dis check the builder, the reader and the disassembler: raw disassembly assembles back into the same words, and the friendly disassembly reads as spirv-dis writes it.
  • The IR. Every operation of the IR, in every type the verifier lets it take, validates, and computes on the GPU what the tests' evaluator, which runs the IR in Lisp, computes for the same IR.
  • The language. Every built-in, in every signature it takes, compiles into a shader that validates. On the GPU every built-in computes what Lisp computes: Lisp's function of the same name, or the GPU's vocabulary, such as mix and refract, by its definition computed in doubles; integers exactly, and floats within the precision Vulkan grants each. Control flow, every and its kin, loops, several values, structs and arrays, local functions, places and assignments compute what the same forms compute in Lisp; the bit operations compute what Lisp's do, ash by 32 bits or more and bytes past bit 31 included; and the guards give their defined results.
  • Data. Uniforms packed by pack-uniforms reach the shader; structs and arrays packed by std140 and std430 come back from storage buffers as pack-value lays them out.
  • Textures and images. Every kind of texture is read every way, at places, levels, layers and samples, with offsets, through filters and comparing samplers, as the evaluator reads it, in compute shaders and, at the level of detail derivatives pick, in fragment shaders; storage images of every kind and format are read, written and read back; textures and images are read and written in and out of range.
  • Compute. Atomics count right across workgroups, and shared memory, which starts at zero, sums a workgroup's share between barriers.
  • Drawing. Derivatives, fine and coarse, are what the evaluator computes across each quad; a triangle, interpolation, the interpolation functions, several render targets, discard and Pyssel's quad shaders draw as expected. A program of all five graphics stages draws a triangle as a vertex and a fragment shader do, tessellated or not, and draws nothing of a patch whose levels its shader leaves at 0; a tessellation evaluation shader's :winding decides which of its triangles culling drops; a geometry shader emits nothing past its :max-vertices; and a struct, a matrix and doubles pass through every stage.
  • Numbers. Doubles compute on the evaluator exactly what Lisp computes, and on the GPU within the precision Vulkan grants them; float pairs, within 2^-45 of what Lisp computes exactly of the reals they hold, and keep a position 10,000 km out to well under a micrometre; every complex function is held to Lisp's, computed in doubles.
  • cl-gmath. Each function of cl-gmath, through clsl/gmath, computes on the evaluator what it computes in Lisp, and each function cl-gmath exports has a shader form or a refusal that says why it has none.
  • Refusals. Every rule of the uniformity analysis is held to shader code it accepts and shader code it refuses, with the message; every form the language refuses is refused with its message.
  • Generated code. The committed spirv/grammar.lisp is what the generator writes. cl-slug's pixel shader, rewritten in clsl, compiles and validates.

The shaders run on a Vulkan 1.4 device through cl-gfx, under the Khronos validation layer, which must report nothing.

Continuous integration

CI runs the suite on every push to main and every pull request, with SBCL 2.6.9 in a Debian forky container, on lavapipe, installing spirv-tools, libvulkan1, mesa-vulkan-drivers and vulkan-validationlayers.

Working on clsl

The source, by the way a program goes:

Where What
src/language/checker.lisp and the files after it the checker, which types shader code into a tree; each form and built-in has its file, such as loops.lisp, bits.lisp and textures.lisp
src/language/lowerer.lisp, lowering.lisp lowering, which makes IR of the tree
src/language/pairs.lisp, complex.lisp the shader functions of float pairs and complex numbers, written in clsl's own language
src/language/programs.lisp, packing.lisp, documentation.lisp programs, packing and shader-documentation
src/ir/ the IR: its types, values, operations, builder, external form, verifier (verifier.lisp) and uniformity analysis (uniformity.lisp)
spirv/ SPIR-V: the tables, the encoding, the module builder, the reader and the disassembler
spirv/back-end/translation.lisp the back end, which writes SPIR-V of the IR
vulkan/target.lisp the :vulkan target
gmath/gmath.lisp the cl-gmath glue
generator/ the grammar generator; grammar/ holds the grammar files
src/sys.lisp clsl/sys, the one place for SBCL's own code: the bits of floats and float arithmetic with its traps masked
tests/evaluator.lisp the evaluator, which runs the IR in Lisp for the tests to compare with the GPU
doc/language.org the language's reference, which describes every form and built-in

The README describes the API, the IR and SPIR-V, doc/language.org the language, and this wiki is the manual: a change to what a page describes, such as the public API, behaviour, errors, thread rules or requirements, updates the page with it.

SPIR-V assembly writes ids as %name and %1, and the IR's external form labels values %1, %2 and so on: they are data, not Lisp names.