Summary
Every controller type in a veb app produces its own handle_route[A, X] instantiation containing the compile-time expansion of that controller's routes. Growth is linear in the number of controllers and routes:
- 2 controllers × 1 route → 3
handle_routeinstances,monomorphize1.56 s - 16 controllers × 8 routes → 17 instances,
monomorphize3.25 s, 42,088 veb C lines
Slope: ~0.11 s of monomorphization and ~2.5k C lines per controller (8 routes), i.e. ~14 ms and ~315 C lines per route, on top of a ~1.5 s framework baseline.
Environment
- V 0.5.2
5bd6709(vlang/v master), Linux x86_64 v -new-compiler -cc tcc -no-retry-compilation, compile to C with-o out.c- 2026-10-09, ±5% run-to-run variance
Minimal case (single file, 2 controllers × 1 route)
module main
import veb
struct Context {
veb.Context
}
struct App {
veb.Middleware[Context]
veb.Controller
}
struct C0 {
veb.Middleware[Context]
}
@['/c0/r0'; get]
pub fn (app &C0) h0_0(mut ctx Context) veb.Result {
return ctx.text('c0r0')
}
struct C1 {
veb.Middleware[Context]
}
@['/c1/r0'; get]
pub fn (app &C1) h1_0(mut ctx Context) veb.Result {
return ctx.text('c1r0')
}
fn main() {
mut app := &App{}
app.register_controller[C0, Context]('/c0', mut &C0{}) or { panic(err) }
app.register_controller[C1, Context]('/c1', mut &C1{}) or { panic(err) }
veb.run[App, Context](mut app, 18099)
}$ v -new-compiler -cc tcc -no-retry-compilation -show-timings -o out.c minimal.v
monomorphize 1555.86 ms
cgen (parallel) 553.19 ms
total 4124.12 ms
generated C size 4042933 bytesgrep -oE 'veb__handle_route_T_[A-Za-z_0-9]+' out.c | sort -u | wc -l → 3 (one per controller + one for the app itself).
Scaling reproduction
#!/bin/sh
# Usage: sh gen.sh 16 8 > main.v (N controllers × M routes)
N=${1:-16}
M=${2:-8}
cat <<'HDR'
module main
import veb
struct Context {
veb.Context
}
struct App {
veb.Middleware[Context]
veb.Controller
}
HDR
c=0
while [ "$c" -lt "$N" ]; do
cat <<EOF
struct C$c {
veb.Middleware[Context]
}
EOF
r=0
while [ "$r" -lt "$M" ]; do
cat <<EOF
@['/c$c/r$r'; get]
pub fn (app &C$c) h${c}_${r}(mut ctx Context) veb.Result {
return ctx.text('c${c}r${r}')
}
EOF
r=$((r + 1))
done
c=$((c + 1))
done
echo ''
echo 'fn main() {'
echo ' mut app := &App{}'
c=0
while [ "$c" -lt "$N" ]; do
echo " app.register_controller[C$c, Context]('/c$c', mut &C$c{}) or { panic(err) }"
c=$((c + 1))
done
echo ' veb.run[App, Context](mut app, 18099)'
echo '}'$ sh gen.sh 16 8 > main.v
$ v -new-compiler -cc tcc -no-retry-compilation -show-timings -o out.c main.v
monomorphize 3253.68 ms 344 MB RSS 597 MB stage peak
cgen (parallel) 855.53 ms 299 MB RSS 490 MB stage peak
total 6095.14 ms
generated C size 6443690 bytes| controllers × routes | handle_route instances | veb C lines | monomorphize | cgen | total | generated C |
|---|---|---|---|---|---|---|
| 2 × 1 | 3 | — | 1.56 s | 0.55 s | 4.12 s | 4.04 MB |
| 2 × 8 | 3 | 6,836 | 1.68 s | 0.59 s | 4.28 s | 4.29 MB |
| 8 × 8 | 9 | 21,944 | 2.36 s | 0.67 s | 5.02 s | 5.21 MB |
| 16 × 8 | 17 | 42,088 | 3.25 s | 0.86 s | 6.10 s | 6.44 MB |
Where the cost is
vlib/veb/veb.v:323handle_route[A, X]— per-controller monomorphized dispatch (the bulk of the generated code)vlib/veb/veb.v:46generate_routes[A, X]— also instantiated per controllervlib/veb/controller.v:81check_duplicate_routes_in_controllers[T],controller.v:25register_controller[A, X]
Expected
Route dispatch could be table-driven: one small function per route plus a data table, instead of expanding the full controller dispatch into a monomorphized handle_route per controller. Users can't work around this by merging controllers — the per-route code is the bulk and remains.
Impact
Real app: 34 controllers / 198 routes → veb__handle_route instantiations total 61k C lines (largest single instance 8354 lines for a ~30-route controller; Pay: 5439 lines). This is per-build cost on top of the framework baseline.
Notes
- Steady-state codegen volume; not the memory blow-up in #29304 (fixed by #29311) or the non-convergence in #29764 (fixed).
- Happy to provide the generator/output for any N × M if that helps.
Activity
0No activity yet.