Skip to content

Commit 1e1abc9

Browse files
committed
docs: add constexpr design and usage
Document constexpr syntax and constraints in docs/ispc.rst, add design notes and implementation observations, and include a blog post summarizing design decisions, hack removals, and test coverage.
1 parent fbf3928 commit 1e1abc9

File tree

4 files changed

+788
-2
lines changed

4 files changed

+788
-2
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Implementing constexpr in ISPC: design, hacks removed, and tests
2+
3+
## Motivation
4+
5+
ISPC has long relied on a mix of constant folding and stdlib/builtins tricks to
6+
feed compile-time constants to LLVM intrinsics. This work adds a first-class
7+
`constexpr` feature, similar in spirit to modern C++, so users can express
8+
compile-time computation directly and the front end can enforce it.
9+
10+
Key goals for v1:
11+
12+
- `constexpr` variables and functions usable in constant-expression contexts
13+
(array sizes, short-vector lengths, switch cases, enum values, template args,
14+
default parameter values).
15+
- C++-style “constexpr-suitable” validation at function definition time.
16+
- Support for both uniform and varying constexpr values.
17+
- Aggregate support (arrays, structs) and short vectors.
18+
- Immediate-operand (immarg) friendly lowering for LLVM intrinsics.
19+
20+
## Front-end design decisions
21+
22+
1. **C++-style validation at definition time**
23+
`constexpr` functions are validated as they are defined. The validator checks
24+
that only constexpr-safe statements and expressions are used. This keeps the
25+
language predictable and errors early.
26+
27+
2. **Uniform control flow**
28+
V1 requires uniform conditions in `if`, `switch`, `for`, `while`, and `do` in
29+
constexpr functions. This simplifies evaluation and mirrors constant
30+
evaluation expectations.
31+
32+
3. **Aggregate support with element-wise evaluation**
33+
`ConstExpr` now represents vectors, arrays, and structs as element lists, and
34+
constexpr evaluation folds aggregates element-wise. This is what enables
35+
`constexpr` arrays/structs and local aggregate initializers inside constexpr
36+
functions.
37+
38+
4. **Pointer constants with strict limits**
39+
Pointer support is intentionally narrow: constexpr may materialize null or
40+
addresses of globals/functions, allow pointer comparisons, pointer-to-bool,
41+
and pointer +/- integer arithmetic. Pointer dereference (and pointer-pointer
42+
arithmetic) remains disallowed.
43+
44+
5. **Deferral for forward usage**
45+
Global initializers and default parameter values can reference constexpr
46+
functions defined later in the file. The front end defers evaluation until
47+
after parsing and then resolves any remaining constexpr work. Array sizes,
48+
vector lengths, and template args are still evaluated immediately and must
49+
see definitions before use.
50+
51+
6. **Immediate-operand enforcement for intrinsics**
52+
LLVM requires certain intrinsic operands to be immediates. The front end now
53+
marks immarg parameters and rewrites constexpr arguments to literal
54+
`ConstExpr`s during type checking, so LLVM sees real immediate operands in IR.
55+
56+
## Constexpr evaluation pipeline
57+
58+
The implementation is layered end-to-end:
59+
60+
- **Lexer/Parser**: new `constexpr` keyword and grammar support for constexpr
61+
declarations and constexpr function definitions.
62+
- **AST/Types**: `constexpr` flag on symbols and function types. Function
63+
parameter defaults can store constexpr expressions.
64+
- **Validator**: definition-time check for constexpr-suitable bodies.
65+
- **Evaluator**: an interpreter-style evaluator for constexpr functions,
66+
supporting control flow and local variables, plus aggregate folding and
67+
pointer constants.
68+
- **Deferral**: postponed constexpr evaluation for globals/default params, with
69+
resolution prior to IR generation.
70+
71+
## Hacks removed (and what remains)
72+
73+
### Removed or replaced
74+
75+
- **`*l` immarg literal hacks** in `builtins/generic.ispc` were replaced with
76+
`constexpr uniform int` constants (`kImmArgZero/One/Two/Three`). This keeps
77+
immediates literal in IR while making the intent explicit.
78+
- **Stdlib constants** (`programIndex`, `programCount`, and target selector
79+
values) were migrated from `static const` to `static constexpr` now that
80+
constexpr aggregates and vector initializers are supported.
81+
- **Compiler-side immarg checks** no longer rely on fragile constant folding; the
82+
front end now enforces immarg parameters and rewrites constexpr arguments to
83+
immediate constants in IR.
84+
85+
### Still present (by design or future work)
86+
87+
- **`convert_scale_to_const*` switch macros** in `builtins/util.m4` and
88+
`builtins/target-avx512-utils.ll` remain as runtime fallbacks for dynamic
89+
gather/scatter scales. The front end can now enforce constant scales, but
90+
there is still an opportunity to bypass these switches for constexpr scales.
91+
- **`__is_compile_time_constant_*` probes** in `builtins/util*.m4` and
92+
`builtins/target-*.ll` remain for backend optimization (shuffle/rotate and
93+
mask-known fast paths). These are optimization-only and can coexist with
94+
constexpr; a constexpr-first path could skip them in the front end.
95+
96+
## Test coverage
97+
98+
The new behavior is covered with lit tests across the feature surface:
99+
100+
- Basic constexpr variable folding and constexpr function calls.
101+
- Varying constexpr lane-wise evaluation.
102+
- Control flow validation (if/switch/loops) and error diagnostics.
103+
- Non-type template args using constexpr expressions.
104+
- Aggregate constexpr values (structs, arrays, short vectors), including
105+
initializer lists and element access.
106+
- Pointer constexpr support (null and non-null addresses, pointer conditions,
107+
pointer arithmetic).
108+
- Immarg enforcement for LLVM intrinsics using constexpr values.
109+
- Deferred evaluation for globals and default parameters referencing constexpr
110+
functions defined later in the file.
111+
112+
These tests intentionally check both positive behavior (IR constants) and
113+
negative behavior (errors for disallowed constructs) to keep the front end
114+
honest.
115+
116+
## What remains
117+
118+
The feature is usable, but there are obvious follow-ups for a “v2”:
119+
120+
- Extend deferred constexpr evaluation to array sizes, vector lengths, and
121+
template arguments.
122+
- Relax the `constant_expression_no_gt` grammar workaround for template args.
123+
- Evaluate whether aggregate folding should be unified with general optimizer
124+
constant folding to remove constexpr-only paths.
125+

0 commit comments

Comments
 (0)