Describe the feature
Evaluate a const initializer that calls @[comptime] functions at compile time, and emit the result as static C data, for the values V already has: integers, floats, strings, fixed arrays, []T, maps, and structs of these.
@[comptime]
fn crc32_table(poly u32) [256]u32 {
mut t := [256]u32{}
for i in 0 .. 256 {
mut crc := u32(i)
for _ in 0 .. 8 {
crc = if crc & 1 == 1 { (crc >> 1) ^ poly } else { crc >> 1 }
}
t[i] = crc
}
return t
}
const ieee_table = crc32_table(0xedb88320)Expected C: const u32 main__ieee_table[256] = {0x00000000, 0x77073096, …};, with no code in _vinit.
@[comptime] already exists (#26475, 2519498), and docs.md says: "Calls to this function in const and enum expressions can be evaluated at compile time, when all call arguments are compile-time constants." That works for enum values. For consts it doesn't (see Today).
Requirements
- Static data, no startup work. The result becomes a C initializer:
const u32 x[256] = {…},string x = {"…", n, 1}, a struct literal. A[]Tbecomes a static backing array with.nofree | .nogrow. A map becomes static storage, either in V's layout or as a perfect hash over the known keys. Nothing runs in_vinit, and nothing goes on the heap. - Same answers as at runtime. Integers wrap at their declared width. The value must be the one the function returns when called at runtime.
- Failures are compile errors at the const. A
panic, anor { panic(…) }, or an error return reached during evaluation is an error at the const. So is anything the evaluator can't run (C functions,os.getenv,time.now,rand, globals, I/O). Evaluation never falls back to_vinitsilently. Calls to a@[comptime]function with runtime arguments, outside const and enum expressions, stay ordinary calls. - Target, not host.
$if,sizeof, the width ofintand endianness inside an evaluated function follow-os/-arch.
Use case
Today a table is either built at every startup or pasted as numbers. The examples below are from vlib.
- Built at every startup:
hash.crc32builds four&Crc32objects with their 256-entry tables, pluscrc32q_table, in the_vinitof every program that imports it.net.httpbuilds the HPACK Huffman table and its decode map (… or { panic('hpack: …') });net.quicbuilds the QPACK one.compress.deflatebuilds its fixed Huffman trees.term.uibuilds the 256-color palette (color_table = init_color_table()).math.bigheap-allocates 23 constants (one_int…c20).
- Pasted:
crypto/aes/const.vholds about 300 lines of hex (pow_x,s_box0,s_box1,te0,td0) next to thepolythey derive from. A 20-line@[comptime] fn make_sbox() [256]u8(GF(2⁸) inverse plus the affine map) reproducess_box0byte for byte, and a reviewer can check it. - Large const maps, rebuilt entry by entry:
net.http.mimehas 2,354MimeTypeentries and 1,203 extension → type entries. A program whose only work ismime.get_mime_type('wasm')takes 1.48 ms of CPU at startup against 0.37 ms for a baseline, and its binary is 746 KB against 35 KB (-prod -gc none,perf stat -r 300). About 700 KB of the extra.textis the code that inserts the entries. Others:encoding.htmlnamed references (about 2,100 entries), and the compiler's own reserved-word and libc-symbol maps, paid on everyvrun. - Generated files:
net/http/mime/build.vshdownloads mime-db's JSON and writes a 15,337-linedb.v. With a@[comptime]parser over$embed_file('db.json'), the JSON would be the source, and the extension index would be derived from it instead of being generated next to it. - Validation:
base58.btc_alphabet = new_alphabet('…') or { panic(impossible) }, the HPACK and QPACK builders and theedwards25519point constants turn a bad literal into a crash at startup. Evaluated at compile time, the mistake would be a compile error. - Prebuilt bytes:
const r = 'HTTP/1.1 404 …'.bytes()copies a literal that is already in.rodatato the heap in_vinit. enghitalo/vanilla had more than 200 of these, as prebuilt HTTP responses. This case only needs constant folding.
The full list, with measurements and outside-vlib cases (routers from attributes, math tables, sRGB/gamma curves, lexer byte classes), is in enghitalo/vanilla#239, section A.
Today
At 9d34aa2, checked in the generated C. I called @[comptime] functions from consts in six forms: mask(1, 4) u32, wrap8(250, 10) u8, a loop returning [256]u8, a string, a recursive fib(20), and the crc32_table above. Every one became a plain C global filled in _vinit:
u32 main__ieee_table[256];
…
memmove(main__ieee_table, (crc32_table(0xedb88320)).ret_arr, sizeof(main__ieee_table));const n = mask(1, 3)used as[n]u8{}fails witherror: non-constant array bound n.- Enum values are evaluated, but in
int: with@[comptime] fn wrap8(a u8, b u8) u8 { return a + b },enum Small as u32 { w = wrap8(250, 10) }emits#define Small__w ((Small)(260)), whilewrap8(250, 10)returns 4 at runtime. The enum evaluator handles parameters, straight-line assignments andreturnover amap[string]int: no loops, no arrays or strings, and no integer widths.
Without @[comptime], only literals, arithmetic, literal concatenation, fixed arrays and plain struct literals are static. These all go to _vinit (emit_const; []T is always runtime):
| initializer | emitted as |
|---|---|
'…'.bytes() | string__bytes(), a heap copy |
[1, 2, 3] | new_array_from_c_array() |
{'html': 'text/html', …} | new_map(), then one map__set() per entry |
'Content-Length: ' + s.len.str() | int__str(), string_plus_many() |
error('…') | memdup() of the error object |
| any call | the call (plus memmove() for a fixed array) |
[[u8(1), 2]!, [u8(3), 4]!]! | memmove() |
Proposed solution
Pointers are at 9d34aa2.
- Make the enum path correct. Wrap each
@[comptime]result and parameter to its declared type (u8,u32, …) incomptime_static_enum_call_valueandcomptime_static_enum_local_expr_value. A value that doesn't fit the enum's type should be an error. This could be a separate bug report if preferred. - Fold builtin methods over literals in const initializers.
'…'.bytes()becomes a staticArrayover the literal's bytes (.nofree | .nogrow);.len,.str()of a const int, and literal interpolation become literals. No interpreter is needed. This overlaps with step 3 of #29378 (try_lower_string_method_call). - Evaluate
@[comptime]calls in const initializers withv.eval. It already interprets V: loops, arrays, maps, structs, closures. After type checking, run the call with its constant arguments and turn the resultingeval.Valueinto a C initializer inemit_const. Two prerequisites inv.eval:- Fixed-width integers.
Valuecarries integers asi64, sou8(250) + u8(10)gives 260, and au32CRC-style generator (shifts and xors) returns734460456773where the compiled program returns21049157. - Coverage of the builtin methods generators call.
'content-type'.to_upper()fails withv.eval: unsupported string method `to_upper`.
- Fixed-width integers.
- Lower the values to C.
- Scalars, strings, fixed arrays and plain structs map to C initializers directly, as literals already do.
- A
[]Tbecomes a static backing array plus.nofree | .nogrow. - A map becomes static buckets in V's layout, or a perfect hash.
- An
IErrorbecomes a staticMessageErrorobject, asbuiltin__error_sentinelalready is. &Tvalues, sum types and interfaces need their payloads emitted as static objects.- Anything else is a compile error.
- Cache the results with the module cache, keyed by the source of the evaluated functions, so a 2,000-entry map isn't re-evaluated on every build.
Floating point needs one rule: math.pow, math.sin and similar functions, evaluated by the compiler, can differ in the last bit from the target's libm. Most tables tolerate that. A table that must match a runtime computation exactly needs correctly rounded functions in the evaluator.
Other information
- #26475 and 2519498:
@[comptime]for enum values (integers, straight-line bodies). - #29378: folding pure string functions on compile-time strings; its step 3 covers part of step 2 here.
- enghitalo/vanilla#239: the use cases with measurements (section A), and the other proposals from the same router work.
Environment
- V 0.5.2
9d34aa2(9d34aa2ecc297f8896a4916195a1ba0424807f8f, 2026-10-03) - OS: Manjaro Linux, kernel 7.1.13, x86_64
- C compiler: gcc 16.2.1 (20260810)
Activity
0No activity yet.