Compile-Time Evaluation
Djinn supports compile-time evaluation through constexpr and consteval, allowing expressions and functions to be computed during compilation rather than at runtime.
constexpr Variables
A constexpr variable must be initialized with a compile-time constant expression. The value is computed by the compiler and inlined at every usage site:
constexpr i32 WIDTH = 80;
constexpr i32 HEIGHT = 24;
constexpr i32 AREA = WIDTH * HEIGHT;Supported expressions in constexpr initializers:
- Integer, float, and boolean literals
- Arithmetic:
+,-,*,/,% - Comparison:
==,!=,<,<=,>,>= - Logic:
&&,||,! - Unary negation:
- - Type casts between numeric types
- References to other
constexprvariables orconststruct fields - Number literal formats: hex (
0xFF), binary (0b1010), separators (1_000_000), scientific (1e9)
constexpr i32 BUF_SIZE = 8192;
i32 main() {
i8* buffer = i8[BUF_SIZE]; // fixed-size stack array
buffer[0] = 42;
return (i32)buffer[0];
}Local constexpr / consteval
constexpr and consteval variables can also be declared inside function bodies:
i32 main() {
constexpr i32 a = 10;
consteval i32 b = 7 * 6;
constexpr i32 SIZE = 32;
i8* buf = i8[SIZE];
return a + b;
}constexpr Functions
A constexpr function can be evaluated at compile time when all arguments are known constants, or at runtime otherwise:
constexpr i32 square(i32 x) {
return x * x;
}
constexpr i32 factorial(i32 n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
i32 main() {
constexpr i32 val = square(5); // evaluated at compile time
i32 x = 10;
i32 y = square(x); // evaluated at runtime (x is not constexpr)
return val + y;
}Supported in constexpr function bodies
- Local variables and assignments
if/elsebranchingwhileandforloopsreturnstatements- Calls to other
constexprfunctions - All arithmetic, comparison, and logic operations
Restrictions
- Cannot call
externfunctions (I/O, malloc, etc.) - Cannot use pointers or heap allocation
- Cannot be
async - Loop iterations are bounded (default: 1,000,000) to prevent infinite loops
- Call depth is bounded (default: 512) to prevent stack overflow
consteval Functions
A consteval function must be evaluated at compile time. If called with non-constant arguments, the compiler emits an error:
consteval i32 compile_time_only(i32 x) {
return x * 2 + 1;
}
i32 main() {
i32 a = compile_time_only(10); // OK: argument is a literal
// i32 x = 10;
// i32 b = compile_time_only(x); // ERROR: x is not a compile-time constant
return a;
}consteval functions do not exist at runtime — they are fully erased after compilation.
Struct Const Fields
Struct const fields are evaluated at compile time using the same evaluator. They can be accessed statically via the struct name or through an instance:
struct Player {
const i32 MAX_HP = 100;
i32 hp;
i32 x;
i32 y;
}
i32 main() {
Player p = { 50, 10, 20 };
i32 a = Player.MAX_HP; // static access
i32 b = p.MAX_HP; // instance access (same value)
return a;
}Const fields do not occupy space in the struct layout — they are inlined at every usage site. In the example above, Player has only 3 fields (hp, x, y).
Rules
- A
constfield must have an initializer — omitting it is a compile error mut constis invalid — the two modifiers are mutually exclusiveconstfields can have visibility modifiers (public,private)
struct Direction {
public const i32 UP = 0;
public const i32 DOWN = 1;
public const i32 LEFT = 2;
public const i32 RIGHT = 3;
}How It Works
The Djinn compiler includes a stack-based bytecode VM that runs during compilation:
- Compile: The AST expression/function is compiled to bytecode (PUSH, ADD, JMP, CALL, RET, etc.)
- Execute: The VM runs the bytecode on a value stack with local variable slots and call frames
- Inline: The resulting constant is emitted directly into the LLVM IR
This architecture allows complex compile-time computation (loops, recursion, branching) while keeping the compiler fast and the evaluation bounded.