package lower import "../hir" import "../ir" import "../source" 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, expr_stack: [dynamic]Lower_Expr_Frame, allocator: mem.Allocator, } 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) -> i64 { switch value_type.bits { 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_concrete_integer(fallback) { fallback = types.I64 } return append_instruction(state, ir.Instruction{ op=.Const, span=span, type=fallback, integer=sentinel(fallback), 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_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 { switch expr.kind { case .Invalid: last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic) _ = pop(&stack) case .Integer: 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 .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 .Widen: stack[frame_index].stage = 1 append(&stack, Lower_Expr_Frame{expr=expr.left}) case .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 || 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 == 1 { last = append_instruction(state, ir.Instruction{ op=.Widen, 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 { last = append_instruction(state, ir.Instruction{ op=.Add_Checked, 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 } } last = append_instruction(state, ir.Instruction{ op=.Call, span=expr.span, type=expr.type, target=ir.function_ref(ir.Function_Id(hir.as_function(expr.target))), a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, args=stack[frame_index].args, diagnostic=source.INVALID_DIAGNOSTIC, }) stack[frame_index].args = nil _ = pop(&stack) } } return last } lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: mem.Allocator) -> []ir.Instruction { state := State{ hir_module=hir_module, allocator=allocator, 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 defer { delete(state.local_values, allocator) delete(state.local_slots, allocator) delete(state.expr_stack) } 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 statement_id in function.body { statement := hir_module.statements[statement_id] switch statement.kind { case .Declaration: value := lower_expr(&state, statement.expr) if statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(function.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 := function.locals[statement.local] if local.mutable { 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 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, }) } else { state.local_values[statement.local] = value } case .Assignment: value := lower_expr(&state, statement.expr) slot := ir.INVALID_INSTRUCTION if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) { slot = state.local_slots[statement.local] } if slot == ir.INVALID_INSTRUCTION || statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(function.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 } append_instruction(&state, ir.Instruction{ op=.Store, span=statement.span, type=function.locals[statement.local].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=function.result, target=ir.INVALID_REF, a=value, 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, }) } } if len(state.instructions) == 0 || (state.instructions[len(state.instructions)-1].op != .Return && state.instructions[len(state.instructions)-1].op != .Return_Void) { if function.result.kind == .Void { 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), 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(allocator) for global in hir_module.globals { _ = ir.global_id(len(module.globals)) append(&module.globals, ir.Global{ name=global.name, type=global.type, is_static=global.is_static, static_value=global.static_value, initializer=nil if global.is_static 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, 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 }