Skip to content

Commit 932c562

Browse files
committed
v3: selective backend compilation (45% speed up)
1 parent 836d2b5 commit 932c562

2 files changed

Lines changed: 129 additions & 35 deletions

File tree

vlib/v3/gen/c/cleanc.v

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1925,7 +1925,16 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) {
19251925
} else {
19261926
mod
19271927
}
1928-
g.write(c_name('${short_mod}.${node.value}'))
1928+
// A module-level const is stored under the importing module's full path
1929+
// (e.g. `v3.gen.wasm`), matching its function naming. Reference it by that
1930+
// exact storage name rather than the short alias, otherwise we'd emit an
1931+
// undeclared `wasm__x` for a const defined as `v3__gen__wasm__x`.
1932+
full_qname := g.const_storage_name(mod, node.value)
1933+
if full_qname in g.const_vals {
1934+
g.write(c_name(full_qname))
1935+
} else {
1936+
g.write(c_name('${short_mod}.${node.value}'))
1937+
}
19291938
} else if base.kind == .selector && base.children_count > 0
19301939
&& g.is_module_qualified_enum(base) {
19311940
inner_base := g.a.child_node(&base, 0)

vlib/v3/v3.v

Lines changed: 119 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ fn main() {
134134
mut no_parallel := false
135135
mut parallel_transform := false
136136
mut building_v := false
137+
mut all_backends := false
138+
mut compile_backends := []string{}
137139
mut user_defines := []string{}
138140
mut i := 0
139141
for i < args.len {
@@ -163,6 +165,12 @@ fn main() {
163165
} else if args[i] == '-parallel-transform' || args[i] == '--parallel-transform' {
164166
parallel_transform = true
165167
i++
168+
} else if args[i] == '-all-backends' || args[i] == '--all-backends' {
169+
all_backends = true
170+
i++
171+
} else if args[i] in ['-compile-backend', '--compile-backend'] && i + 1 < args.len {
172+
compile_backends << args[i + 1]
173+
i += 2
166174
} else if args[i] == '-d' && i + 1 < args.len {
167175
user_defines << args[i + 1]
168176
i += 2
@@ -209,6 +217,47 @@ fn main() {
209217
output_file = bin_file + '.c'
210218
}
211219

220+
// Decide which backend modules to compile into the output. By default only the C
221+
// backend is built; the arm64/wasm/eval backends (and the whole SSA pipeline that the
222+
// arm64 backend pulls in: v3.ssa + v3.ssa.optimize) are skipped entirely. When compiling
223+
// the V compiler itself this avoids parsing/checking/transforming/cgen-ing ~30k lines of
224+
// unused backend code, which measurably speeds up the self-host build. The `skip_*`
225+
// defines drive two things in lock-step: `$if !skip_* ?` gates in main() make the parser
226+
// drop the dispatch blocks (so the backend symbols are never referenced), and
227+
// resolve_imports skips parsing the corresponding module directories.
228+
// `-all-backends` keeps everything; `-compile-backend <name>` opts a specific backend back
229+
// in; the active `-b` target backend is always force-included.
230+
mut include_arm64 := all_backends
231+
mut include_wasm := all_backends
232+
mut include_eval := all_backends
233+
for cb in compile_backends {
234+
for name in cb.split(',') {
235+
match name.trim_space() {
236+
'arm64', 'aarch64' { include_arm64 = true }
237+
'wasm', 'wasm32' { include_wasm = true }
238+
'eval' { include_eval = true }
239+
// 'c' is always built; there is no native amd64 backend in v3 yet.
240+
else {}
241+
}
242+
}
243+
}
244+
match backend {
245+
'arm64' { include_arm64 = true }
246+
'wasm' { include_wasm = true }
247+
'eval' { include_eval = true }
248+
else {}
249+
}
250+
251+
if !include_arm64 {
252+
user_defines << 'skip_arm64'
253+
}
254+
if !include_wasm {
255+
user_defines << 'skip_wasm'
256+
}
257+
if !include_eval {
258+
user_defines << 'skip_eval'
259+
}
260+
212261
mut b := bench.new()
213262
println('=== v3 benchmark ===')
214263

@@ -273,14 +322,16 @@ fn main() {
273322
b.step('check')
274323

275324
if backend == 'eval' {
276-
mut runner := eval.new(prefs)
277-
runner.run_files(a) or {
278-
eprintln('error: ${err.msg()}')
279-
exit(1)
325+
$if !skip_eval ? {
326+
mut runner := eval.new(prefs)
327+
runner.run_files(a) or {
328+
eprintln('error: ${err.msg()}')
329+
exit(1)
330+
}
331+
b.step('eval')
332+
b.print_report()
333+
return
280334
}
281-
b.step('eval')
282-
b.print_report()
283-
return
284335
}
285336

286337
// Mark used functions (dead-code elimination). This is done before transform
@@ -306,21 +357,23 @@ fn main() {
306357
b.step('annotate types')
307358

308359
if backend == 'wasm' {
309-
// Direct flat-AST-to-WASM native backend. Runs before monomorphize (which
310-
// targets generics, not yet supported here). output_file is the exact path
311-
// requested via -o (or the <name>.wasm default).
312-
mut g := wasm.Gen.new(a, pre_tc, used_fns)
313-
g.gen()
314-
g.write(output_file) or {
315-
eprintln('error writing ${output_file}')
316-
exit(1)
317-
}
318-
for w in g.warnings_list() {
319-
eprintln('wasm: ${w}')
360+
$if !skip_wasm ? {
361+
// Direct flat-AST-to-WASM native backend. Runs before monomorphize (which
362+
// targets generics, not yet supported here). output_file is the exact path
363+
// requested via -o (or the <name>.wasm default).
364+
mut g := wasm.Gen.new(a, pre_tc, used_fns)
365+
g.gen()
366+
g.write(output_file) or {
367+
eprintln('error writing ${output_file}')
368+
exit(1)
369+
}
370+
for w in g.warnings_list() {
371+
eprintln('wasm: ${w}')
372+
}
373+
b.step('wasm gen')
374+
b.print_report()
375+
return
320376
}
321-
b.step('wasm gen')
322-
b.print_report()
323-
return
324377
}
325378

326379
// Monomorphization only adds specialized generic instantiations to `used_fns`. The V
@@ -332,21 +385,23 @@ fn main() {
332385
b.step('monomorphize')
333386

334387
if backend == 'arm64' {
335-
// SSA + ARM64 native backend
336-
mut m := ssa.build_with_used(a, used_fns, pre_tc)
337-
b.step('ssa build')
338-
339-
if is_prod {
340-
optimize.optimize(mut m)
341-
b.step('optimize')
342-
}
388+
$if !skip_arm64 ? {
389+
// SSA + ARM64 native backend
390+
mut m := ssa.build_with_used(a, used_fns, pre_tc)
391+
b.step('ssa build')
392+
393+
if is_prod {
394+
optimize.optimize(mut m)
395+
b.step('optimize')
396+
}
343397

344-
mut g := arm64.Gen.new(m)
345-
g.gen()
346-
b.step('arm64 gen')
398+
mut g := arm64.Gen.new(m)
399+
g.gen()
400+
b.step('arm64 gen')
347401

348-
g.write_and_link(bin_file)
349-
b.step('link')
402+
g.write_and_link(bin_file)
403+
b.step('link')
404+
}
350405
} else {
351406
// C backend (default)
352407
mut g := cgen.FlatGen.new()
@@ -566,12 +621,42 @@ fn path_is_in_dir(path string, dir string) bool {
566621
return real_path == real_dir || real_path.starts_with(real_dir + os.path_separator)
567622
}
568623

624+
// skipped_backend_modules lists the importable backend module names that the current
625+
// configuration excludes (driven by the same `skip_*` defines that gate the dispatch in
626+
// main()). The arm64 backend is the only consumer of the SSA pipeline, so skipping it also
627+
// skips v3.ssa and v3.ssa.optimize.
628+
fn skipped_backend_modules(prefs &pref.Preferences) []string {
629+
mut skipped := []string{}
630+
if 'skip_arm64' in prefs.user_defines {
631+
skipped << 'v3.gen.arm64'
632+
skipped << 'v3.ssa'
633+
skipped << 'v3.ssa.optimize'
634+
}
635+
if 'skip_wasm' in prefs.user_defines {
636+
skipped << 'v3.gen.wasm'
637+
}
638+
if 'skip_eval' in prefs.user_defines {
639+
skipped << 'v3.eval'
640+
}
641+
return skipped
642+
}
643+
569644
// resolve_imports resolves resolve imports information for v3 entry point.
570645
fn resolve_imports(mut a flat.FlatAst, mut p parser.Parser, prefs &pref.Preferences, initial_files []string) {
571646
mut parsed_modules := map[string]bool{}
572647
parsed_modules['builtin'] = true
573648
parsed_modules['main'] = true
574649

650+
// Backend modules excluded by the active configuration are never parsed: their
651+
// dispatch in main() is gated out by the matching `$if !skip_* ?`, so nothing
652+
// references their symbols. Pre-seeding parsed_modules makes the loop below treat
653+
// them as already handled, so neither v3.v's top-level imports nor any transitive
654+
// import pulls them in. Skipping the arm64 group (v3.gen.arm64 + the v3.ssa SSA
655+
// pipeline) and the wasm/eval backends avoids ~30k lines of work when self-hosting.
656+
for skipped in skipped_backend_modules(prefs) {
657+
parsed_modules[skipped] = true
658+
}
659+
575660
mut first_file := ''
576661
if initial_files.len > 0 {
577662
first_file = initial_files[0]

0 commit comments

Comments
 (0)