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, .Scalar_Cast, .Pointer_Cast, .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 .Scalar_Cast: op = .Scalar_Cast case .Pointer_Cast: op = .Pointer_Cast 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 }