From f6fc25a8993fc76d6f79537440d6a7d9c09924a6 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Wed, 24 Jun 2026 21:13:30 +0200 Subject: [PATCH] address rvalue aggregates for field access --- compiler/lower/lower.odin | 54 +++++++++++++++++---------------------- compiler_tests.odin | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/compiler/lower/lower.odin b/compiler/lower/lower.odin index d153c3c..38912e5 100644 --- a/compiler/lower/lower.odin +++ b/compiler/lower/lower.odin @@ -152,6 +152,24 @@ lower_location :: proc(state: ^State, expr_id: hir.Expr_Id, for_write := false) diagnostic=source.INVALID_DIAGNOSTIC, }) } + if !hir_expr_is_location(state, expr_id) && + types.is_runtime_value(expr.type, &state.hir_module.types) { + // rvalue aggregate (e.g. a by-value struct/array return) — spill into a + // function-scoped temporary so its fields/elements are addressable. + // Lifetime matches a local: valid until the function returns. + value := lower_nested_expr(state, expr_id) + slot := append_instruction(state, ir.Instruction{ + op=.Alloca, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + append_instruction(state, ir.Instruction{ + op=.Store, span=expr.span, type=expr.type, + target=ir.INVALID_REF, a=slot, b=value, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + return slot + } return ir.INVALID_INSTRUCTION } @@ -244,24 +262,12 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi diagnostic=source.INVALID_DIAGNOSTIC, }) case .Slice: + // Array operands are addressed (locations) or spilled to a temporary + // (rvalues) by lower_location; other containers are slice/pointer values. left_type := state.hir_module.exprs[expr.left].type container: ir.Instruction_Id if types.is_array(left_type, &state.hir_module.types) { - if hir_expr_is_location(state, expr.left) { - container = lower_location(state, expr.left) - } else { - value := lower_nested_expr(state, expr.left) - container = append_instruction(state, ir.Instruction{ - op=.Alloca, span=expr.span, type=left_type, - target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, - diagnostic=source.INVALID_DIAGNOSTIC, - }) - append_instruction(state, ir.Instruction{ - op=.Store, span=expr.span, type=left_type, - target=ir.INVALID_REF, a=container, b=value, - diagnostic=source.INVALID_DIAGNOSTIC, - }) - } + container = lower_location(state, expr.left) } else { container = lower_nested_expr(state, expr.left) } @@ -1085,21 +1091,9 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) { } iterable_value := ir.INVALID_INSTRUCTION if types.is_array(iterable_type, &hir_module.types) { - if hir_expr_is_location(state, statement.expr) { - iterable_value = lower_location(state, statement.expr) - } else { - value := lower_expr(state, statement.expr) - iterable_value = append_instruction(state, ir.Instruction{ - op=.Alloca, span=statement.span, type=iterable_type, - target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, - diagnostic=source.INVALID_DIAGNOSTIC, - }) - append_instruction(state, ir.Instruction{ - op=.Store, span=statement.span, type=iterable_type, - target=ir.INVALID_REF, a=iterable_value, b=value, - diagnostic=source.INVALID_DIAGNOSTIC, - }) - } + // Address an array location, or spill an array rvalue to a + // temporary — both handled by lower_location. + iterable_value = lower_location(state, statement.expr) } else { iterable_value = lower_expr(state, statement.expr) } diff --git a/compiler_tests.odin b/compiler_tests.odin index 681726f..ec481db 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -1089,6 +1089,53 @@ main :: func() i32 { testing.expect(t, strings.contains(llvm_text, "getelementptr [4 x i32], ptr")) } +@(test) +field_and_index_access_on_an_rvalue_aggregate_materializes_it :: proc(t: ^testing.T) { + // A by-value struct return is a temporary with no address. Reading a field, + // slicing an array field, and taking its address must spill it into a + // temporary and address that, rather than addressing the aggregate value. + text := `Box :: struct { + score i32 + data [4]i32 +} +make_box :: func() Box { + return Box { score = 7, data = [1, 2, 3, 4] } +} +sink :: func(s []i32) i32 { + return s[0] +} +main :: func() i32 { + s :: make_box().score + v :: sink(make_box().data[0..]) + p :: &make_box().data + return s + v + p^[1] +} +` + source_file := source.Source{path="test.bro", text=text} + diagnostics := source.init_diagnostics(&source_file) + defer source.destroy_diagnostics(&diagnostics) + symbols := symbol.init_table() + defer symbol.destroy_table(&symbols) + stream := lexer.lex(&source_file, &diagnostics, &symbols) + defer delete(stream.items) + ast_module := parser.parse(&stream, &source_file, &diagnostics) + defer ast.destroy_module(&ast_module) + hir_module := checker.check(&ast_module, &diagnostics, &symbols) + defer hir.destroy_module(&hir_module) + ir_module := lower.lower(&hir_module) + defer ir.destroy_module(&ir_module) + llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols) + defer delete(llvm_text) + + testing.expect_value(t, len(diagnostics.items), 0) + // The rvalue Box is spilled into a stack temporary (structs render as + // %bro.type.N), then stored, then its fields are addressed off a `ptr`. + // A regression addresses the aggregate value directly, which llc rejects. + testing.expect(t, strings.contains(llvm_text, "alloca %bro.type.")) + testing.expect(t, strings.contains(llvm_text, "store %bro.type.")) + testing.expect(t, strings.contains(llvm_text, "getelementptr [4 x i32], ptr")) +} + @(test) c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) { text := `variadic :: c_func(tag c_int, ...) c_int