Skip to content

Commit 05c0a0f

Browse files
authored
cgen: snapshot Boehm scope GC pins on the stack, fixes #27467 (#27471)
1 parent c5ce895 commit 05c0a0f

1 file changed

Lines changed: 30 additions & 9 deletions

File tree

vlib/v/gen/c/cgen.v

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8164,26 +8164,47 @@ fn (mut g Gen) scope_gc_pin_pregen(node_pos int) []ScopeGcPin {
81648164
opened_scope = true
81658165
}
81668166
// Snapshot nested heap buffers before the call, then keep those snapshots
8167-
// reachable after it. This covers Boehm opt/noscan arrays of structs.
8167+
// reachable across it. This covers Boehm opt/noscan arrays of structs.
8168+
//
8169+
// The snapshot is normally placed in a small fixed stack array. Boehm scans
8170+
// the C stack conservatively, so the leaf pointers stored there stay rooted for
8171+
// the duration of the call without any GC root (de)registration. Only snapshots
8172+
// larger than the stack buffer fall back to an explicit `calloc` + `GC_add_roots`
8173+
// / `GC_remove_roots` pair. This avoids the per-call `GC_add_roots`/`GC_remove_roots`
8174+
// + `calloc`/`free` churn, which otherwise dominates hot loops that call functions
8175+
// while holding aggregates of pointers in scope (e.g. JSON encoding).
8176+
stack_cap := 32
81688177
tmp_name := g.new_tmp_var()
81698178
len_tmp_name := g.new_tmp_var()
81708179
roots_tmp_name := g.new_tmp_var()
8180+
stack_tmp_name := g.new_tmp_var()
8181+
on_heap_tmp_name := g.new_tmp_var()
81718182
setup_gc_state_name := g.new_tmp_var()
81728183
cleanup_gc_state_name := g.new_tmp_var()
8184+
styp := g.styp(obj.typ)
81738185
g.writeln('voidptr ${tmp_name} = &${cvar_name};')
8174-
g.writeln('int ${len_tmp_name} = ${collect_helper_name}((${g.styp(obj.typ)}*)${tmp_name}, 0, 0);')
8175-
g.writeln('int ${setup_gc_state_name} = GC_is_disabled();')
8176-
g.writeln('if (!${setup_gc_state_name}) { GC_disable(); }')
8177-
g.writeln('voidptr* ${roots_tmp_name} = 0;')
8178-
g.writeln('if (${len_tmp_name} > 0) {')
8186+
g.writeln('int ${len_tmp_name} = ${collect_helper_name}((${styp}*)${tmp_name}, 0, 0);')
8187+
g.writeln('voidptr ${stack_tmp_name}[${stack_cap}];')
8188+
g.writeln('voidptr* ${roots_tmp_name} = ${stack_tmp_name};')
8189+
g.writeln('bool ${on_heap_tmp_name} = false;')
8190+
g.writeln('if (${len_tmp_name} > ${stack_cap}) {')
8191+
// Oversized snapshot: the original explicit-roots path, guarded against a
8192+
// collection happening between allocation and registration.
8193+
g.writeln('\tint ${setup_gc_state_name} = GC_is_disabled();')
8194+
g.writeln('\tif (!${setup_gc_state_name}) { GC_disable(); }')
81798195
g.writeln('\t${roots_tmp_name} = (voidptr*)calloc(${len_tmp_name}, sizeof(voidptr));')
81808196
g.writeln('\tif (${roots_tmp_name} == 0) { builtin___memory_panic(_S("calloc"), sizeof(voidptr) * ${len_tmp_name}); }')
8181-
g.writeln('\t${collect_helper_name}((${g.styp(obj.typ)}*)${tmp_name}, ${roots_tmp_name}, 0);')
8197+
g.writeln('\t${on_heap_tmp_name} = true;')
8198+
g.writeln('\t${collect_helper_name}((${styp}*)${tmp_name}, ${roots_tmp_name}, 0);')
81828199
g.writeln('\tGC_add_roots(${roots_tmp_name}, ${roots_tmp_name} + ${len_tmp_name});')
8200+
g.writeln('\tif (!${setup_gc_state_name}) { GC_enable(); }')
8201+
g.writeln('} else if (${len_tmp_name} > 0) {')
8202+
// Common case: snapshot into the stack buffer. The fill performs no allocation,
8203+
// so no collection can run while it is partially filled.
8204+
g.writeln('\t${collect_helper_name}((${styp}*)${tmp_name}, ${roots_tmp_name}, 0);')
81838205
g.writeln('}')
8184-
g.writeln('if (!${setup_gc_state_name}) { GC_enable(); }')
81858206
pins << ScopeGcPin{
8186-
post_stmt: 'GC_reachable_here(${tmp_name}); if (${len_tmp_name} > 0) { for (int _v_keep_i = 0; _v_keep_i < ${len_tmp_name}; ++_v_keep_i) { GC_reachable_here(${roots_tmp_name}[_v_keep_i]); } } int ${cleanup_gc_state_name} = GC_is_disabled(); if (!${cleanup_gc_state_name}) { GC_disable(); } if (${len_tmp_name} > 0) { GC_remove_roots(${roots_tmp_name}, ${roots_tmp_name} + ${len_tmp_name}); free(${roots_tmp_name}); } if (!${cleanup_gc_state_name}) { GC_enable(); }'
8207+
post_stmt: 'GC_reachable_here(${tmp_name}); if (${len_tmp_name} > 0) { for (int _v_keep_i = 0; _v_keep_i < ${len_tmp_name}; ++_v_keep_i) { GC_reachable_here(${roots_tmp_name}[_v_keep_i]); } } GC_reachable_here(${roots_tmp_name}); if (${on_heap_tmp_name}) { int ${cleanup_gc_state_name} = GC_is_disabled(); if (!${cleanup_gc_state_name}) { GC_disable(); } GC_remove_roots(${roots_tmp_name}, ${roots_tmp_name} + ${len_tmp_name}); free(${roots_tmp_name}); if (!${cleanup_gc_state_name}) { GC_enable(); } }'
81878208
}
81888209
}
81898210
return pins

0 commit comments

Comments
 (0)