From 8f2a3d74ade1bfbff0448ac51c6f796d9f685ee4 Mon Sep 17 00:00:00 2001 From: Felix Ehlers Date: Sun, 28 Dec 2025 14:20:14 +0100 Subject: [PATCH] cgen: fix stack overflow for `@[heap]` structs with large fixed arrays (fix #22690) (#26183) --- vlib/v/gen/c/assign.v | 50 ++++++++++++- vlib/v/gen/c/struct.v | 22 ++++++ .../struct_heap_large_fixed_array_test.v | 71 +++++++++++++++++++ 3 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 vlib/v/tests/structs/struct_heap_large_fixed_array_test.v diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index b30729b2f..c8aa8de56 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -992,7 +992,14 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { } else { is_option_unwrapped := val is ast.Ident && val.or_expr.kind != .absent is_option_auto_heap := is_auto_heap && is_option_unwrapped - if is_auto_heap && !is_fn_var { + // For large structs (with large fixed arrays), avoid stack-allocated + // compound literals which can cause stack overflow. Use vcalloc directly. + mut is_large_struct_heap := false + if is_auto_heap && !is_fn_var && val is ast.StructInit + && g.struct_has_large_fixed_array(val.typ) { + is_large_struct_heap = true + } + if is_auto_heap && !is_fn_var && !is_large_struct_heap { if aligned != 0 { g.write('HEAP_align(${styp}, (') } else { @@ -1037,6 +1044,44 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { tmp_var := g.expr_with_var(val, var_type, false) g.fixed_array_var_init(tmp_var, false, unaliased_right_sym.info.elem_type, unaliased_right_sym.info.size) + } else if is_large_struct_heap && val is ast.StructInit { + // For large structs, use vcalloc directly to avoid stack overflow + // from compound literals on the stack + tmp_var := g.new_tmp_var() + stmt_str := g.go_before_last_stmt() + g.empty_line = true + g.writeln('${styp}* ${tmp_var} = (${styp}*)builtin__vcalloc(sizeof(${styp}));') + // Initialize non-zero fields + val_sym := g.table.final_sym(val.typ) + if val_sym.info is ast.Struct { + for init_field in val.init_fields { + if init_field.typ == 0 { + continue + } + field_name := c_name(init_field.name) + g.write('${tmp_var}->${field_name} = ') + g.expr(init_field.expr) + g.writeln(';') + } + // Handle fields with default values + for field in val_sym.info.fields { + mut found := false + for init_field in val.init_fields { + if init_field.name == field.name { + found = true + break + } + } + if !found && field.has_default_expr { + field_name := c_name(field.name) + g.write('${tmp_var}->${field_name} = ') + g.expr(field.default_expr) + g.writeln(';') + } + } + } + g.empty_line = false + g.write2(stmt_str, tmp_var) } else { old_inside_assign_fn_var := g.inside_assign_fn_var g.inside_assign_fn_var = val is ast.PrefixExpr && val.op == .amp @@ -1044,7 +1089,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { g.expr(val) g.inside_assign_fn_var = old_inside_assign_fn_var } - if !is_fn_var && is_auto_heap && !is_option_auto_heap { + if !is_fn_var && is_auto_heap && !is_option_auto_heap + && !is_large_struct_heap { if aligned != 0 { g.write('), ${aligned})') } else { diff --git a/vlib/v/gen/c/struct.v b/vlib/v/gen/c/struct.v index 7ee976a92..ba00916da 100644 --- a/vlib/v/gen/c/struct.v +++ b/vlib/v/gen/c/struct.v @@ -821,3 +821,25 @@ fn (mut g Gen) struct_init_field_default(field_unwrap_typ ast.Type, sfield &ast. g.expr_with_cast(sfield.expr, field_unwrap_typ, sfield.expected_type) } } + +// struct_has_large_fixed_array returns true if the struct type contains +// fixed arrays whose total size exceeds the threshold (64KB). +// This is used to determine whether to avoid stack-allocated compound literals +// which can cause stack overflow for large structs. +fn (g &Gen) struct_has_large_fixed_array(typ ast.Type) bool { + sym := g.table.final_sym(typ) + if sym.info is ast.Struct { + for field in sym.info.fields { + field_sym := g.table.final_sym(field.typ) + if field_sym.info is ast.ArrayFixed { + // Check if this fixed array is large (> 64KB) + // Conservative estimate: 8 bytes per element + size := i64(field_sym.info.size) * 8 + if size > 65536 { + return true + } + } + } + } + return false +} diff --git a/vlib/v/tests/structs/struct_heap_large_fixed_array_test.v b/vlib/v/tests/structs/struct_heap_large_fixed_array_test.v new file mode 100644 index 000000000..44a43e8f8 --- /dev/null +++ b/vlib/v/tests/structs/struct_heap_large_fixed_array_test.v @@ -0,0 +1,71 @@ +// Test @[heap] structs with large fixed arrays. + +@[heap] +struct LargeData { + array [1024 * 1024]int +} + +@[heap] +struct LargeDataWithFields { + array [1024 * 1024]int + value int = 42 + name string +} + +fn get_large_ref() &LargeData { + d := LargeData{} + return &d +} + +fn get_large_with_fields_ref(name string) &LargeDataWithFields { + d := LargeDataWithFields{ + name: name + } + return &d +} + +fn test_heap_large_fixed_array_basic() { + // Basic test: create a heap struct with large fixed array + d := LargeData{} + assert d.array[0] == 0 + assert d.array[1024 * 1024 - 1] == 0 +} + +fn test_heap_large_fixed_array_reference() { + // Test returning reference to heap struct with large array + ptr := get_large_ref() + assert ptr.array[0] == 0 + assert ptr.array[500000] == 0 +} + +fn test_heap_large_fixed_array_with_fields() { + // Test struct with large array and other fields + d := LargeDataWithFields{ + name: 'test' + } + assert d.array[0] == 0 + assert d.value == 42 // default value + assert d.name == 'test' // explicit value +} + +fn test_heap_large_fixed_array_with_fields_ref() { + // Test reference to struct with large array and fields + ptr := get_large_with_fields_ref('hello') + assert ptr.array[0] == 0 + assert ptr.value == 42 + assert ptr.name == 'hello' +} + +fn test_heap_large_fixed_array_multiple() { + // Test creating multiple large heap structs + a := LargeData{} + b := LargeData{} + c := LargeDataWithFields{ + value: 100 + name: 'multi' + } + assert a.array[0] == 0 + assert b.array[0] == 0 + assert c.value == 100 + assert c.name == 'multi' +} -- 2.39.5