Skip to content

Commit 11af49e

Browse files
committed
v2: 3x cgen speedup (caching)
1 parent e52ff06 commit 11af49e

8 files changed

Lines changed: 176 additions & 13 deletions

File tree

cmd/v2/test_all.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ rm -rf /tmp/v2_cleanc_obj_cache
3131

3232
echo "=== 1/14: ARM64 self-host hello world ==="
3333
backup_v2_src
34-
"${v1_compiler}" -cc cc -o v2 v2.v
34+
"${v1_compiler}" -gc none -cc cc -o v2 v2.v
3535
restore_v2_src
3636
./v2 --no-parallel -backend arm64 -gc none -nocache -o v3 v2.v
3737
./v3 --no-parallel -backend arm64 -o hello_arm hello.v

cmd/v2/test_v2_self.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ cp -R "${v2_src}" "${v2_bak}"
3232

3333
# Build v2 with v1.
3434
rm -f "${v2_bin}" "${v3_bin}" "${v3_bin}.c" "${v4_bin}" "${v4_bin}.c" "${v5_bin}" "${v5_bin}.c"
35-
"${v1_compiler}" -cc cc -o "${v2_bin}" "${v2_source}"
35+
"${v1_compiler}" -gc none -cc cc -o "${v2_bin}" "${v2_source}"
3636

3737
# Restore v2 sources after V1 build.
3838
rsync -a --delete "${v2_bak}/" "${v2_src}/"

vlib/v2/builder/gen_cleanc_parallel.v

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
module builder
55

66
import runtime
7+
import time
78
import v2.gen.cleanc
89

910
struct GenCleancChunkArgs {
@@ -28,16 +29,39 @@ fn gen_cleanc_chunk_thread(arg voidptr) voidptr {
2829
return unsafe { nil }
2930
}
3031

32+
fn print_cleanc_parallel_step_time(stats_enabled bool, step string, elapsed time.Duration) {
33+
if !stats_enabled {
34+
return
35+
}
36+
println(' - C Gen/full ${step}: ${elapsed.milliseconds()}ms')
37+
}
38+
39+
fn mark_cleanc_parallel_step(stats_enabled bool, mut sw time.StopWatch, stage_start time.Duration, step string) time.Duration {
40+
if !stats_enabled {
41+
return stage_start
42+
}
43+
now := sw.elapsed()
44+
print_cleanc_parallel_step_time(true, step, time.Duration(now - stage_start))
45+
return now
46+
}
47+
3148
fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) {
49+
stats_enabled := b.pref != unsafe { nil } && b.pref.stats
50+
mut stats_sw := time.new_stopwatch()
51+
mut stage_start := stats_sw.elapsed()
3252
emit_indices := gen.gen_pass5_pre()
53+
stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, 'pass 5 pre')
3354

3455
n_files := emit_indices.len
3556
n_jobs := runtime.nr_jobs()
3657

3758
if n_files <= 1 || n_jobs <= 1 {
3859
// Fallback to sequential
3960
gen.gen_pass5_files(emit_indices)
61+
stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start,
62+
'pass 5 files')
4063
gen.gen_pass5_post()
64+
_ = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, 'pass 5 post')
4165
return
4266
}
4367

@@ -59,10 +83,14 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) {
5983
for i := 0; i < n_files; i++ {
6084
chunk_indices[i % chunk_idx] << emit_indices[i]
6185
}
86+
stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start,
87+
'pass 5 chunk split')
6288
for ci := 0; ci < chunk_idx; ci++ {
6389
w := gen.new_pass5_worker(chunk_indices[ci], ci)
6490
workers << voidptr(w)
6591
}
92+
stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start,
93+
'pass 5 worker setup')
6694

6795
// Set up args after all chunk_indices are stable
6896
for ci := 0; ci < chunk_idx; ci++ {
@@ -87,12 +115,17 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) {
87115
for ci := 0; ci < chunk_idx; ci++ {
88116
C.pthread_join(thread_ids[ci], unsafe { nil })
89117
}
118+
stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start,
119+
'pass 5 worker run')
90120

91121
// Merge worker results in order
92122
for ci := 0; ci < chunk_idx; ci++ {
93123
w := unsafe { &cleanc.Gen(workers[ci]) }
94124
gen.merge_pass5_worker(w)
95125
}
126+
stage_start = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start,
127+
'pass 5 merge')
96128

97129
gen.gen_pass5_post()
130+
_ = mark_cleanc_parallel_step(stats_enabled, mut stats_sw, stage_start, 'pass 5 post')
98131
}

vlib/v2/gen/cleanc/cheaders.v

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,13 @@ fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct {
857857
if g.env == unsafe { nil } {
858858
return types.Struct{}
859859
}
860+
cache_key := '${g.cur_module}|${c_name}'
861+
if cached := g.struct_type_lookup_cache[cache_key] {
862+
return cached
863+
}
864+
if cache_key in g.struct_type_lookup_miss {
865+
return types.Struct{}
866+
}
860867
// Try extracting module from mangled name (e.g. "os__File" -> module "os", name "File")
861868
mut mod_name := ''
862869
mut struct_name := c_name
@@ -870,9 +877,11 @@ fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct {
870877
typ := obj.typ()
871878
// Skip sum types - their merged fields aren't safe for direct access
872879
if typ is types.Alias || typ is types.SumType {
880+
g.struct_type_lookup_miss[cache_key] = true
873881
return types.Struct{}
874882
}
875883
if typ is types.Struct {
884+
g.struct_type_lookup_cache[cache_key] = typ
876885
return typ
877886
}
878887
}
@@ -890,9 +899,11 @@ fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct {
890899
if obj := scope.lookup_parent(struct_name, 0) {
891900
typ := obj.typ()
892901
if typ is types.Alias {
902+
g.struct_type_lookup_miss[cache_key] = true
893903
return types.Struct{}
894904
}
895905
if typ is types.Struct {
906+
g.struct_type_lookup_cache[cache_key] = typ
896907
return typ
897908
}
898909
}
@@ -909,14 +920,17 @@ fn (mut g Gen) lookup_struct_type_by_c_name(c_name string) types.Struct {
909920
if obj := scope.lookup_parent(struct_name, 0) {
910921
typ := obj.typ()
911922
if typ is types.Alias {
923+
g.struct_type_lookup_miss[cache_key] = true
912924
return types.Struct{}
913925
}
914926
if typ is types.Struct {
927+
g.struct_type_lookup_cache[cache_key] = typ
915928
return typ
916929
}
917930
}
918931
}
919932
}
933+
g.struct_type_lookup_miss[cache_key] = true
920934
return types.Struct{}
921935
}
922936

vlib/v2/gen/cleanc/cleanc.v

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,14 @@ mut:
8484
not_local_var_cache map[string]bool // per-function negative cache for get_local_var_c_type
8585
resolved_module_names map[string]string // per-function cache for resolve_module_name
8686
cached_env_scopes map[string]voidptr // cache of env_scope results (avoids repeated locking)
87+
struct_field_lookup_cache map[string]string
88+
struct_field_lookup_miss map[string]bool
89+
struct_type_lookup_cache map[string]types.Struct
90+
struct_type_lookup_miss map[string]bool
91+
struct_decl_info_cache map[string]StructDeclInfo
92+
struct_decl_info_miss map[string]bool
93+
alias_base_lookup_cache map[string]string
94+
alias_base_lookup_miss map[string]bool
8795

8896
const_exprs map[string]string // const name → C expression string (for inlining)
8997
const_types map[string]string // const name → C type string
@@ -217,16 +225,24 @@ pub fn Gen.new_with_env(files []ast.File, env &types.Environment) &Gen {
217225

218226
pub fn Gen.new_with_env_and_pref(files []ast.File, env &types.Environment, p &pref.Preferences) &Gen {
219227
return &Gen{
220-
files: files
221-
env: unsafe { env }
222-
pref: unsafe { p }
223-
sb: strings.new_builder(10_000)
224-
fn_param_is_ptr: map[string][]bool{}
225-
fn_param_types: map[string][]string{}
226-
fn_return_types: map[string]string{}
227-
runtime_local_types: map[string]string{}
228-
cur_fn_returned_idents: map[string]bool{}
229-
active_generic_types: map[string]types.Type{}
228+
files: files
229+
env: unsafe { env }
230+
pref: unsafe { p }
231+
sb: strings.new_builder(10_000)
232+
fn_param_is_ptr: map[string][]bool{}
233+
fn_param_types: map[string][]string{}
234+
fn_return_types: map[string]string{}
235+
runtime_local_types: map[string]string{}
236+
cur_fn_returned_idents: map[string]bool{}
237+
active_generic_types: map[string]types.Type{}
238+
struct_field_lookup_cache: map[string]string{}
239+
struct_field_lookup_miss: map[string]bool{}
240+
struct_type_lookup_cache: map[string]types.Struct{}
241+
struct_type_lookup_miss: map[string]bool{}
242+
struct_decl_info_cache: map[string]StructDeclInfo{}
243+
struct_decl_info_miss: map[string]bool{}
244+
alias_base_lookup_cache: map[string]string{}
245+
alias_base_lookup_miss: map[string]bool{}
230246

231247
fixed_array_fields: map[string]bool{}
232248
fixed_array_field_elem: map[string]string{}
@@ -1080,6 +1096,11 @@ fn (g &Gen) should_skip_plain_v_fallback_fn(fn_key string) bool {
10801096

10811097
// gen_finalize runs post-pass-5 finalization and returns the complete C source string.
10821098
pub fn (mut g Gen) gen_finalize() string {
1099+
stats_enabled := g.cgen_stats_enabled()
1100+
stats_scope := g.cgen_stats_scope_label()
1101+
mut stats_sw := time.new_stopwatch()
1102+
mut stage_start := stats_sw.elapsed()
1103+
10831104
// Generate test runner main if this is a test file (has test_ functions but no main)
10841105
// Skip when generating cached module sources (cache_bundle_name is set) - main belongs only in the main module source
10851106
if !g.has_main && g.test_fn_names.len > 0 && g.cache_bundle_name.len == 0 {
@@ -1126,11 +1147,15 @@ pub fn (mut g Gen) gen_finalize() string {
11261147
g.sb.writeln('\treturn 0;')
11271148
g.sb.writeln('}')
11281149
}
1150+
stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1151+
'finalize test main')
11291152

11301153
g.emit_missing_array_contains_fallbacks()
11311154
g.emit_missing_runtime_fallbacks()
11321155
g.emit_cached_module_init_function()
11331156
g.emit_exported_const_symbols()
1157+
stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1158+
'finalize fallbacks')
11341159

11351160
mut out := ''
11361161
// Emit deferred str macros for late-discovered generic struct instances.
@@ -1142,9 +1167,13 @@ pub fn (mut g Gen) gen_finalize() string {
11421167
g.late_struct_defs << '#define ${inst_name}__str(v) ((string){.str = "${label}", .len = ${label.len}, .is_lit = 1})\n#define ${inst_name}_str(v) ${inst_name}__str(v)\n'
11431168
}
11441169
}
1170+
stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1171+
'finalize late str macros')
11451172
if g.anon_fn_defs.len > 0 || g.spawn_wrapper_defs.len > 0 || g.trampoline_defs.len > 0
11461173
|| g.late_struct_defs.len > 0 || g.pending_late_body_keys.len > 0 {
11471174
full := g.sb.str()
1175+
stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1176+
'finalize snapshot')
11481177
mut out_sb := strings.new_builder(full.len + 4096)
11491178
unsafe { out_sb.write_ptr(full.str, g.pass5_start_pos) }
11501179
// Late-discovered generic struct definitions (discovered during setup/pass 4 codegen)
@@ -1183,12 +1212,20 @@ pub fn (mut g Gen) gen_finalize() string {
11831212
} else {
11841213
out = g.sb.str()
11851214
}
1215+
_ = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'finalize output')
11861216
return out
11871217
}
11881218

11891219
// gen_pass5 generates Pass 5 (function bodies, globals, etc.) sequentially.
11901220
fn (mut g Gen) gen_pass5() {
1221+
stats_enabled := g.cgen_stats_enabled()
1222+
stats_scope := g.cgen_stats_scope_label()
1223+
mut stats_sw := time.new_stopwatch()
1224+
mut stage_start := stats_sw.elapsed()
1225+
11911226
g.pass5_start_pos = g.sb.len
1227+
g.struct_field_lookup_cache = map[string]string{}
1228+
g.struct_field_lookup_miss = map[string]bool{}
11921229
g.collect_force_emit_str_fns()
11931230
// Pre-pass: emit extern forward declarations for all globals across all modules
11941231
for file in g.files {
@@ -1203,13 +1240,18 @@ fn (mut g Gen) gen_pass5() {
12031240
}
12041241
g.gen_file(file)
12051242
}
1243+
stage_start = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start,
1244+
'pass 5 files')
12061245
g.gen_pass5_post()
1246+
_ = g.mark_cgen_step(stats_enabled, stats_scope, mut stats_sw, stage_start, 'pass 5 post')
12071247
}
12081248

12091249
// gen_pass5_pre runs Pass 5 sequential pre-work: extern globals and extern consts
12101250
// for non-emitted modules. Returns the list of file indices that need gen_file().
12111251
pub fn (mut g Gen) gen_pass5_pre() []int {
12121252
g.pass5_start_pos = g.sb.len
1253+
g.struct_field_lookup_cache = map[string]string{}
1254+
g.struct_field_lookup_miss = map[string]bool{}
12131255
g.collect_force_emit_str_fns()
12141256
for file in g.files {
12151257
g.set_file_module(file)
@@ -1353,6 +1395,14 @@ pub fn (g &Gen) new_pass5_worker(file_indices []int, worker_id int) &Gen {
13531395
resolved_module_names: map[string]string{}
13541396
cur_fn_mut_params: map[string]bool{}
13551397
cached_env_scopes: map[string]voidptr{}
1398+
struct_field_lookup_cache: map[string]string{}
1399+
struct_field_lookup_miss: map[string]bool{}
1400+
struct_type_lookup_cache: map[string]types.Struct{}
1401+
struct_type_lookup_miss: map[string]bool{}
1402+
struct_decl_info_cache: map[string]StructDeclInfo{}
1403+
struct_decl_info_miss: map[string]bool{}
1404+
alias_base_lookup_cache: map[string]string{}
1405+
alias_base_lookup_miss: map[string]bool{}
13561406
needed_interface_wrappers: map[string]bool{}
13571407
needed_ierror_wrapper_bases: map[string]bool{}
13581408
spawned_fns: map[string]bool{}

vlib/v2/gen/cleanc/fn.v

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4621,8 +4621,19 @@ fn (g &Gen) alias_base_c_type(type_name string) ?string {
46214621
if type_name == '' {
46224622
return none
46234623
}
4624+
cache_key := '${g.cur_module}|${type_name}'
4625+
if cached := g.alias_base_lookup_cache[cache_key] {
4626+
return cached
4627+
}
4628+
if cache_key in g.alias_base_lookup_miss {
4629+
return none
4630+
}
46244631
if base_name := g.alias_base_types[type_name] {
46254632
if base_name != '' && base_name != type_name {
4633+
unsafe {
4634+
mut self := g
4635+
self.alias_base_lookup_cache[cache_key] = base_name
4636+
}
46264637
return base_name
46274638
}
46284639
}
@@ -4641,12 +4652,20 @@ fn (g &Gen) alias_base_c_type(type_name string) ?string {
46414652
}
46424653
base_name := stmt.base_type.name().replace('.', '__')
46434654
if base_name != '' && base_name != type_name {
4655+
unsafe {
4656+
mut self := g
4657+
self.alias_base_lookup_cache[cache_key] = base_name
4658+
}
46444659
return base_name
46454660
}
46464661
}
46474662
}
46484663
}
46494664
if g.env == unsafe { nil } {
4665+
unsafe {
4666+
mut self := g
4667+
self.alias_base_lookup_miss[cache_key] = true
4668+
}
46504669
return none
46514670
}
46524671
mut modules := []string{}
@@ -4673,6 +4692,10 @@ fn (g &Gen) alias_base_c_type(type_name string) ?string {
46734692
alias_obj := obj as types.Alias
46744693
base_name := g.types_type_to_c(alias_obj.base_type)
46754694
if base_name != '' && base_name != type_name {
4695+
unsafe {
4696+
mut self := g
4697+
self.alias_base_lookup_cache[cache_key] = base_name
4698+
}
46764699
return base_name
46774700
}
46784701
}
@@ -4691,12 +4714,20 @@ fn (g &Gen) alias_base_c_type(type_name string) ?string {
46914714
alias_obj := obj as types.Alias
46924715
base_name := g.types_type_to_c(alias_obj.base_type)
46934716
if base_name != '' && base_name != type_name {
4717+
unsafe {
4718+
mut self := g
4719+
self.alias_base_lookup_cache[cache_key] = base_name
4720+
}
46944721
return base_name
46954722
}
46964723
}
46974724
}
46984725
}
46994726
}
4727+
unsafe {
4728+
mut self := g
4729+
self.alias_base_lookup_miss[cache_key] = true
4730+
}
47004731
return none
47014732
}
47024733

vlib/v2/gen/cleanc/struct.v

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,8 @@ fn (mut g Gen) gen_struct_decl(node ast.StructDecl) {
750750
// Register field types for this instantiation
751751
g.struct_field_types['${inst.c_name}.${field_name}'] = field_type
752752
}
753+
g.struct_field_lookup_cache = map[string]string{}
754+
g.struct_field_lookup_miss = map[string]bool{}
753755
if node.fields.len == 0 {
754756
g.sb.writeln('\tu8 _dummy;')
755757
}

0 commit comments

Comments
 (0)