1635 lines
63 KiB
Odin
1635 lines
63 KiB
Odin
package lower
|
|
|
|
import "../hir"
|
|
import "../ir"
|
|
import "../source"
|
|
import "../symbol"
|
|
import "../target"
|
|
import "../types"
|
|
import "core:fmt"
|
|
import "core:mem"
|
|
|
|
State :: struct {
|
|
hir_module: ^hir.Module,
|
|
instructions: [dynamic]ir.Instruction,
|
|
local_values: []ir.Instruction_Id,
|
|
local_slots: []ir.Instruction_Id,
|
|
func_locals: []hir.Local,
|
|
func_result: types.Type,
|
|
expr_stack: [dynamic]Lower_Expr_Frame,
|
|
// Innermost-last stack of enclosing loop targets for `break`/`continue`.
|
|
loops: [dynamic]Loop_Ctx,
|
|
next_label: i64,
|
|
allocator: mem.Allocator,
|
|
}
|
|
|
|
// An enclosing exit target. `break` branches to `exit_lbl`; `continue` branches to
|
|
// `continue_lbl` (the loop's update/latch, which runs the update clause then re-tests
|
|
// the condition). A labeled `break`/`continue`/`yield` matches `label`; a value block is
|
|
// `is_loop = false` (it has no `continue` and is skipped by plain `break`/`continue`).
|
|
Loop_Ctx :: struct {
|
|
label: symbol.Id,
|
|
exit_lbl: i64,
|
|
continue_lbl: i64,
|
|
is_loop: bool,
|
|
}
|
|
|
|
fresh_label :: proc(state: ^State) -> i64 {
|
|
id := state.next_label
|
|
state.next_label += 1
|
|
return id
|
|
}
|
|
|
|
append_instruction :: proc(state: ^State, instruction: ir.Instruction) -> ir.Instruction_Id {
|
|
id := ir.instruction_id(len(state.instructions))
|
|
append(&state.instructions, instruction)
|
|
return id
|
|
}
|
|
|
|
clone_args :: proc(values: []ir.Instruction_Id, allocator: mem.Allocator) -> []ir.Instruction_Id {
|
|
result := make([]ir.Instruction_Id, len(values), allocator)
|
|
copy(result, values)
|
|
return result
|
|
}
|
|
|
|
sentinel :: proc(value_type: types.Type, store: ^types.Store, selected := target.DEFAULT) -> i64 {
|
|
repr := types.runtime_representation(value_type, store)
|
|
if types.is_float(repr, selected) {
|
|
return i64(0x7fc0_0000) if types.bits(repr, selected) == 32 else transmute(i64)u64(0x7ff8_0000_0000_0000)
|
|
}
|
|
switch types.bits(repr, selected) {
|
|
case 8: return -86
|
|
case 16: return -21846
|
|
case 32: return -1431655766
|
|
case: return -6148914691236517206
|
|
}
|
|
}
|
|
|
|
append_recovery_value :: proc(
|
|
state: ^State,
|
|
span: source.Span,
|
|
value_type: types.Type,
|
|
diagnostic := source.INVALID_DIAGNOSTIC,
|
|
) -> ir.Instruction_Id {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Trap,
|
|
span=span,
|
|
type=types.VOID,
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=diagnostic,
|
|
})
|
|
fallback := value_type
|
|
if !types.is_valid(fallback) {
|
|
fallback = types.I64
|
|
}
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Const,
|
|
span=span,
|
|
type=fallback,
|
|
integer=sentinel(fallback, &state.hir_module.types, state.hir_module.target),
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
|
|
Lower_Expr_Frame :: struct {
|
|
expr: hir.Expr_Id,
|
|
stage: u8,
|
|
left: ir.Instruction_Id,
|
|
arg_index: int,
|
|
args: []ir.Instruction_Id,
|
|
}
|
|
|
|
lower_nested_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|
outer := state.expr_stack
|
|
state.expr_stack = nil
|
|
state.expr_stack.allocator = state.allocator
|
|
result := lower_expr(state, expr_id)
|
|
delete(state.expr_stack)
|
|
state.expr_stack = outer
|
|
return result
|
|
}
|
|
|
|
lower_location :: proc(state: ^State, expr_id: hir.Expr_Id, for_write := false) -> ir.Instruction_Id {
|
|
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(state.hir_module.exprs) {
|
|
return ir.INVALID_INSTRUCTION
|
|
}
|
|
expr := state.hir_module.exprs[expr_id]
|
|
#partial switch expr.kind {
|
|
case .Local:
|
|
local := hir.as_local(expr.target)
|
|
if local != hir.INVALID_LOCAL && int(local) < len(state.local_slots) {
|
|
return state.local_slots[local]
|
|
}
|
|
case .Global:
|
|
global := hir.as_global(expr.target)
|
|
if global != hir.INVALID_GLOBAL && int(global) < len(state.hir_module.globals) {
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Address_Global, span=expr.span, type=expr.type,
|
|
target=ir.global_ref(ir.Global_Id(global)),
|
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
case .Deref:
|
|
return lower_nested_expr(state, expr.left)
|
|
case .Index:
|
|
container_type := state.hir_module.exprs[expr.left].type
|
|
container := ir.INVALID_INSTRUCTION
|
|
if types.is_array(container_type, &state.hir_module.types) {
|
|
container = lower_location(state, expr.left, for_write)
|
|
} else {
|
|
container = lower_nested_expr(state, expr.left)
|
|
}
|
|
index := lower_nested_expr(state, expr.right)
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Index_Address, span=expr.span, type=expr.type,
|
|
integer=0 if for_write else 1,
|
|
target=ir.INVALID_REF, a=container, b=index,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Field:
|
|
base_type := state.hir_module.exprs[expr.left].type
|
|
base := ir.INVALID_INSTRUCTION
|
|
if !types.is_pointer(base_type, &state.hir_module.types) {
|
|
base = lower_location(state, expr.left, for_write)
|
|
} else {
|
|
base = lower_nested_expr(state, expr.left)
|
|
}
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Field_Address, span=expr.span, type=expr.type, integer=expr.integer,
|
|
target=ir.INVALID_REF, a=base, b=ir.INVALID_INSTRUCTION,
|
|
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
|
|
}
|
|
|
|
hir_expr_is_location :: proc(state: ^State, expr_id: hir.Expr_Id) -> bool {
|
|
if expr_id == hir.INVALID_EXPR || int(expr_id) >= len(state.hir_module.exprs) {
|
|
return false
|
|
}
|
|
#partial switch state.hir_module.exprs[expr_id].kind {
|
|
case .Local, .Global, .Deref, .Index, .Field:
|
|
return true
|
|
case:
|
|
return false
|
|
}
|
|
}
|
|
|
|
lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|
expr := state.hir_module.exprs[expr_id]
|
|
#partial switch expr.kind {
|
|
case .String:
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.String, span=expr.span, type=expr.type, integer=expr.integer,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Array, .Struct:
|
|
args := make([]ir.Instruction_Id, len(expr.args), state.allocator)
|
|
for arg, index in expr.args {
|
|
// A void union variant (`T{ variant }`) has no payload operand; leave it
|
|
// invalid so codegen emits only the tag, not a trapping recovery value.
|
|
args[index] = lower_nested_expr(state, arg) if arg != hir.INVALID_EXPR else ir.INVALID_INSTRUCTION
|
|
}
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Aggregate, span=expr.span, type=expr.type, args=args, integer=expr.integer,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Range:
|
|
args := make([]ir.Instruction_Id, 3, state.allocator)
|
|
args[0] = lower_nested_expr(state, expr.args[0])
|
|
args[1] = lower_nested_expr(state, expr.args[1])
|
|
args[2] = append_instruction(state, ir.Instruction{
|
|
op=.Const,
|
|
span=expr.span,
|
|
type=types.BOOL,
|
|
integer=expr.integer,
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Aggregate,
|
|
span=expr.span,
|
|
type=expr.type,
|
|
args=args,
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .None:
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.None, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Optional_Some:
|
|
value := lower_nested_expr(state, expr.left)
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Optional_Some, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Address:
|
|
location := lower_location(state, expr.left, types.is_mutable(expr.type, &state.hir_module.types))
|
|
if location != ir.INVALID_INSTRUCTION {
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Address_Of, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=location, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
case .Deref, .Index, .Field:
|
|
location := lower_location(state, expr_id)
|
|
if location == ir.INVALID_INSTRUCTION {
|
|
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
}
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Load, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=location, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Union_Tag:
|
|
// Read a tagged union's discriminant: the tag sits at offset 0, so the union's
|
|
// address is the tag's address — load the tag enum (`expr.type`) directly.
|
|
address := lower_location(state, expr.left)
|
|
if address == ir.INVALID_INSTRUCTION {
|
|
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
}
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Union_Tag, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=address, b=ir.INVALID_INSTRUCTION,
|
|
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) {
|
|
container = lower_location(state, expr.left)
|
|
} else {
|
|
container = lower_nested_expr(state, expr.left)
|
|
}
|
|
args := make([]ir.Instruction_Id, len(expr.args), state.allocator)
|
|
for arg, index in expr.args {
|
|
args[index] = ir.INVALID_INSTRUCTION
|
|
if arg != hir.INVALID_EXPR {
|
|
args[index] = lower_nested_expr(state, arg)
|
|
}
|
|
}
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Slice, span=expr.span, type=expr.type, args=args,
|
|
target=ir.INVALID_REF, a=container, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Unwrap:
|
|
value := lower_nested_expr(state, expr.left)
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Unwrap, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Length:
|
|
container_type := state.hir_module.exprs[expr.left].type
|
|
item, ok := types.node(&state.hir_module.types, container_type)
|
|
if ok && item.kind == .Array {
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Const, span=expr.span, type=types.USIZE, integer=i64(item.count),
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
_, array, array_ok := types.array_pointer(container_type, &state.hir_module.types)
|
|
if array_ok {
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Const, span=expr.span, type=types.USIZE, integer=i64(array.count),
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
value := lower_nested_expr(state, expr.left)
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Length, span=expr.span, type=types.USIZE,
|
|
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Slice_Ptr:
|
|
container_type := state.hir_module.exprs[expr.left].type
|
|
value := lower_nested_expr(state, expr.left)
|
|
if types.is_array(container_type, &state.hir_module.types) {
|
|
value = lower_location(state, expr.left)
|
|
}
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Slice_Ptr, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Orelse:
|
|
value := lower_nested_expr(state, expr.left)
|
|
begin := append_instruction(state, ir.Instruction{
|
|
op=.Orelse_Begin, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
fallback := lower_nested_expr(state, expr.right)
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Orelse, span=expr.span, type=expr.type,
|
|
target=ir.INVALID_REF, a=begin, b=fallback,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Try, .Catch:
|
|
channel := lower_nested_expr(state, expr.left)
|
|
channel_type := state.hir_module.exprs[expr.left].type
|
|
success := types.fallible_success(channel_type, &state.hir_module.types)
|
|
channel_slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca, span=expr.span, type=channel_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=channel_type,
|
|
target=ir.INVALID_REF, a=channel_slot, b=channel, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
code := append_instruction(state, ir.Instruction{
|
|
op=.Union_Tag, span=expr.span, type=types.U16,
|
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
zero := append_instruction(state, ir.Instruction{
|
|
op=.Const, span=expr.span, type=types.U16, integer=0,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
ok := append_instruction(state, ir.Instruction{
|
|
op=.Compare, span=expr.span, type=types.BOOL, integer=i64(ir.Compare_Predicate.Eq),
|
|
target=ir.INVALID_REF, a=code, b=zero, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
success_lbl := fresh_label(state)
|
|
error_lbl := fresh_label(state)
|
|
merge_lbl := fresh_label(state)
|
|
slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca, span=expr.span, type=success,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br, span=expr.span, type=types.VOID,
|
|
integer=success_lbl, target=ir.Ref(u32(error_lbl)), a=ok,
|
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=expr.span, type=types.VOID, integer=error_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
if expr.kind == .Try {
|
|
result := channel
|
|
if !types.equal(channel_type, state.func_result) {
|
|
error_type := types.fallible_error(channel_type, &state.hir_module.types)
|
|
enclosing_error := types.fallible_error(state.func_result, &state.hir_module.types)
|
|
error_value := append_instruction(state, ir.Instruction{
|
|
op=.Fallible_Error, span=expr.span, type=error_type,
|
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
if !types.equal(error_type, enclosing_error) {
|
|
error_value = append_instruction(state, ir.Instruction{
|
|
op=.Sum_Widen, span=expr.span, type=enclosing_error,
|
|
target=ir.INVALID_REF, a=error_value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
args := make([]ir.Instruction_Id, 1, state.allocator)
|
|
args[0] = error_value
|
|
result = append_instruction(state, ir.Instruction{
|
|
op=.Aggregate, span=expr.span, type=state.func_result, integer=1,
|
|
args=args, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Return, span=expr.span, type=state.func_result,
|
|
target=ir.INVALID_REF, a=result, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
if expr.integer != 0 {
|
|
capture := hir.as_local(expr.target)
|
|
if capture != hir.INVALID_LOCAL && int(capture) < len(state.func_locals) {
|
|
error_type := state.func_locals[capture].type
|
|
error_value := append_instruction(state, ir.Instruction{
|
|
op=.Fallible_Error, span=expr.span, type=error_type,
|
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
capture_slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca, span=expr.span, type=error_type,
|
|
target=ir.local_ref(ir.Local_Id(capture)),
|
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
state.local_slots[capture] = capture_slot
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=expr.span, type=error_type,
|
|
target=ir.INVALID_REF, a=capture_slot, b=error_value,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
lower_statements(state, expr.body)
|
|
}
|
|
fallback := lower_nested_expr(state, expr.right)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=expr.span, type=success,
|
|
target=ir.INVALID_REF, a=slot, b=fallback, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=expr.span, type=types.VOID, integer=success_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
if !types.is_void(success) {
|
|
payload := append_instruction(state, ir.Instruction{
|
|
op=.Field_Address, span=expr.span, type=success, integer=0,
|
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
loaded := append_instruction(state, ir.Instruction{
|
|
op=.Load, span=expr.span, type=success,
|
|
target=ir.INVALID_REF, a=payload, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=expr.span, type=success,
|
|
target=ir.INVALID_REF, a=slot, b=loaded, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=expr.span, type=types.VOID, integer=merge_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=expr.span, type=types.VOID, integer=merge_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Load, span=expr.span, type=success,
|
|
target=ir.INVALID_REF, a=slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Not:
|
|
value := lower_nested_expr(state, expr.left)
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Not, span=expr.span, type=types.BOOL,
|
|
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
|
left := lower_nested_expr(state, expr.left)
|
|
right := lower_nested_expr(state, expr.right)
|
|
predicate := ir.Compare_Predicate.Eq
|
|
#partial switch expr.kind {
|
|
case .Eq: predicate = .Eq
|
|
case .Ne: predicate = .Ne
|
|
case .Lt: predicate = .Lt
|
|
case .Le: predicate = .Le
|
|
case .Gt: predicate = .Gt
|
|
case .Ge: predicate = .Ge
|
|
}
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Compare, span=expr.span, type=types.BOOL, integer=i64(predicate),
|
|
target=ir.INVALID_REF, a=left, b=right,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .And, .Or:
|
|
// Short-circuit via a bool slot: store the left operand, branch on it, and
|
|
// only evaluate/store the right operand when needed. Avoids phi nodes.
|
|
slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca, span=expr.span, type=types.BOOL,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
left := lower_nested_expr(state, expr.left)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=expr.span, type=types.BOOL,
|
|
target=ir.INVALID_REF, a=slot, b=left, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
rhs_lbl := fresh_label(state)
|
|
done_lbl := fresh_label(state)
|
|
true_target := rhs_lbl if expr.kind == .And else done_lbl
|
|
false_target := done_lbl if expr.kind == .And else rhs_lbl
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br, span=expr.span, type=types.VOID,
|
|
a=left, integer=true_target, target=ir.Ref(u32(false_target)),
|
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=expr.span, type=types.VOID, integer=rhs_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
right := lower_nested_expr(state, expr.right)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=expr.span, type=types.BOOL,
|
|
target=ir.INVALID_REF, a=slot, b=right, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=expr.span, type=types.VOID, integer=done_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=expr.span, type=types.VOID, integer=done_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return append_instruction(state, ir.Instruction{
|
|
op=.Load, span=expr.span, type=types.BOOL,
|
|
target=ir.INVALID_REF, a=slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
}
|
|
|
|
lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|
stack := state.expr_stack
|
|
clear_dynamic_array(&stack)
|
|
defer {
|
|
for frame in stack {
|
|
delete(frame.args, state.allocator)
|
|
}
|
|
clear_dynamic_array(&stack)
|
|
state.expr_stack = stack
|
|
}
|
|
append(&stack, Lower_Expr_Frame{expr=expr_id})
|
|
last := ir.INVALID_INSTRUCTION
|
|
for len(stack) > 0 {
|
|
frame_index := len(stack)-1
|
|
frame := stack[frame_index]
|
|
if frame.expr == hir.INVALID_EXPR || int(frame.expr) >= len(state.hir_module.exprs) {
|
|
last = append_recovery_value(state, source.Span{}, types.I64)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
expr := state.hir_module.exprs[frame.expr]
|
|
if frame.stage == 0 {
|
|
#partial switch expr.kind {
|
|
case .Invalid:
|
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
_ = pop(&stack)
|
|
case .Integer, .Float, .Bool:
|
|
last = append_instruction(state, ir.Instruction{
|
|
op=.Const, span=expr.span, type=expr.type, integer=expr.integer,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
_ = pop(&stack)
|
|
case .String, .Array, .Struct, .Range, .None, .Optional_Some, .Address, .Deref,
|
|
.Index, .Slice, .Field, .Union_Tag, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
|
.Try, .Catch, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
|
last = lower_compound_expr(state, frame.expr)
|
|
_ = pop(&stack)
|
|
case .Local:
|
|
last = ir.INVALID_INSTRUCTION
|
|
local := hir.as_local(expr.target)
|
|
if local != hir.INVALID_LOCAL && int(local) < len(state.local_slots) && state.local_slots[local] != ir.INVALID_INSTRUCTION {
|
|
last = append_instruction(state, ir.Instruction{
|
|
op=.Load, span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
|
a=state.local_slots[local], b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else if local != hir.INVALID_LOCAL && int(local) < len(state.local_values) &&
|
|
state.local_values[local] != ir.INVALID_INSTRUCTION {
|
|
last = state.local_values[local]
|
|
}
|
|
if last == ir.INVALID_INSTRUCTION {
|
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
}
|
|
_ = pop(&stack)
|
|
case .Global:
|
|
global := hir.as_global(expr.target)
|
|
if global == hir.INVALID_GLOBAL || int(global) >= len(state.hir_module.globals) {
|
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
} else {
|
|
last = append_instruction(state, ir.Instruction{
|
|
op=.Load_Global, span=expr.span, type=expr.type, target=ir.global_ref(ir.Global_Id(global)),
|
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
_ = pop(&stack)
|
|
case .Function:
|
|
function := hir.as_function(expr.target)
|
|
if function == hir.INVALID_FUNCTION || int(function) >= len(state.hir_module.functions) {
|
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
} else {
|
|
last = append_instruction(state, ir.Instruction{
|
|
op=.Function_Address, span=expr.span, type=expr.type,
|
|
target=ir.function_ref(ir.Function_Id(function)),
|
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
_ = pop(&stack)
|
|
case .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
|
|
stack[frame_index].stage = 1
|
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
|
case .Negate:
|
|
stack[frame_index].stage = 5
|
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
|
case .Add, .Sub, .Mul, .Div, .Pointer_Add:
|
|
stack[frame_index].stage = 2
|
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
|
case .Call:
|
|
function := hir.as_function(expr.target)
|
|
if function == hir.INVALID_FUNCTION {
|
|
if expr.left == hir.INVALID_EXPR {
|
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].stage = 6
|
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
|
continue
|
|
}
|
|
if int(function) >= len(state.hir_module.functions) {
|
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
stack[frame_index].args = make([]ir.Instruction_Id, len(expr.args), state.allocator)
|
|
stack[frame_index].stage = 4
|
|
if len(expr.args) > 0 {
|
|
append(&stack, Lower_Expr_Frame{expr=expr.args[0]})
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if frame.stage == 6 {
|
|
stack[frame_index].left = last
|
|
stack[frame_index].args = make([]ir.Instruction_Id, len(expr.args), state.allocator)
|
|
stack[frame_index].stage = 4
|
|
if len(expr.args) > 0 {
|
|
append(&stack, Lower_Expr_Frame{expr=expr.args[0]})
|
|
}
|
|
continue
|
|
}
|
|
if frame.stage == 5 {
|
|
last = append_instruction(state, ir.Instruction{
|
|
op=.Neg_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
|
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if frame.stage == 1 {
|
|
op := ir.Opcode.Widen
|
|
#partial switch expr.kind {
|
|
case .Sum_Widen: op = .Sum_Widen
|
|
case .Weaken_Pointer: op = .Weaken_Pointer
|
|
case .Weaken_Slice: op = .Weaken_Slice
|
|
case .Decay_Array_Pointer: op = .Decay_Array_Pointer
|
|
case .C_Coerce: op = .C_Coerce
|
|
case .C_Vararg_Promote: op = .C_Vararg_Promote
|
|
case .Retype: op = .Retype
|
|
case: op = .Widen
|
|
}
|
|
last = append_instruction(state, ir.Instruction{
|
|
op=op,
|
|
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
|
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if frame.stage == 2 {
|
|
stack[frame_index].left = last
|
|
stack[frame_index].stage = 3
|
|
append(&stack, Lower_Expr_Frame{expr=expr.right})
|
|
continue
|
|
}
|
|
if frame.stage == 3 {
|
|
op := ir.Opcode.Add_Checked
|
|
#partial switch expr.kind {
|
|
case .Sub: op = .Sub_Checked
|
|
case .Mul: op = .Mul_Checked
|
|
case .Div: op = .Div_Checked
|
|
case .Pointer_Add: op = .Pointer_Add
|
|
}
|
|
last = append_instruction(state, ir.Instruction{
|
|
op=op,
|
|
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
|
a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
_ = pop(&stack)
|
|
continue
|
|
}
|
|
if frame.stage == 4 {
|
|
if frame.arg_index < len(expr.args) {
|
|
stack[frame_index].args[frame.arg_index] = last
|
|
stack[frame_index].arg_index += 1
|
|
if frame.arg_index+1 < len(expr.args) {
|
|
append(&stack, Lower_Expr_Frame{expr=expr.args[frame.arg_index+1]})
|
|
continue
|
|
}
|
|
}
|
|
function := hir.as_function(expr.target)
|
|
callee := frame.left
|
|
if function != hir.INVALID_FUNCTION {
|
|
callee = ir.INVALID_INSTRUCTION
|
|
}
|
|
last = append_instruction(state, ir.Instruction{
|
|
op=.Call, span=expr.span, type=expr.type, target=ir.function_ref(ir.Function_Id(function)),
|
|
a=callee, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
stack[frame_index].args = nil
|
|
_ = pop(&stack)
|
|
}
|
|
}
|
|
return last
|
|
}
|
|
|
|
lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|
hir_module := state.hir_module
|
|
for statement_id in statements {
|
|
statement := hir_module.statements[statement_id]
|
|
switch statement.kind {
|
|
case .Declaration:
|
|
if statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(state.func_locals) {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Trap, span=statement.span, type=types.VOID,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
|
})
|
|
continue
|
|
}
|
|
local := state.func_locals[statement.local]
|
|
slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca,
|
|
span=statement.span,
|
|
type=local.type,
|
|
target=ir.local_ref(ir.Local_Id(statement.local)),
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
state.local_slots[statement.local] = slot
|
|
if statement.expr == hir.INVALID_EXPR {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Fill,
|
|
span=statement.span,
|
|
type=local.type,
|
|
target=ir.INVALID_REF,
|
|
a=slot,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
continue
|
|
}
|
|
value := lower_expr(state, statement.expr)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store,
|
|
span=statement.span,
|
|
type=local.type,
|
|
target=ir.INVALID_REF,
|
|
a=slot,
|
|
b=value,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Assignment:
|
|
if statement.assignment_op != .Set && statement.target != hir.INVALID_EXPR {
|
|
address := lower_location(state, statement.target, true)
|
|
target_type := types.INVALID
|
|
if int(statement.target) < len(hir_module.exprs) {
|
|
target_type = hir_module.exprs[statement.target].type
|
|
}
|
|
if address == ir.INVALID_INSTRUCTION || !types.is_valid(target_type) {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Trap, span=statement.span, type=types.VOID,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
|
})
|
|
continue
|
|
}
|
|
current := append_instruction(state, ir.Instruction{
|
|
op=.Load, span=statement.span, type=target_type,
|
|
target=ir.INVALID_REF, a=address, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
rhs := lower_expr(state, statement.expr)
|
|
op := ir.Opcode.Add_Checked
|
|
#partial switch statement.assignment_op {
|
|
case .Sub: op = .Sub_Checked
|
|
case .Mul: op = .Mul_Checked
|
|
case .Div: op = .Div_Checked
|
|
case .Pointer_Add: op = .Pointer_Add
|
|
}
|
|
value := append_instruction(state, ir.Instruction{
|
|
op=op, span=statement.span, type=target_type,
|
|
target=ir.INVALID_REF, a=current, b=rhs,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=statement.span, type=target_type,
|
|
target=ir.INVALID_REF, a=address, b=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
continue
|
|
}
|
|
value := lower_expr(state, statement.expr)
|
|
slot := ir.INVALID_INSTRUCTION
|
|
value_type := types.INVALID
|
|
if statement.target != hir.INVALID_EXPR {
|
|
slot = lower_location(state, statement.target, true)
|
|
if int(statement.target) < len(hir_module.exprs) {
|
|
value_type = hir_module.exprs[statement.target].type
|
|
}
|
|
} else if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) {
|
|
slot = state.local_slots[statement.local]
|
|
value_type = state.func_locals[statement.local].type
|
|
}
|
|
if slot == ir.INVALID_INSTRUCTION || !types.is_valid(value_type) {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Trap, span=statement.span, type=types.VOID,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
|
})
|
|
continue
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store,
|
|
span=statement.span,
|
|
type=value_type,
|
|
target=ir.INVALID_REF,
|
|
a=slot,
|
|
b=value,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Return:
|
|
if statement.expr == hir.INVALID_EXPR {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Return_Void,
|
|
span=statement.span,
|
|
type=types.VOID,
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
value := lower_expr(state, statement.expr)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Return,
|
|
span=statement.span,
|
|
type=state.func_result,
|
|
target=ir.INVALID_REF,
|
|
a=value,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
case .Break, .Continue:
|
|
// Find the target: a labeled `break`/`continue` matches the innermost target
|
|
// (loop or value block) with that label; an unlabeled one takes the innermost
|
|
// loop (`continue` and unlabeled targets skip non-loop block targets). The
|
|
// checker guarantees a match exists; guard defensively regardless.
|
|
target_index := -1
|
|
for i := len(state.loops) - 1; i >= 0; i -= 1 {
|
|
ctx := state.loops[i]
|
|
if symbol.is_valid(statement.label) {
|
|
if ctx.label == statement.label && (ctx.is_loop || statement.kind == .Break) {
|
|
target_index = i
|
|
break
|
|
}
|
|
} else if ctx.is_loop {
|
|
target_index = i
|
|
break
|
|
}
|
|
}
|
|
if target_index >= 0 {
|
|
target := state.loops[target_index]
|
|
label := target.exit_lbl if statement.kind == .Break else target.continue_lbl
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=label,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
case .Block:
|
|
// A labeled value block: lower its body, then emit the exit label that its
|
|
// `yield :blk` (a labeled break) branches to. Not a loop, so plain
|
|
// `break`/`continue` skip it (is_loop=false).
|
|
exit_lbl := fresh_label(state)
|
|
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=exit_lbl, is_loop=false})
|
|
lower_statements(state, statement.then_body)
|
|
pop(&state.loops)
|
|
// Explicit fall-through to the exit label so the preceding block is terminated
|
|
// (dead code after a terminator gets a fresh recovery block in the emitter),
|
|
// mirroring the `br` a `while`/`for` emits before its labels.
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=exit_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=exit_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .Expression, .Sink:
|
|
_ = lower_expr(state, statement.expr)
|
|
case .Trap:
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Trap,
|
|
span=statement.span,
|
|
type=types.VOID,
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=statement.diagnostic,
|
|
})
|
|
case .If:
|
|
has_else := statement.else_body != nil
|
|
then_lbl := fresh_label(state)
|
|
else_lbl := fresh_label(state) if has_else else then_lbl
|
|
merge_lbl := fresh_label(state)
|
|
false_target := else_lbl if has_else else merge_lbl
|
|
|
|
if len(statement.unwraps) > 0 {
|
|
// Evaluate each optional exactly once, entering the next operand only
|
|
// after the previous one is present. Capture storage is initialized in
|
|
// these success blocks so the optional guard can use every binding.
|
|
for unwrap in statement.unwraps {
|
|
optional := lower_expr(state, unwrap.expr)
|
|
present := append_instruction(state, ir.Instruction{
|
|
op=.Optional_Is_Some,
|
|
span=statement.span,
|
|
type=types.BOOL,
|
|
target=ir.INVALID_REF,
|
|
a=optional,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
success_lbl := fresh_label(state)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br,
|
|
span=statement.span,
|
|
type=types.VOID,
|
|
a=present,
|
|
integer=success_lbl,
|
|
target=ir.Ref(u32(false_target)),
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label,
|
|
span=statement.span,
|
|
type=types.VOID,
|
|
integer=success_lbl,
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
if unwrap.local != hir.INVALID_LOCAL && int(unwrap.local) < len(state.func_locals) {
|
|
local := state.func_locals[unwrap.local]
|
|
slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca,
|
|
span=statement.span,
|
|
type=local.type,
|
|
target=ir.local_ref(ir.Local_Id(unwrap.local)),
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
state.local_slots[unwrap.local] = slot
|
|
inner := append_instruction(state, ir.Instruction{
|
|
op=.Optional_Value,
|
|
span=statement.span,
|
|
type=local.type,
|
|
target=ir.INVALID_REF,
|
|
a=optional,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store,
|
|
span=statement.span,
|
|
type=local.type,
|
|
target=ir.INVALID_REF,
|
|
a=slot,
|
|
b=inner,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
}
|
|
if statement.guard != hir.INVALID_EXPR {
|
|
guard := lower_expr(state, statement.guard)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br,
|
|
span=statement.span,
|
|
type=types.VOID,
|
|
a=guard,
|
|
integer=then_lbl,
|
|
target=ir.Ref(u32(false_target)),
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br,
|
|
span=statement.span,
|
|
type=types.VOID,
|
|
integer=then_lbl,
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
} else {
|
|
cond := lower_expr(state, statement.expr)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br, span=statement.span, type=types.VOID,
|
|
a=cond, integer=then_lbl, target=ir.Ref(u32(false_target)),
|
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=then_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
lower_statements(state, statement.then_body)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=merge_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
if has_else {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=else_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
lower_statements(state, statement.else_body)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=merge_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=merge_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .While:
|
|
condition_lbl := fresh_label(state)
|
|
body_lbl := fresh_label(state)
|
|
exit_lbl := fresh_label(state)
|
|
update_lbl := condition_lbl
|
|
if statement.update != hir.INVALID_STMT {
|
|
update_lbl = fresh_label(state)
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
condition := lower_expr(state, statement.expr)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br, span=statement.span, type=types.VOID,
|
|
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
|
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=update_lbl, is_loop=true})
|
|
lower_statements(state, statement.then_body)
|
|
pop(&state.loops)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=update_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
if statement.update != hir.INVALID_STMT {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=update_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
update := [1]hir.Stmt_Id{statement.update}
|
|
lower_statements(state, update[:])
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=exit_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
case .For:
|
|
iterable_type := hir_module.exprs[statement.expr].type
|
|
if types.is_range(iterable_type, &hir_module.types) {
|
|
child := types.child_type(iterable_type, &hir_module.types)
|
|
range_value := lower_expr(state, statement.expr)
|
|
start := append_instruction(state, ir.Instruction{
|
|
op=.Extract, span=statement.span, type=child, integer=0,
|
|
target=ir.INVALID_REF, a=range_value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
end := append_instruction(state, ir.Instruction{
|
|
op=.Extract, span=statement.span, type=child, integer=1,
|
|
target=ir.INVALID_REF, a=range_value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
inclusive := append_instruction(state, ir.Instruction{
|
|
op=.Extract, span=statement.span, type=types.BOOL, integer=2,
|
|
target=ir.INVALID_REF, a=range_value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
current_slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca, span=statement.span, type=child,
|
|
target=ir.local_ref(ir.Local_Id(statement.local)),
|
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
state.local_slots[statement.local] = current_slot
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=statement.span, type=child,
|
|
target=ir.INVALID_REF, a=current_slot, b=start,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
condition_lbl := fresh_label(state)
|
|
body_lbl := fresh_label(state)
|
|
update_lbl := fresh_label(state)
|
|
exit_lbl := fresh_label(state)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
current := append_instruction(state, ir.Instruction{
|
|
op=.Load, span=statement.span, type=child,
|
|
target=ir.INVALID_REF, a=current_slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
less := append_instruction(state, ir.Instruction{
|
|
op=.Compare, span=statement.span, type=types.BOOL,
|
|
integer=i64(ir.Compare_Predicate.Lt),
|
|
target=ir.INVALID_REF, a=current, b=end,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
equal := append_instruction(state, ir.Instruction{
|
|
op=.Compare, span=statement.span, type=types.BOOL,
|
|
integer=i64(ir.Compare_Predicate.Eq),
|
|
target=ir.INVALID_REF, a=current, b=end,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
false_value := append_instruction(state, ir.Instruction{
|
|
op=.Const, span=statement.span, type=types.BOOL, integer=0,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
true_value := append_instruction(state, ir.Instruction{
|
|
op=.Const, span=statement.span, type=types.BOOL, integer=1,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
inclusive_args := make([]ir.Instruction_Id, 2, state.allocator)
|
|
inclusive_args[0] = equal
|
|
inclusive_args[1] = false_value
|
|
inclusive_equal := append_instruction(state, ir.Instruction{
|
|
op=.Select, span=statement.span, type=types.BOOL,
|
|
target=ir.INVALID_REF, a=inclusive, b=ir.INVALID_INSTRUCTION,
|
|
args=inclusive_args, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
condition_args := make([]ir.Instruction_Id, 2, state.allocator)
|
|
condition_args[0] = true_value
|
|
condition_args[1] = inclusive_equal
|
|
condition := append_instruction(state, ir.Instruction{
|
|
op=.Select, span=statement.span, type=types.BOOL,
|
|
target=ir.INVALID_REF, a=less, b=ir.INVALID_INSTRUCTION,
|
|
args=condition_args, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br, span=statement.span, type=types.VOID,
|
|
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
|
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
// `continue` rejoins the normal end-of-iteration path (via a fresh
|
|
// label before the bounds/overflow guard) rather than jumping straight
|
|
// to the increment, so it behaves exactly like falling off the body —
|
|
// e.g. `for 0..=255 |b: u8| { ... continue }` exits cleanly instead of
|
|
// overflowing the increment on the final element.
|
|
continue_lbl := fresh_label(state)
|
|
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=continue_lbl, is_loop=true})
|
|
lower_statements(state, statement.then_body)
|
|
pop(&state.loops)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=continue_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=continue_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
after_body := append_instruction(state, ir.Instruction{
|
|
op=.Load, span=statement.span, type=child,
|
|
target=ir.INVALID_REF, a=current_slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
at_end := append_instruction(state, ir.Instruction{
|
|
op=.Compare, span=statement.span, type=types.BOOL,
|
|
integer=i64(ir.Compare_Predicate.Eq),
|
|
target=ir.INVALID_REF, a=after_body, b=end,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br, span=statement.span, type=types.VOID,
|
|
a=at_end, integer=exit_lbl, target=ir.Ref(u32(update_lbl)),
|
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=update_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
one := append_instruction(state, ir.Instruction{
|
|
op=.Const, span=statement.span, type=child, integer=1,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
next := append_instruction(state, ir.Instruction{
|
|
op=.Add_Checked, span=statement.span, type=child,
|
|
target=ir.INVALID_REF, a=after_body, b=one,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=statement.span, type=child,
|
|
target=ir.INVALID_REF, a=current_slot, b=next,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=exit_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
continue
|
|
}
|
|
|
|
item, item_ok := types.container(iterable_type, &hir_module.types)
|
|
if !item_ok {
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Trap, span=statement.span, type=types.VOID,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=statement.diagnostic,
|
|
})
|
|
continue
|
|
}
|
|
iterable_value := ir.INVALID_INSTRUCTION
|
|
if types.is_array(iterable_type, &hir_module.types) {
|
|
// 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)
|
|
}
|
|
base := append_instruction(state, ir.Instruction{
|
|
op=.Slice_Ptr, span=statement.span, type=statement.iterator_type,
|
|
target=ir.INVALID_REF, a=iterable_value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
length := ir.INVALID_INSTRUCTION
|
|
if item.kind == .Array {
|
|
length = append_instruction(state, ir.Instruction{
|
|
op=.Const, span=statement.span, type=types.USIZE, integer=i64(item.count),
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
} else {
|
|
length = append_instruction(state, ir.Instruction{
|
|
op=.Length, span=statement.span, type=types.USIZE,
|
|
target=ir.INVALID_REF, a=iterable_value, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
counter_target := ir.INVALID_REF
|
|
if statement.index_local != hir.INVALID_LOCAL {
|
|
counter_target = ir.local_ref(ir.Local_Id(statement.index_local))
|
|
}
|
|
counter_slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca, span=statement.span, type=types.USIZE,
|
|
target=counter_target, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
if statement.index_local != hir.INVALID_LOCAL {
|
|
state.local_slots[statement.index_local] = counter_slot
|
|
}
|
|
zero := append_instruction(state, ir.Instruction{
|
|
op=.Const, span=statement.span, type=types.USIZE, integer=0,
|
|
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=types.USIZE,
|
|
target=ir.INVALID_REF, a=counter_slot, b=zero,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
capture_type := state.func_locals[statement.local].type
|
|
capture_slot := append_instruction(state, ir.Instruction{
|
|
op=.Alloca, span=statement.span, type=capture_type,
|
|
target=ir.local_ref(ir.Local_Id(statement.local)),
|
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
state.local_slots[statement.local] = capture_slot
|
|
condition_lbl := fresh_label(state)
|
|
body_lbl := fresh_label(state)
|
|
update_lbl := fresh_label(state)
|
|
exit_lbl := fresh_label(state)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
index := append_instruction(state, ir.Instruction{
|
|
op=.Load, span=statement.span, type=types.USIZE,
|
|
target=ir.INVALID_REF, a=counter_slot, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
condition := append_instruction(state, ir.Instruction{
|
|
op=.Compare, span=statement.span, type=types.BOOL,
|
|
integer=i64(ir.Compare_Predicate.Lt),
|
|
target=ir.INVALID_REF, a=index, b=length,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Cond_Br, span=statement.span, type=types.VOID,
|
|
a=condition, integer=body_lbl, target=ir.Ref(u32(exit_lbl)),
|
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=body_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
pointer_type := statement.iterator_type
|
|
if statement.pointer_capture {
|
|
pointer_type = capture_type
|
|
}
|
|
element_pointer := append_instruction(state, ir.Instruction{
|
|
op=.Pointer_Add, span=statement.span, type=pointer_type,
|
|
target=ir.INVALID_REF, a=base, b=index,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
captured := element_pointer
|
|
if !statement.pointer_capture {
|
|
captured = append_instruction(state, ir.Instruction{
|
|
op=.Load, span=statement.span, type=item.child,
|
|
target=ir.INVALID_REF, a=element_pointer, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=statement.span, type=capture_type,
|
|
target=ir.INVALID_REF, a=capture_slot, b=captured,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append(&state.loops, Loop_Ctx{label=statement.label, exit_lbl=exit_lbl, continue_lbl=update_lbl, is_loop=true})
|
|
lower_statements(state, statement.then_body)
|
|
pop(&state.loops)
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=update_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=update_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
one := append_instruction(state, ir.Instruction{
|
|
op=.Const, span=statement.span, type=types.USIZE, integer=1,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
next := append_instruction(state, ir.Instruction{
|
|
op=.Add_Checked, span=statement.span, type=types.USIZE,
|
|
target=ir.INVALID_REF, a=index, b=one,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Store, span=statement.span, type=types.USIZE,
|
|
target=ir.INVALID_REF, a=counter_slot, b=next,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Br, span=statement.span, type=types.VOID, integer=condition_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(state, ir.Instruction{
|
|
op=.Label, span=statement.span, type=types.VOID, integer=exit_lbl,
|
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: mem.Allocator) -> []ir.Instruction {
|
|
state := State{
|
|
hir_module=hir_module,
|
|
allocator=allocator,
|
|
func_locals=function.locals,
|
|
func_result=function.result,
|
|
local_values=make([]ir.Instruction_Id, len(function.locals), allocator),
|
|
local_slots=make([]ir.Instruction_Id, len(function.locals), allocator),
|
|
}
|
|
state.instructions.allocator = allocator
|
|
state.expr_stack.allocator = allocator
|
|
state.loops.allocator = allocator
|
|
defer {
|
|
delete(state.local_values, allocator)
|
|
delete(state.local_slots, allocator)
|
|
delete(state.expr_stack)
|
|
delete(state.loops)
|
|
}
|
|
for _, index in state.local_values {
|
|
state.local_values[index] = ir.INVALID_INSTRUCTION
|
|
state.local_slots[index] = ir.INVALID_INSTRUCTION
|
|
}
|
|
for local_id in function.params {
|
|
param := append_instruction(&state, ir.Instruction{
|
|
op=.Param,
|
|
type=function.locals[local_id].type,
|
|
target=ir.local_ref(ir.Local_Id(local_id)),
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
state.local_values[local_id] = param
|
|
}
|
|
for local_id in function.params {
|
|
slot := append_instruction(&state, ir.Instruction{
|
|
op=.Alloca, type=function.locals[local_id].type,
|
|
target=ir.local_ref(ir.Local_Id(local_id)),
|
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(&state, ir.Instruction{
|
|
op=.Store, type=function.locals[local_id].type,
|
|
target=ir.INVALID_REF, a=slot, b=state.local_values[local_id],
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
state.local_slots[local_id] = slot
|
|
}
|
|
|
|
lower_statements(&state, function.body)
|
|
if len(state.instructions) == 0 ||
|
|
(state.instructions[len(state.instructions)-1].op != .Return &&
|
|
state.instructions[len(state.instructions)-1].op != .Return_Void) {
|
|
if types.is_void(function.result) {
|
|
append_instruction(&state, ir.Instruction{op=.Return_Void, type=types.VOID, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC})
|
|
} else {
|
|
value := append_instruction(&state, ir.Instruction{
|
|
op=.Const,
|
|
type=function.result,
|
|
integer=sentinel(function.result, &hir_module.types, hir_module.target),
|
|
target=ir.INVALID_REF,
|
|
a=ir.INVALID_INSTRUCTION,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
append_instruction(&state, ir.Instruction{op=.Return, type=function.result, target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC})
|
|
}
|
|
}
|
|
return state.instructions[:]
|
|
}
|
|
|
|
lower_global_initializer :: proc(hir_module: ^hir.Module, global: hir.Global, allocator: mem.Allocator) -> []ir.Instruction {
|
|
state := State{hir_module=hir_module, allocator=allocator}
|
|
state.instructions.allocator = allocator
|
|
state.expr_stack.allocator = allocator
|
|
defer delete(state.expr_stack)
|
|
value := lower_expr(&state, global.expr)
|
|
append_instruction(&state, ir.Instruction{
|
|
op=.Return,
|
|
type=global.type,
|
|
target=ir.INVALID_REF,
|
|
a=value,
|
|
b=ir.INVALID_INSTRUCTION,
|
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
})
|
|
return state.instructions[:]
|
|
}
|
|
|
|
lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Module {
|
|
module := ir.init_module(hir_module.target, allocator)
|
|
types.destroy_store(&module.types)
|
|
module.types = types.clone_store(&hir_module.types, allocator)
|
|
for value in hir_module.strings {
|
|
append(&module.strings, fmt.aprintf("%s", value, allocator=allocator))
|
|
}
|
|
for global in hir_module.globals {
|
|
_ = ir.global_id(len(module.globals))
|
|
append(&module.globals, ir.Global{
|
|
name=global.name,
|
|
link_name=fmt.aprintf("%s", global.link_name, allocator=allocator),
|
|
type=global.type,
|
|
is_static=global.is_static,
|
|
external=global.external,
|
|
writable=global.writable,
|
|
static_value=global.static_value,
|
|
initializer=nil if global.is_static || global.external else lower_global_initializer(hir_module, global, allocator),
|
|
problematic=global.problematic,
|
|
diagnostic=global.diagnostic,
|
|
})
|
|
}
|
|
for function in hir_module.functions {
|
|
param_types := make([]types.Type, len(function.params), allocator)
|
|
for local_id, index in function.params {
|
|
param_types[index] = function.locals[local_id].type
|
|
}
|
|
_ = ir.function_id(len(module.functions))
|
|
append(&module.functions, ir.Function{
|
|
link_name=fmt.aprintf("%s", function.link_name, allocator=allocator),
|
|
calling_convention=.C if function.calling_convention == .C else .Brolang,
|
|
implementation=.Declaration if function.implementation == .Declaration else .Definition,
|
|
linkage=.External if function.linkage == .External else .Internal,
|
|
is_main=function.is_main,
|
|
variadic=function.variadic,
|
|
param_types=param_types,
|
|
result=function.result,
|
|
instructions=nil if function.implementation == .Declaration else lower_body(hir_module, function, allocator),
|
|
problematic=function.problematic,
|
|
})
|
|
}
|
|
return module
|
|
}
|