Skip to content

Commit 1a32401

Browse files
committed
v2: prealloc
1 parent a5403f9 commit 1a32401

9 files changed

Lines changed: 76 additions & 9 deletions

File tree

vlib/builtin/prealloc.c.v

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ fn vmemory_block_new(prev &VMemoryBlock, at_least isize, align isize) &VMemoryBl
8989

9090
@[unsafe]
9191
fn vmemory_block_malloc(n isize, align isize) &u8 {
92+
unsafe {
93+
// Lazy per-thread initialization: when g_memory_block is thread-local,
94+
// new threads start with a null pointer and need their own arena.
95+
if g_memory_block == nil {
96+
g_memory_block = vmemory_block_new(nil, isize(prealloc_block_size), 0)
97+
}
98+
}
9299
$if prealloc_trace_malloc ? {
93100
C.fprintf(C.stderr, c'vmemory_block_malloc g_memory_block.id: %d, n: %lld align: %d\n',
94101
g_memory_block.id, n, align)

vlib/v2/gen/cleanc/cheaders.v

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,13 @@ fn (mut g Gen) write_preamble() {
249249
preamble_includes_full
250250
})
251251
g.emit_collected_c_directives()
252+
if g.pref != unsafe { nil } && g.pref.prealloc {
253+
g.sb.writeln('#define _VPREALLOC (1)')
254+
// Save the real free() before redefining it as a no-op.
255+
// prealloc_vcleanup needs the real free to release arena chunks.
256+
g.sb.writeln('static inline void _v_cfree(void *p) { free(p); }')
257+
g.sb.writeln('#define free(p) ((void)(p), (void)0)')
258+
}
252259
g.sb.writeln('')
253260

254261
// V primitive type aliases

vlib/v2/gen/cleanc/cleanc.v

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,7 @@ fn is_builtin_runtime_keep_file(path string) bool {
461461
|| normalized.ends_with('vlib/builtin/builtin.v')
462462
|| normalized.ends_with('vlib/builtin/cfns_wrapper.c.v')
463463
|| normalized.ends_with('vlib/builtin/allocation.c.v')
464+
|| normalized.ends_with('vlib/builtin/prealloc.c.v')
464465
|| normalized.ends_with('vlib/builtin/panicing.c.v')
465466
|| normalized.ends_with('vlib/builtin/chan_option_result.v')
466467
|| normalized.ends_with('vlib/builtin/int.v') || normalized.ends_with('vlib/builtin/rune.v')

vlib/v2/gen/cleanc/consts_and_globals.v

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,9 +342,10 @@ fn (mut g Gen) gen_const_decl_extern(node ast.ConstDecl) {
342342
if value_expr.len == 0 {
343343
continue
344344
}
345-
// Array consts with static backing data cannot be #defined across
346-
// translation units. Emit extern declarations instead.
347-
if value_expr.contains('__const_array_data_') {
345+
// Array consts with static backing data or inline compound literals
346+
// cannot be #defined across translation units. Emit extern declarations instead.
347+
if value_expr.contains('__const_array_data_')
348+
|| value_expr.contains('new_array_from_c_array') {
348349
g.emitted_types[macro_key] = true
349350
g.sb.writeln('extern array ${name};')
350351
continue
@@ -436,7 +437,13 @@ fn (mut g Gen) gen_global_decl(node ast.GlobalDecl) {
436437
typ = 'int'
437438
}
438439
g.global_var_types[name] = typ
439-
g.sb.write_string('${typ} ${name}')
440+
// With prealloc, g_memory_block must be thread-local so each thread
441+
// gets its own arena and the bump allocator is safe without locks.
442+
if name == 'g_memory_block' && g.pref != unsafe { nil } && g.pref.prealloc {
443+
g.sb.write_string('_Thread_local ${typ} ${name}')
444+
} else {
445+
g.sb.write_string('${typ} ${name}')
446+
}
440447
if field.value !is ast.EmptyExpr {
441448
// Function calls are not compile-time constants in C
442449
if g.contains_call_expr(field.value) {
@@ -486,7 +493,11 @@ fn (mut g Gen) gen_global_decl_extern(node ast.GlobalDecl) {
486493
typ = 'int'
487494
}
488495
g.global_var_types[name] = typ
489-
g.sb.writeln('extern ${typ} ${name};')
496+
if name == 'g_memory_block' && g.pref != unsafe { nil } && g.pref.prealloc {
497+
g.sb.writeln('extern _Thread_local ${typ} ${name};')
498+
} else {
499+
g.sb.writeln('extern ${typ} ${name};')
500+
}
490501
}
491502
}
492503

vlib/v2/gen/cleanc/expr.v

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3128,7 +3128,10 @@ fn (g &Gen) eval_comptime_flag(name string) bool {
31283128
'tinyc' {
31293129
return g.pref != unsafe { nil } && (g.pref.backend == .arm64 || g.pref.backend == .x64)
31303130
}
3131-
'new_int', 'gcboehm', 'prealloc', 'autofree' {
3131+
'prealloc' {
3132+
return g.pref != unsafe { nil } && g.pref.prealloc
3133+
}
3134+
'new_int', 'gcboehm', 'autofree', 'ppc64' {
31323135
return false
31333136
}
31343137
else {

vlib/v2/gen/cleanc/fn.v

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,11 @@ fn (mut g Gen) gen_fn_decl_with_name_ptr(node &ast.FnDecl, fn_name string) {
14571457
g.sb.writeln('g_main_argc = ___argc;')
14581458
g.write_indent()
14591459
g.sb.writeln('g_main_argv = (void*)___argv;')
1460+
// Prealloc arena initialization (must run before any V allocations)
1461+
if g.pref != unsafe { nil } && g.pref.prealloc {
1462+
g.write_indent()
1463+
g.sb.writeln('prealloc_vinit();')
1464+
}
14601465
// GC initialization (translated from Go's runtime GC init)
14611466
if g.pref != unsafe { nil } && g.pref.gc_mode == .vgc {
14621467
g.write_indent()
@@ -2131,6 +2136,11 @@ fn (g &Gen) is_simple_addressable(expr ast.Expr) bool {
21312136

21322137
fn (mut g Gen) gen_addr_of_expr(arg ast.Expr, typ string) {
21332138
base_arg := if arg is ast.ModifierExpr { arg.expr } else { arg }
2139+
// nil is a null pointer — emit NULL directly, never wrap in compound literal
2140+
if base_arg is ast.Ident && base_arg.name == 'nil' {
2141+
g.sb.write_string('NULL')
2142+
return
2143+
}
21342144
if g.can_take_address(base_arg) {
21352145
g.sb.write_string('&')
21362146
g.expr(base_arg)
@@ -2303,6 +2313,19 @@ fn (mut g Gen) gen_call_arg(fn_name string, idx int, arg ast.Expr) {
23032313
return
23042314
}
23052315
}
2316+
// nil is always a valid pointer value (NULL) — never wrap it
2317+
if (base_arg is ast.Ident && base_arg.name == 'nil') || base_arg is ast.Type {
2318+
if ptr_params := g.fn_param_is_ptr[fn_name] {
2319+
if idx < ptr_params.len && ptr_params[idx] {
2320+
g.sb.write_string('NULL')
2321+
return
2322+
}
2323+
}
2324+
if base_arg is ast.Ident && base_arg.name == 'nil' {
2325+
g.sb.write_string('NULL')
2326+
return
2327+
}
2328+
}
23062329
if fn_name == 'new_map_init_noscan_value' && idx in [7, 8] {
23072330
// new_map_init_noscan_value expects raw key/value element pointers.
23082331
// Lowered dynamic arrays arrive as `array` values; pass their `.data`.
@@ -3592,6 +3615,11 @@ fn (mut g Gen) call_expr(lhs ast.Expr, args []ast.Expr) {
35923615
// Handle C.puts, C.putchar etc.
35933616
if lhs.lhs is ast.Ident && lhs.lhs.name == 'C' {
35943617
name = lhs.rhs.name
3618+
// With prealloc, free() is redefined as a no-op macro.
3619+
// C.free calls in prealloc_vcleanup need the real free via _v_cfree.
3620+
if name == 'free' && g.pref != unsafe { nil } && g.pref.prealloc {
3621+
name = '_v_cfree'
3622+
}
35953623
g.sb.write_string('${name}(')
35963624
for i in 0 .. args.len {
35973625
arg := args[i]

vlib/v2/pref/pref.v

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub mut:
5050
is_shared_lib bool // Compile to shared library (.dylib/.so) for live reload
5151
no_optimize bool // -O0: skip SSA optimization (mem2reg, phi elimination)
5252
is_prod bool // -prod: use -O3 optimization for C compiler
53+
prealloc bool // -prealloc: use arena allocation (bump-pointer, not thread-safe)
5354
gc_mode GarbageCollectionMode // Garbage collection mode (-gc flag)
5455
backend Backend
5556
arch Arch = .auto
@@ -290,7 +291,7 @@ pub fn new_preferences_from_args(args []string) Preferences {
290291
'-nomarkused', '--nomarkused', '-showcc', '--showcc', '-stats', '--stats',
291292
'-print-parsed-files', '--print-parsed-files', '-keepc', '--profile-alloc', '-profile-alloc',
292293
'-enable-globals', '--enable-globals', '-shared', '--shared', '-O0', '--single-backend',
293-
'-single-backend', '-prod']
294+
'-single-backend', '-prod', '-prealloc']
294295
for opt in options {
295296
if opt !in known_flags_with_values && opt !in known_boolean_flags {
296297
eprintln('error: unknown flag `${opt}`')
@@ -308,6 +309,7 @@ pub fn new_preferences_from_args(args []string) Preferences {
308309
eprintln(' -d <name> Define a comptime flag')
309310
eprintln(' -enable-globals Accepted for v1 compatibility')
310311
eprintln(' -prod Production build: optimize with -O3 -flto')
312+
eprintln(' -prealloc Use arena allocation (faster, not thread-safe)')
311313
eprintln(' -O0 Skip SSA optimization (faster compile, slower code)')
312314
eprintln(' --debug Enable debug mode')
313315
eprintln(' -v, --verbose Enable verbose output')
@@ -338,6 +340,7 @@ pub fn new_preferences_from_args(args []string) Preferences {
338340
is_shared_lib: '-shared' in options || '--shared' in options
339341
no_optimize: '-O0' in options
340342
is_prod: '-prod' in options
343+
prealloc: '-prealloc' in options
341344
single_backend: '--single-backend' in options || '-single-backend' in options
342345
gc_mode: gc_mode
343346
backend: backend
@@ -395,6 +398,7 @@ pub fn new_preferences_using_options(options []string) Preferences {
395398
is_shared_lib: '-shared' in options || '--shared' in options
396399
no_optimize: '-O0' in options
397400
is_prod: '-prod' in options
401+
prealloc: '-prealloc' in options
398402
single_backend: '--single-backend' in options || '-single-backend' in options
399403
backend: backend
400404
arch: arch

vlib/v2/transformer/if.v

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1172,8 +1172,11 @@ fn (t &Transformer) eval_comptime_flag(name string) bool {
11721172
// guards select the software fallback path instead of inline asm.
11731173
return t.pref != unsafe { nil } && (t.pref.backend == .arm64 || t.pref.backend == .x64)
11741174
}
1175+
'prealloc' {
1176+
return t.pref != unsafe { nil } && t.pref.prealloc
1177+
}
11751178
// Feature flags that are typically false
1176-
'new_int', 'gcboehm', 'prealloc', 'autofree' {
1179+
'new_int', 'gcboehm', 'autofree', 'ppc64' {
11771180
return false
11781181
}
11791182
else {

vlib/v2/types/checker.v

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3078,7 +3078,10 @@ fn (c &Checker) eval_comptime_flag(name string) bool {
30783078
'builtin_write_buf_to_fd_should_use_c_write' {
30793079
return c.pref != unsafe { nil } && (c.pref.backend == .arm64 || c.pref.backend == .x64)
30803080
}
3081-
'new_int', 'gcboehm', 'prealloc', 'autofree' {
3081+
'prealloc' {
3082+
return c.pref != unsafe { nil } && c.pref.prealloc
3083+
}
3084+
'new_int', 'gcboehm', 'autofree', 'ppc64' {
30823085
return false
30833086
}
30843087
else {

0 commit comments

Comments
 (0)