Best practices for creating and modifying ISPC builtin files. Use when adding target-specific optimizations, implementing new builtin functions, or working with the hierarchical target system.
Understanding the hierarchical target system is critical for correctly implementing and maintaining builtins.
ISPC's standard library consist of two layers:
stdlib/stdlib.ispc) — User-visible functions written in ISPC languagebuiltins/) — Target-specific implementations in LLVM IR that support the stdlibKey Concept: Targets are organized hierarchically. A child target inherits all functions from its parent that it doesn't explicitly override.
src/builtins.cpp)The hierarchy is defined in targetParentMap:
std::unordered_map<ISPCTarget, ISPCTarget> targetParentMap = {
// ARM NEON -> generic
{ISPCTarget::neon_i8x16, ISPCTarget::generic_i8x16},
// AVX512 hierarchy: dmr -> gnr -> spr -> icl -> skx -> generic
{ISPCTarget::avx10_2dmr_x16, ISPCTarget::avx512gnr_x16},
{ISPCTarget::avx512gnr_x16, ISPCTarget::avx512spr_x16},
{ISPCTarget::avx512spr_x16, ISPCTarget::avx512icl_x16},
{ISPCTarget::avx512icl_x16, ISPCTarget::avx512skx_x16},
{ISPCTarget::avx512skx_x16, ISPCTarget::generic_i1x16},
// AVX/SSE hierarchy: avx2vnni -> avx2 -> avx1 -> sse4 -> sse2 -> generic
{ISPCTarget::avx2vnni_i32x8, ISPCTarget::avx2_i32x8},
{ISPCTarget::avx2_i32x8, ISPCTarget::avx1_i32x8},
// ...
};
When compiling user code:
avx512skx-x16)generic-i1x16)Implication: You only need to implement functions that differ from the parent.
builtins/)| File Pattern | Description |
|---|---|
target-<isa>-<variant>.ll |
Target-specific LLVM IR (e.g., target-avx512skx-x16.ll) |
target-<isa>-common.ll |
Shared code for ISA family (e.g., target-sse4-common.ll) |
target-<isa>-utils.ll |
Utility macros/functions for ISA (e.g., target-avx512-utils.ll) |
generic.ispc |
Target-independent implementations in ISPC |
util.m4 |
M4 macros for generating LLVM IR |
Function signatures are declared in stdlib/include/builtins.isph.
Add declaration to stdlib/include/builtins.isph:
EXT inline READNONE varying float __my_builtin_float(varying float);
New builtins must always have a generic implementation in generic.ispc. This ensures all targets work correctly via hierarchy fallback.
Then add optimized LLVM IR versions at the appropriate level — they will be inherited by all children:
avx512skx — inherited by icl, spr, gnr, dmravx512icl — inherited by spr, gnr, dmr (overrides skx)Example optimized implementation in target-avx512skx-x16.ll:
define <16 x float> @__my_builtin_float(<16 x float> %input) nounwind readnone alwaysinline {
%result = call <16 x float> @llvm.x86.avx512.something(<16 x float> %input)
ret <16 x float> %result
}
generic.ispc first, optimize laterinclude() — Share code via target-*-utils.ll filesnounwind readnone alwaysinline attributesSee CLAUDE.md for build, lit test, and IR/assembly inspection commands.
Dump IR before optimizations to see builtins as they were linked:
build/bin/ispc test.ispc --target=avx512skx-x16 --debug-phase=pre:first --dump-file=dbg -o /dev/null
# Check dbg/ir_*.ll files for the builtin implementation