address rvalue aggregates for field access
This commit is contained in:
+24
-30
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user