package llvm import "../ir" import "../source" import "../symbol" import "../target" import "../types" import "core:fmt" import "core:mem" import "core:strings" Trap_Message :: struct { text: string, } Emitter :: struct { module: ^ir.Module, diagnostics: ^source.Diagnostics, symbols: ^symbol.Table, builder: strings.Builder, messages: [dynamic]Trap_Message, allocator: mem.Allocator, } C_Record_ABI_Kind :: enum u8 { None, Small_Integer, Integer_Pair, Homogeneous_Float, Indirect, } C_Record_ABI :: struct { kind: C_Record_ABI_Kind, size: u64, alignment: int, float_type: types.Type, float_count: int, } hfa_walk :: proc(value: types.Type, store: ^types.Store, scalar: ^types.Type, count: ^int, depth := 0) -> bool { if depth > 64 || count^ > 4 { return false } if types.is_float(value, store.selected) { repr := types.representation(value, store.selected) if scalar^ == types.INVALID { scalar^ = repr } if scalar^ != repr { return false } count^ += 1 return count^ <= 4 } item, ok := types.node(store, value) if !ok || item.kind == .Union { return false } if item.kind == .Array { for _ in 0.. C_Record_ABI { if !types.is_record(value, store) { return {} } result := C_Record_ABI{ size=types.size(value, store, store.selected), alignment=types.alignment_of(value, store, store.selected), } scalar := types.INVALID count := 0 if !types.is_union(value, store) && hfa_walk(value, store, &scalar, &count) && count > 0 { result.kind = .Homogeneous_Float result.float_type = scalar result.float_count = count return result } if result.size <= 8 { result.kind = .Small_Integer } else if result.size <= 16 { result.kind = .Integer_Pair } else { result.kind = .Indirect } return result } c_abi_param_type :: proc(value: types.Type, store: ^types.Store) -> string { abi := c_record_abi(value, store) switch abi.kind { case .None: return llvm_type(value, store) case .Small_Integer: return "i64" case .Integer_Pair: return "[2 x i64]" case .Homogeneous_Float: return fmt.tprintf("[%d x %s]", abi.float_count, llvm_type(abi.float_type, store)) case .Indirect: return "ptr" } return llvm_type(value, store) } c_abi_result_type :: proc(value: types.Type, store: ^types.Store) -> string { abi := c_record_abi(value, store) switch abi.kind { case .None, .Homogeneous_Float: return llvm_type(value, store) case .Small_Integer: return fmt.tprintf("i%d", abi.size*8) case .Integer_Pair: return "[2 x i64]" case .Indirect: return "void" } return llvm_type(value, store) } llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string { resolved := value if store != nil { resolved = types.runtime_representation(value, store) } if types.is_void(resolved) { return "void" } if types.is_bool(resolved) { return "i1" } #partial switch types.kind(resolved, store) { case .Pointer: return "ptr" case .Slice: return "{ ptr, i64 }" case .Range: item, _ := types.node(store, resolved) child := llvm_type(item.child, store) return fmt.tprintf("{{ %s, %s, i1 }}", child, child) case .Array: item, _ := types.node(store, resolved) return fmt.tprintf("[%d x %s]", types.physical_count(resolved, store), llvm_type(item.child, store)) case .Optional: item, _ := types.node(store, resolved) if types.is_pointer(item.child, store) { return "ptr" } return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store)) case .Struct, .Union: return fmt.tprintf("%%bro.type.%d", resolved) } selected := store.selected if store != nil else target.DEFAULT repr := types.representation(resolved, selected) if types.is_float(repr) { return "float" if types.bits(repr) == 32 else "double" } switch types.bits(repr) { case 8: return "i8" case 16: return "i16" case 32: return "i32" case: return "i64" } } function_result_type :: proc(function: ir.Function, store: ^types.Store) -> string { if function.is_main { return "i32" } if function.calling_convention == .C { return c_abi_result_type(function.result, store) } return llvm_type(function.result, store) } c_abi_extension :: proc(value: types.Type, store: ^types.Store) -> string { resolved := types.runtime_representation(value, store) if types.is_bool(resolved) { // clang lowers C `_Bool` as `zeroext i1` across the ABI boundary. return "zeroext" } if !types.is_concrete_integer(resolved) { return "" } switch target.c_integer_extension( store.selected, types.bits(resolved, store.selected), types.is_signed(resolved, store.selected), ) { case .Sign: return "signext" case .Zero: return "zeroext" case .None: return "" } return "" } emit_function_result :: proc(builder: ^strings.Builder, function: ir.Function, store: ^types.Store) { if function.calling_convention == .C { extension := c_abi_extension(function.result, store) if len(extension) > 0 { fmt.sbprintf(builder, "%s ", extension) } } strings.write_string(builder, function_result_type(function, store)) } sentinel :: proc(value_type: types.Type, store: ^types.Store = nil, selected := target.DEFAULT) -> i64 { repr := value_type if store != nil { repr = types.runtime_representation(value_type, store) } if types.is_bool(repr) { return 0 } switch types.bits(repr, selected) { case 8: return -86 case 16: return -21846 case 32: return -1431655766 case: return -6148914691236517206 } } valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: ir.Instruction_Id) -> bool { return instruction_id != ir.INVALID_INSTRUCTION && int(instruction_id) < len(instructions) } valid_value :: proc( instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type, store: ^types.Store, ) -> bool { if !valid_instruction(instructions, value_id) || !types.is_runtime_value(expected, store) || !types.equal(instructions[value_id].type, expected) { return false } switch instructions[value_id].op { case .Param, .Const, .String, .Aggregate, .None, .Optional_Some, .Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Extract, .Select, .Unwrap, .Optional_Is_Some, .Optional_Value, .Orelse, .Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer, .Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call: return true case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin, .Store, .Fill, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void: return false } return false } valid_address :: proc( instructions: []ir.Instruction, value_id: ir.Instruction_Id, pointee: types.Type, store: ^types.Store, ) -> bool { if !valid_instruction(instructions, value_id) { return false } value := instructions[value_id] #partial switch value.op { case .Address_Global, .Alloca, .Index_Address, .Field_Address: return types.equal(value.type, pointee) case: return types.is_pointer(value.type, store) && types.equal(types.child_type(value.type, store), pointee) && valid_value(instructions, value_id, value.type, store) } } write_constant :: proc(builder: ^strings.Builder, value: i64, value_type: types.Type, store: ^types.Store = nil) { resolved := value_type if store != nil { resolved = types.runtime_representation(value_type, store) } if !types.is_concrete_scalar(resolved) { strings.write_string(builder, "zeroinitializer") return } if types.is_bool(resolved) { strings.write_string(builder, "true" if value != 0 else "false") return } selected := store.selected if store != nil else target.DEFAULT if types.is_float(resolved, selected) { // LLVM rejects decimal float literals that don't round-trip exactly, so // emit the IEEE-754 double bit pattern as a hex literal (`0x...`), which // always parses. For `float` we widen the f32 to f64 first — exact, and // LLVM requires the value be representable as float, which it is. number := transmute(f64)value if types.bits(resolved, selected) == 32 { number = f64(transmute(f32)u32(value)) } fmt.sbprintf(builder, "0x%016X", transmute(u64)number) return } fmt.sbprintf(builder, "%d", value) } write_operand :: proc( builder: ^strings.Builder, instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type, store: ^types.Store, ) { if !valid_value(instructions, value_id, expected, store) { write_constant(builder, sentinel(expected, store, store.selected), expected, store) return } value := instructions[value_id] if value.op == .Const { write_constant(builder, value.integer, expected, store) } else { fmt.sbprintf(builder, "%%v%d", value_id) } } register_message :: proc(emitter: ^Emitter, text: string) -> int { id := len(emitter.messages) cloned := fmt.aprintf("%s\n", text, allocator=emitter.allocator) append(&emitter.messages, Trap_Message{text=cloned}) return id } diagnostic_message :: proc(emitter: ^Emitter, diagnostic: source.Diagnostic_Id, span: source.Span, fallback: string) -> int { if _, ok := source.diagnostic_index(diagnostic, len(emitter.diagnostics.items)); ok { message := source.format(emitter.diagnostics, diagnostic, emitter.allocator) id := register_message(emitter, message) delete(message, emitter.allocator) return id } source_file := source.source_for_span(emitter.diagnostics, span) if source_file == nil { return register_message(emitter, fallback) } line, column := source.line_and_column(source_file, span.start) message := fmt.aprintf( "%s:%d:%d: runtime trap: %s", source_file.path, line, column, fallback, allocator=emitter.allocator, ) id := register_message(emitter, message) delete(message, emitter.allocator) return id } emit_trap_call :: proc(emitter: ^Emitter, message_id: int) { message := emitter.messages[message_id] fmt.sbprintf( &emitter.builder, " call void @bro.trap(ptr @bro.msg.%d, i64 %d)\n", message_id, len(message.text), ) } emit_recovery_value :: proc(emitter: ^Emitter, instruction_id: int, instruction: ir.Instruction, fallback: string) { message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, fallback) emit_trap_call(emitter, message) if types.is_runtime_value(instruction.type, &emitter.module.types) { if !types.is_float(instruction.type, emitter.module.target) { if !types.is_concrete_scalar(instruction.type) { fmt.sbprintf( &emitter.builder, " %%v%d = freeze %s zeroinitializer\n", instruction_id, llvm_type(instruction.type, &emitter.module.types), ) return } fmt.sbprintf( &emitter.builder, " %%v%d = add %s 0, %d\n", instruction_id, llvm_type(instruction.type, &emitter.module.types), sentinel(instruction.type, &emitter.module.types, emitter.module.target), ) return } fmt.sbprintf( &emitter.builder, " %%v%d = select i1 true, %s ", instruction_id, llvm_type(instruction.type, &emitter.module.types), ) write_constant(&emitter.builder, sentinel(instruction.type, &emitter.module.types, emitter.module.target), instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(instruction.type, &emitter.module.types)) write_constant(&emitter.builder, sentinel(instruction.type, &emitter.module.types, emitter.module.target), instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") } } emit_call_args :: proc( builder: ^strings.Builder, instructions: []ir.Instruction, args: []ir.Instruction_Id, param_types: []types.Type, store: ^types.Store, c_abi := false, ) { for arg, index in args { if index > 0 { strings.write_string(builder, ", ") } arg_type := param_types[index] if index < len(param_types) && valid_instruction(instructions, arg) else (instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID) fmt.sbprintf(builder, "%s ", llvm_type(arg_type, store)) if c_abi && index < len(param_types) { extension := c_abi_extension(arg_type, store) if len(extension) > 0 { fmt.sbprintf(builder, "%s ", extension) } } write_operand(builder, instructions, arg, arg_type, store) } } emit_pack_c_record_arg :: proc( emitter: ^Emitter, instructions: []ir.Instruction, value: ir.Instruction_Id, value_type: types.Type, call_index, arg_index: int, ) -> string { abi := c_record_abi(value_type, &emitter.module.types) if abi.kind == .None { return fmt.tprintf("%%v%d", value) } if abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, " %%abi_arg_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, llvm_type(value_type, &emitter.module.types), abi.alignment) fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(value_type, &emitter.module.types)) write_operand(&emitter.builder, instructions, value, value_type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr %%abi_arg_slot%d_%d\n", call_index, arg_index) return fmt.tprintf("%%abi_arg_slot%d_%d", call_index, arg_index) } abi_type := c_abi_param_type(value_type, &emitter.module.types) temp_alignment := max(abi.alignment, 8) fmt.sbprintf(&emitter.builder, " %%abi_arg_value_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, llvm_type(value_type, &emitter.module.types), abi.alignment) fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(value_type, &emitter.module.types)) write_operand(&emitter.builder, instructions, value, value_type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr %%abi_arg_value_slot%d_%d\n", call_index, arg_index) fmt.sbprintf(&emitter.builder, " %%abi_arg_slot%d_%d = alloca %s, align %d\n", call_index, arg_index, abi_type, temp_alignment) fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%abi_arg_slot%d_%d\n", abi_type, call_index, arg_index) fmt.sbprintf( &emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_arg_slot%d_%d, ptr align %d %%abi_arg_value_slot%d_%d, i64 %d, i1 false)\n", temp_alignment, call_index, arg_index, abi.alignment, call_index, arg_index, abi.size, ) fmt.sbprintf(&emitter.builder, " %%abi_arg%d_%d = load %s, ptr %%abi_arg_slot%d_%d\n", call_index, arg_index, abi_type, call_index, arg_index) return fmt.tprintf("%%abi_arg%d_%d", call_index, arg_index) } emit_unpack_c_record :: proc( emitter: ^Emitter, value_type: types.Type, abi_type, abi_name, result_name: string, tag: int, ) { abi := c_record_abi(value_type, &emitter.module.types) if abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, " %s = load %s, ptr %s\n", result_name, llvm_type(value_type, &emitter.module.types), abi_name) return } temp_alignment := max(abi.alignment, 8) fmt.sbprintf(&emitter.builder, " %%abi_unpack_source_slot%d = alloca %s, align %d\n", tag, abi_type, temp_alignment) fmt.sbprintf(&emitter.builder, " store %s %s, ptr %%abi_unpack_source_slot%d\n", abi_type, abi_name, tag) fmt.sbprintf(&emitter.builder, " %%abi_unpack_slot%d = alloca %s, align %d\n", tag, llvm_type(value_type, &emitter.module.types), abi.alignment) fmt.sbprintf( &emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_unpack_slot%d, ptr align %d %%abi_unpack_source_slot%d, i64 %d, i1 false)\n", abi.alignment, tag, temp_alignment, tag, abi.size, ) fmt.sbprintf(&emitter.builder, " %s = load %s, ptr %%abi_unpack_slot%d\n", result_name, llvm_type(value_type, &emitter.module.types), tag) } integer_predicate :: proc(predicate: ir.Compare_Predicate, signed: bool) -> string { switch predicate { case .Eq: return "eq" case .Ne: return "ne" case .Lt: return "slt" if signed else "ult" case .Le: return "sle" if signed else "ule" case .Gt: return "sgt" if signed else "ugt" case .Ge: return "sge" if signed else "uge" } return "eq" } float_predicate :: proc(predicate: ir.Compare_Predicate) -> string { switch predicate { case .Eq: return "oeq" case .Ne: return "une" case .Lt: return "olt" case .Le: return "ole" case .Gt: return "ogt" case .Ge: return "oge" } return "oeq" } emit_entry_allocas :: proc(emitter: ^Emitter, instructions: []ir.Instruction) { for instruction, instruction_index in instructions { if instruction.op == .Alloca && types.is_runtime_value(instruction.type, &emitter.module.types) { fmt.sbprintf( &emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), ) } } } // emit_checked_arithmetic emits a trapping integer add/sub/mul through the LLVM // `.with.overflow` intrinsics, or a plain floating-point operation. `mnemonic` // is the integer intrinsic stem ("add"/"sub"/"mul"); the signed/unsigned prefix // is chosen from the operand type. `float_op` is the matching float instruction. emit_checked_arithmetic :: proc( emitter: ^Emitter, instructions: []ir.Instruction, instruction_index: int, instruction: ir.Instruction, mnemonic: string, float_op: string, overflow_message: string, ) { type_name := llvm_type(instruction.type, &emitter.module.types) if types.is_float(instruction.type, emitter.module.target) { fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, float_op, type_name) write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, ", ") write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") return } prefix := "u" if types.is_unsigned(instruction.type, emitter.module.target) else "s" fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index) strings.write_string(&emitter.builder, "{ ") fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s%s.with.overflow.%s(%s ", type_name, prefix, mnemonic, type_name, type_name) write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %s ", type_name) write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ")\n") fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index) strings.write_string(&emitter.builder, "{ ") fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 0\n", type_name, instruction_index) fmt.sbprintf(&emitter.builder, " %%overflow%d = extractvalue ", instruction_index) strings.write_string(&emitter.builder, "{ ") fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 1\n", type_name, instruction_index) fmt.sbprintf( &emitter.builder, " br i1 %%overflow%d, label %%overflow_trap%d, label %%overflow_continue%d\n", instruction_index, instruction_index, instruction_index, ) fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index) message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, overflow_message) emit_trap_call(emitter, message) fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index) } // emit_checked_division emits a trapping integer division guarding divide-by-zero // and signed `INT_MIN / -1` overflow, or a plain floating-point division. emit_checked_division :: proc( emitter: ^Emitter, instructions: []ir.Instruction, instruction_index: int, instruction: ir.Instruction, ) { type_name := llvm_type(instruction.type, &emitter.module.types) if types.is_float(instruction.type, emitter.module.target) { fmt.sbprintf(&emitter.builder, " %%v%d = fdiv %s ", instruction_index, type_name) write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, ", ") write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") return } signed := !types.is_unsigned(instruction.type, emitter.module.target) fmt.sbprintf(&emitter.builder, " %%divzero%d = icmp eq %s ", instruction_index, type_name) write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, ", 0\n") fmt.sbprintf( &emitter.builder, " br i1 %%divzero%d, label %%divzero_trap%d, label %%divzero_ok%d\n", instruction_index, instruction_index, instruction_index, ) fmt.sbprintf(&emitter.builder, "divzero_trap%d:\n", instruction_index) zero_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "integer division by zero") emit_trap_call(emitter, zero_message) fmt.sbprintf(&emitter.builder, " unreachable\ndivzero_ok%d:\n", instruction_index) if signed { min_value := -(i128(1) << u32(types.bits(instruction.type, emitter.module.target) - 1)) fmt.sbprintf(&emitter.builder, " %%divminlo%d = icmp eq %s ", instruction_index, type_name) write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %d\n", min_value) fmt.sbprintf(&emitter.builder, " %%divminhi%d = icmp eq %s ", instruction_index, type_name) write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, ", -1\n") fmt.sbprintf(&emitter.builder, " %%divovf%d = and i1 %%divminlo%d, %%divminhi%d\n", instruction_index, instruction_index, instruction_index) fmt.sbprintf( &emitter.builder, " br i1 %%divovf%d, label %%divovf_trap%d, label %%divovf_ok%d\n", instruction_index, instruction_index, instruction_index, ) fmt.sbprintf(&emitter.builder, "divovf_trap%d:\n", instruction_index) ovf_message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer division overflow") emit_trap_call(emitter, ovf_message) fmt.sbprintf(&emitter.builder, " unreachable\ndivovf_ok%d:\n", instruction_index) fmt.sbprintf(&emitter.builder, " %%v%d = sdiv %s ", instruction_index, type_name) } else { fmt.sbprintf(&emitter.builder, " %%v%d = udiv %s ", instruction_index, type_name) } write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, ", ") write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") } emit_instruction_stream :: proc( emitter: ^Emitter, instructions: []ir.Instruction, function: ir.Function, global_initializer := false, sret_name := "", ) -> ir.Instruction_Id { return_value := ir.INVALID_INSTRUCTION after_return := false for instruction, instruction_index in instructions { instruction_id := ir.instruction_id(instruction_index) if after_return { fmt.sbprintf(&emitter.builder, "recover_after_return_%d:\n", instruction_index) after_return = false } switch instruction.op { case .Param, .Const: case .String: string_id := int(instruction.integer) _, array, pointer_ok := types.array_pointer(instruction.type, &emitter.module.types) if string_id < 0 || string_id >= len(emitter.module.strings) || !pointer_ok || array.child != types.U8 || !array.has_sentinel || array.sentinel != 0 || array.count != u64(len(emitter.module.strings[string_id])) { emit_recovery_value(emitter, instruction_index, instruction, "invalid string literal") continue } fmt.sbprintf( &emitter.builder, " %%v%d = getelementptr %s, ptr @bro.str.%d, i64 0\n", instruction_index, llvm_type(types.child_type(instruction.type, &emitter.module.types), &emitter.module.types), string_id, ) case .Aggregate: item, ok := types.node(&emitter.module.types, instruction.type) expected_count := 0 if ok && item.kind == .Array { expected_count = int(item.count) } else if ok && item.kind == .Struct { expected_count = int(item.field_count) } else if ok && item.kind == .Union { expected_count = 1 } else if ok && item.kind == .Range { expected_count = 3 } else { emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate type") continue } if len(instruction.args) != expected_count { emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate operands") continue } type_name := llvm_type(instruction.type, &emitter.module.types) if item.kind == .Union { fields := types.fields_for(&emitter.module.types, instruction.type) field_index := int(instruction.integer) if field_index < 0 || field_index >= len(fields) || !valid_value(instructions, instruction.args[0], fields[field_index].type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid union aggregate operands") continue } fmt.sbprintf(&emitter.builder, " %%union_slot%d = alloca %s, align %d\n", instruction_index, type_name, types.alignment_of(instruction.type, &emitter.module.types, emitter.module.target)) fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%union_slot%d\n", type_name, instruction_index) fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(fields[field_index].type, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.args[0], fields[field_index].type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr %%union_slot%d\n", instruction_index) fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%union_slot%d\n", instruction_index, type_name, instruction_index) continue } total := len(instruction.args) + (1 if item.kind == .Array && item.has_sentinel else 0) if total == 0 { fmt.sbprintf(&emitter.builder, " %%v%d = freeze %s zeroinitializer\n", instruction_index, type_name) continue } for arg_index := 0; arg_index < total; arg_index += 1 { element_type := item.child if item.kind == .Struct { element_type = types.fields_for(&emitter.module.types, instruction.type)[arg_index].type } else if item.kind == .Range && arg_index == 2 { element_type = types.BOOL } final := arg_index == total-1 if final { fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s ", instruction_index, type_name) } else { fmt.sbprintf(&emitter.builder, " %%aggregate%d_%d = insertvalue %s ", instruction_index, arg_index, type_name) } if arg_index == 0 { strings.write_string(&emitter.builder, "poison") } else { fmt.sbprintf(&emitter.builder, "%%aggregate%d_%d", instruction_index, arg_index-1) } fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(element_type, &emitter.module.types)) if arg_index < len(instruction.args) { write_operand(&emitter.builder, instructions, instruction.args[arg_index], element_type, &emitter.module.types) } else { write_constant(&emitter.builder, i64(item.sentinel), element_type, &emitter.module.types) } fmt.sbprintf(&emitter.builder, ", %d\n", arg_index) } case .None: item, ok := types.node(&emitter.module.types, instruction.type) if !ok || item.kind != .Optional { emit_recovery_value(emitter, instruction_index, instruction, "invalid optional none") continue } if types.is_pointer(item.child, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr null, ptr null\n", instruction_index) } else { fmt.sbprintf( &emitter.builder, " %%v%d = insertvalue %s zeroinitializer, i1 false, 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), ) } case .Optional_Some: item, ok := types.node(&emitter.module.types, instruction.type) if !ok || item.kind != .Optional || !valid_value(instructions, instruction.a, item.child, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid optional value") continue } if types.is_pointer(item.child, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr ", instruction_index) write_operand(&emitter.builder, instructions, instruction.a, item.child, &emitter.module.types) strings.write_string(&emitter.builder, ", ptr null\n") } else { type_name := llvm_type(instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " %%optional%d = insertvalue %s poison, i1 true, 0\n", instruction_index, type_name) fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%optional%d, %s ", instruction_index, type_name, instruction_index, llvm_type(item.child, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.a, item.child, &emitter.module.types) strings.write_string(&emitter.builder, ", 1\n") } case .Load_Global: global_id := ir.as_global(instruction.target) if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) { emit_recovery_value(emitter, instruction_index, instruction, "invalid global reference") continue } global := emitter.module.globals[global_id] if !types.equal(instruction.type, global.type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid global reference type") continue } if global.external { fmt.sbprintf( &emitter.builder, " %%v%d = load %s, ptr @%s\n", instruction_index, llvm_type(global.type, &emitter.module.types), global.link_name, ) } else if global.is_static { fmt.sbprintf( &emitter.builder, " %%v%d = load %s, ptr @bro.g.%d\n", instruction_index, llvm_type(global.type, &emitter.module.types), global_id, ) } else { fmt.sbprintf( &emitter.builder, " %%v%d = call %s @bro.get.%d()\n", instruction_index, llvm_type(global.type, &emitter.module.types), global_id, ) } case .Function_Address: function_id := ir.as_function(instruction.target) if function_id == ir.INVALID_FUNCTION || int(function_id) >= len(emitter.module.functions) { emit_recovery_value(emitter, instruction_index, instruction, "invalid function reference") continue } target := emitter.module.functions[function_id] fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr @%s, ptr null\n", instruction_index, target.link_name) case .Address_Global: global_id := ir.as_global(instruction.target) if global_id == ir.INVALID_GLOBAL || int(global_id) >= len(emitter.module.globals) || !types.equal(instruction.type, emitter.module.globals[global_id].type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid global address") continue } global := emitter.module.globals[global_id] if global.external { fmt.sbprintf( &emitter.builder, " %%v%d = getelementptr %s, ptr @%s, i64 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), global.link_name, ) continue } fmt.sbprintf( &emitter.builder, " %%v%d = getelementptr %s, ptr @bro.g.%d, i64 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), global_id, ) case .Address_Of: child := types.child_type(instruction.type, &emitter.module.types) if !types.is_pointer(instruction.type, &emitter.module.types) || !valid_address(instructions, instruction.a, child, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid address operand") continue } fmt.sbprintf( &emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 0\n", instruction_index, llvm_type(child, &emitter.module.types), instruction.a, ) case .Alloca: if !types.is_runtime_value(instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type") continue } if global_initializer { fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types)) } case .Index_Address: if !valid_instruction(instructions, instruction.a) || !valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid index operands") continue } container := instructions[instruction.a] container_node, container_ok := types.container(container.type, &emitter.module.types) if !container_ok { emit_recovery_value(emitter, instruction_index, instruction, "invalid index container") continue } pointer_name := fmt.tprintf("%%v%d", instruction.a) length: u64 bounded := false if container_node.kind == .Array { length = container_node.count if container_node.has_sentinel && instruction.integer != 0 { length += 1 } bounded = true } else if container_node.kind == .Slice { fmt.sbprintf(&emitter.builder, " %%index_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) fmt.sbprintf(&emitter.builder, " %%index_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) pointer_name = fmt.tprintf("%%index_ptr%d", instruction_index) comparison := "ule" if container_node.has_sentinel && instruction.integer != 0 else "ult" fmt.sbprintf(&emitter.builder, " %%index_ok%d = icmp %s i64 ", instruction_index, comparison) write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %%index_len%d\n", instruction_index) bounded = true } else if container_node.kind != .Pointer || !container_node.many { emit_recovery_value(emitter, instruction_index, instruction, "invalid index container") continue } if bounded { if container_node.kind == .Array { fmt.sbprintf(&emitter.builder, " %%index_ok%d = icmp ult i64 ", instruction_index) write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %d\n", length) } fmt.sbprintf(&emitter.builder, " br i1 %%index_ok%d, label %%index_continue%d, label %%index_trap%d\nindex_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index) message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "index out of bounds") emit_trap_call(emitter, message) fmt.sbprintf(&emitter.builder, " unreachable\nindex_continue%d:\n", instruction_index) } if container_node.kind == .Array { array_type := container.type _, array, array_pointer_ok := types.array_pointer(container.type, &emitter.module.types) if array_pointer_ok { array_type = types.child_type(container.type, &emitter.module.types) container_node = array } fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(array_type, &emitter.module.types), pointer_name) } else { fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 ", instruction_index, llvm_type(instruction.type, &emitter.module.types), pointer_name) } write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) strings.write_string(&emitter.builder, "\n") case .Field_Address: if !valid_instruction(instructions, instruction.a) { emit_recovery_value(emitter, instruction_index, instruction, "invalid field base") continue } base_type := instructions[instruction.a].type if types.is_pointer(base_type, &emitter.module.types) { base_type = types.child_type(base_type, &emitter.module.types) } fields := types.fields_for(&emitter.module.types, base_type) field_index := int(instruction.integer) if field_index < 0 || field_index >= len(fields) || !types.equal(fields[field_index].type, instruction.type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid field reference") continue } if types.is_union(base_type, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr i8, ptr %%v%d, i64 0\n", instruction_index, instruction.a) } else { fmt.sbprintf( &emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i32 0, i32 %d\n", instruction_index, llvm_type(base_type, &emitter.module.types), instruction.a, field_index, ) } case .Load: if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid load slot") continue } fmt.sbprintf( &emitter.builder, " %%v%d = load %s, ptr %%v%d\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction.a, ) case .Store: if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid store operand") continue } fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr %%v%d\n", instruction.a) case .Fill: if !valid_address(instructions, instruction.a, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid fill slot") continue } fmt.sbprintf( &emitter.builder, " call void @llvm.memset.p0.i64(ptr %%v%d, i8 -86, i64 %d, i1 false)\n", instruction.a, types.size(instruction.type, &emitter.module.types, emitter.module.target), ) case .Slice: if !valid_instruction(instructions, instruction.a) { emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container") continue } container := instructions[instruction.a] item, ok := types.container(container.type, &emitter.module.types) if !ok || (item.kind != .Array && item.kind != .Slice) { emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container") continue } pointer_name := fmt.tprintf("%%v%d", instruction.a) length_name := fmt.tprintf("%d", item.count) if item.kind == .Array { array_type := container.type if types.is_pointer(container.type, &emitter.module.types) { array_type = types.child_type(container.type, &emitter.module.types) } fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a) pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index) } else if item.kind == .Slice { fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) fmt.sbprintf(&emitter.builder, " %%slice_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index) length_name = fmt.tprintf("%%slice_len%d", instruction_index) } fmt.sbprintf(&emitter.builder, " %%slice_bound_start%d = add i64 0, ", instruction_index) if len(instruction.args) > 0 && instruction.args[0] != ir.INVALID_INSTRUCTION { write_operand(&emitter.builder, instructions, instruction.args[0], types.USIZE, &emitter.module.types) } else { strings.write_string(&emitter.builder, "0") } strings.write_string(&emitter.builder, "\n") fmt.sbprintf(&emitter.builder, " %%slice_bound_end%d = add i64 0, ", instruction_index) if len(instruction.args) > 1 && instruction.args[1] != ir.INVALID_INSTRUCTION { write_operand(&emitter.builder, instructions, instruction.args[1], types.USIZE, &emitter.module.types) } else { strings.write_string(&emitter.builder, length_name) } strings.write_string(&emitter.builder, "\n") start_name := fmt.tprintf("%%slice_bound_start%d", instruction_index) end_name := fmt.tprintf("%%slice_bound_end%d", instruction_index) fmt.sbprintf(&emitter.builder, " %%slice_order%d = icmp ule i64 %s, %s\n", instruction_index, start_name, end_name) fmt.sbprintf(&emitter.builder, " %%slice_end_ok%d = icmp ule i64 %s, %s\n", instruction_index, end_name, length_name) fmt.sbprintf(&emitter.builder, " %%slice_ok%d = and i1 %%slice_order%d, %%slice_end_ok%d\n", instruction_index, instruction_index, instruction_index) fmt.sbprintf(&emitter.builder, " br i1 %%slice_ok%d, label %%slice_continue%d, label %%slice_trap%d\nslice_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index) message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "slice bounds out of range") emit_trap_call(emitter, message) fmt.sbprintf(&emitter.builder, " unreachable\nslice_continue%d:\n", instruction_index) fmt.sbprintf(&emitter.builder, " %%slice_start%d = getelementptr %s, ptr %s, i64 %s\n", instruction_index, llvm_type(item.child, &emitter.module.types), pointer_name, start_name) fmt.sbprintf(&emitter.builder, " %%slice_result%d = insertvalue %s poison, ptr %%slice_start%d, 0\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index) fmt.sbprintf(&emitter.builder, " %%slice_result_len%d = sub i64 %s, %s\n", instruction_index, end_name, start_name) fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%slice_result%d, i64 %%slice_result_len%d, 1\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index, instruction_index) case .Length: if !valid_instruction(instructions, instruction.a) || !types.is_slice(instructions[instruction.a].type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid slice length") continue } fmt.sbprintf( &emitter.builder, " %%v%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(instructions[instruction.a].type, &emitter.module.types), instruction.a, ) case .Slice_Ptr: if !valid_instruction(instructions, instruction.a) || !types.is_pointer(instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid container pointer") continue } container_type := instructions[instruction.a].type _, _, array_pointer_ok := types.array_pointer(container_type, &emitter.module.types) if types.is_array(container_type, &emitter.module.types) { fmt.sbprintf( &emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a, ) } else if array_pointer_ok { fmt.sbprintf( &emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(types.child_type(container_type, &emitter.module.types), &emitter.module.types), instruction.a, ) } else if types.is_slice(container_type, &emitter.module.types) { fmt.sbprintf( &emitter.builder, " %%v%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a, ) } else { emit_recovery_value(emitter, instruction_index, instruction, "invalid container pointer") } case .Extract: if !valid_instruction(instructions, instruction.a) { emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate extraction") continue } aggregate_type := instructions[instruction.a].type item, ok := types.node(&emitter.module.types, aggregate_type) field_index := int(instruction.integer) expected_type := types.INVALID if ok && item.kind == .Range && field_index >= 0 && field_index < 3 { expected_type = types.BOOL if field_index == 2 else item.child } if !types.equal(expected_type, instruction.type) || !valid_value(instructions, instruction.a, aggregate_type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid aggregate extraction") continue } fmt.sbprintf( &emitter.builder, " %%v%d = extractvalue %s %%v%d, %d\n", instruction_index, llvm_type(aggregate_type, &emitter.module.types), instruction.a, field_index, ) case .Select: if len(instruction.args) != 2 || !valid_value(instructions, instruction.a, types.BOOL, &emitter.module.types) || !valid_value(instructions, instruction.args[0], instruction.type, &emitter.module.types) || !valid_value(instructions, instruction.args[1], instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid selection") continue } fmt.sbprintf(&emitter.builder, " %%v%d = select i1 ", instruction_index) write_operand(&emitter.builder, instructions, instruction.a, types.BOOL, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(instruction.type, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.args[0], instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %s ", llvm_type(instruction.type, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.args[1], instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") case .Unwrap: optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID item, ok := types.node(&emitter.module.types, optional_type) if !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid optional unwrap") continue } if types.is_pointer(item.child, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%optional_ok%d = icmp ne ptr %%v%d, null\n", instruction_index, instruction.a) } else { fmt.sbprintf(&emitter.builder, " %%optional_ok%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a) } fmt.sbprintf(&emitter.builder, " br i1 %%optional_ok%d, label %%optional_continue%d, label %%optional_trap%d\noptional_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index) message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "attempted to unwrap none") emit_trap_call(emitter, message) fmt.sbprintf(&emitter.builder, " unreachable\noptional_continue%d:\n", instruction_index) if types.is_pointer(item.child, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a) } else { fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a) } case .Optional_Is_Some: optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID item, ok := types.node(&emitter.module.types, optional_type) if !ok || item.kind != .Optional { emit_recovery_value(emitter, instruction_index, instruction, "invalid optional presence test") continue } if types.is_pointer(item.child, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%v%d = icmp ne ptr %%v%d, null\n", instruction_index, instruction.a) } else { fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a) } case .Optional_Value: optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID item, ok := types.node(&emitter.module.types, optional_type) if !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid optional value") continue } if types.is_pointer(item.child, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a) } else { fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a) } case .Orelse_Begin: optional_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID item, ok := types.node(&emitter.module.types, optional_type) if !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) || !valid_value(instructions, instruction.a, optional_type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid optional fallback") continue } fmt.sbprintf(&emitter.builder, " %%orelse_slot%d = alloca %s\n", instruction_index, llvm_type(instruction.type, &emitter.module.types)) if types.is_pointer(item.child, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%orelse_ok%d = icmp ne ptr %%v%d, null\n", instruction_index, instruction.a) } else { fmt.sbprintf(&emitter.builder, " %%orelse_ok%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(optional_type, &emitter.module.types), instruction.a) } fmt.sbprintf( &emitter.builder, " br i1 %%orelse_ok%d, label %%orelse_some%d, label %%orelse_fallback%d\norelse_fallback%d:\n", instruction_index, instruction_index, instruction_index, instruction_index, ) case .Orelse: begin := instructions[instruction.a] if valid_instruction(instructions, instruction.a) else ir.Instruction{} optional_type := instructions[begin.a].type if valid_instruction(instructions, begin.a) else types.INVALID item, ok := types.node(&emitter.module.types, optional_type) if begin.op != .Orelse_Begin || !ok || item.kind != .Optional || !types.equal(item.child, instruction.type) || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid optional fallback") continue } type_name := llvm_type(instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " store %s ", type_name) write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr %%orelse_slot%d\n", instruction.a) fmt.sbprintf(&emitter.builder, " br label %%orelse_merge%d\norelse_some%d:\n", instruction.a, instruction.a) if types.is_pointer(item.child, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " store ptr %%v%d, ptr %%orelse_slot%d\n", begin.a, instruction.a) } else { fmt.sbprintf(&emitter.builder, " %%orelse_value%d = extractvalue %s %%v%d, 1\n", instruction.a, llvm_type(optional_type, &emitter.module.types), begin.a) fmt.sbprintf(&emitter.builder, " store %s %%orelse_value%d, ptr %%orelse_slot%d\n", type_name, instruction.a, instruction.a) } fmt.sbprintf(&emitter.builder, " br label %%orelse_merge%d\norelse_merge%d:\n", instruction.a, instruction.a) fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%orelse_slot%d\n", instruction_index, type_name, instruction.a) case .Widen: if !valid_instruction(instructions, instruction.a) || !types.can_widen(instructions[instruction.a].type, instruction.type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid widening operand") continue } from_type := instructions[instruction.a].type operation := "fpext" if types.is_float(from_type, emitter.module.target) else ("sext" if types.is_signed(from_type, emitter.module.target) else "zext") fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types)) case .C_Coerce: if !valid_instruction(instructions, instruction.a) || !types.can_coerce_c_integer(instructions[instruction.a].type, instruction.type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid C integer coercion operand") continue } from_type := instructions[instruction.a].type from_bits := types.bits(from_type, emitter.module.target) to_bits := types.bits(instruction.type, emitter.module.target) if from_bits == to_bits { // Same-width signedness change: c_uint and c_int both lower to the // identical `iN`, so this is a pure reinterpret (no-op `select`). type_name := llvm_type(instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %s ", type_name) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") continue } operation := "sext" if types.is_signed(from_type, emitter.module.target) else "zext" fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types)) case .C_Vararg_Promote: if !valid_instruction(instructions, instruction.a) { emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand") continue } from_type := instructions[instruction.a].type if types.equal(from_type, instruction.type) || !types.equal(types.c_vararg_promotion(from_type, emitter.module.target, &emitter.module.types), instruction.type) { emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand") continue } from_repr := types.runtime_representation(from_type, &emitter.module.types) if types.bits(from_repr, emitter.module.target) == types.bits(instruction.type, emitter.module.target) { type_name := llvm_type(instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", %s ", type_name) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") continue } operation := "fpext" if types.is_float(from_repr, emitter.module.target) else ("sext" if types.is_signed(from_repr, emitter.module.target) else "zext") fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types)) case .Retype: if !valid_instruction(instructions, instruction.a) || !types.can_construct_distinct(instructions[instruction.a].type, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid distinct type construction") continue } type_name := llvm_type(instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name) write_operand( &emitter.builder, instructions, instruction.a, instructions[instruction.a].type, &emitter.module.types, ) fmt.sbprintf(&emitter.builder, ", %s zeroinitializer\n", type_name) case .Weaken_Pointer: if !valid_instruction(instructions, instruction.a) || !types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer weakening operand") continue } fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a) case .Weaken_Slice: if !valid_instruction(instructions, instruction.a) || !types.can_weaken_slice(instructions[instruction.a].type, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid slice weakening operand") continue } type_name := llvm_type(instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s %%v%d, %s zeroinitializer\n", instruction_index, type_name, instruction.a, type_name) case .Decay_Array_Pointer: if !valid_instruction(instructions, instruction.a) || !types.can_decay_array_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid array pointer decay operand") continue } array_type := types.child_type(instructions[instruction.a].type, &emitter.module.types) array, _ := types.node(&emitter.module.types, array_type) if types.is_pointer(instruction.type, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a) } else { type_name := llvm_type(instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, " %%decay_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a) fmt.sbprintf(&emitter.builder, " %%decay_slice%d = insertvalue %s poison, ptr %%decay_ptr%d, 0\n", instruction_index, type_name, instruction_index) fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%decay_slice%d, i64 %d, 1\n", instruction_index, type_name, instruction_index, array.count) } case .Neg_Checked: if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand") continue } type_name := llvm_type(instruction.type, &emitter.module.types) if types.is_float(instruction.type, emitter.module.target) { fmt.sbprintf(&emitter.builder, " %%v%d = fneg %s ", instruction_index, type_name) write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") continue } fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index) strings.write_string(&emitter.builder, "{ ") fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.ssub.with.overflow.%s(%s 0, %s ", type_name, type_name, type_name, type_name) write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ")\n") fmt.sbprintf(&emitter.builder, " %%v%d = extractvalue ", instruction_index) strings.write_string(&emitter.builder, "{ ") fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 0\n", type_name, instruction_index) fmt.sbprintf(&emitter.builder, " %%overflow%d = extractvalue ", instruction_index) strings.write_string(&emitter.builder, "{ ") fmt.sbprintf(&emitter.builder, "%s, i1 } %%pair%d, 1\n", type_name, instruction_index) fmt.sbprintf( &emitter.builder, " br i1 %%overflow%d, label %%overflow_trap%d, label %%overflow_continue%d\n", instruction_index, instruction_index, instruction_index, ) fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index) message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer negation overflow") emit_trap_call(emitter, message) fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index) case .Add_Checked: if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid addition operand") continue } emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "add", "fadd", "integer addition overflow") case .Sub_Checked: if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid subtraction operand") continue } emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "sub", "fsub", "integer subtraction overflow") case .Mul_Checked: if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid multiplication operand") continue } emit_checked_arithmetic(emitter, instructions, instruction_index, instruction, "mul", "fmul", "integer multiplication overflow") case .Div_Checked: if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid division operand") continue } emit_checked_division(emitter, instructions, instruction_index, instruction) case .Pointer_Add: result_item, result_ok := types.node(&emitter.module.types, instruction.type) base_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID base_item, base_ok := types.node(&emitter.module.types, base_type) if !result_ok || result_item.kind != .Pointer || !base_ok || base_item.kind != .Pointer || !base_item.many || result_item.child != base_item.child || result_item.mutable != base_item.mutable || !valid_value(instructions, instruction.a, base_type, &emitter.module.types) || !valid_value(instructions, instruction.b, types.USIZE, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid pointer offset") continue } fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 ", instruction_index, llvm_type(result_item.child, &emitter.module.types), instruction.a) write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types) strings.write_string(&emitter.builder, "\n") case .Call: function_id := ir.as_function(instruction.target) if function_id == ir.INVALID_FUNCTION { if !valid_instruction(instructions, instruction.a) || !valid_value(instructions, instruction.a, instructions[instruction.a].type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call target") continue } callee := instructions[instruction.a] _, function_item, function_type, ok := types.function_pointer(callee.type, &emitter.module.types) if !ok { emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call target") continue } param_fields := types.params_for(&emitter.module.types, function_type) valid_args := (len(instruction.args) >= len(param_fields) if function_item.variadic else len(instruction.args) == len(param_fields)) && (!function_item.variadic || function_item.c_abi) if valid_args { for arg, index in instruction.args { expected := param_fields[index].type if index < len(param_fields) && valid_instruction(instructions, arg) else (instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID) if index >= len(param_fields) && (!types.is_c_vararg_type(expected, &emitter.module.types) || !types.equal(types.c_vararg_promotion(expected, emitter.module.target, &emitter.module.types), expected)) { valid_args = false break } if !valid_value(instructions, arg, expected, &emitter.module.types) { valid_args = false break } } } if !valid_args || !types.equal(instruction.type, function_item.child) { emit_recovery_value(emitter, instruction_index, instruction, "invalid function pointer call operands") continue } arg_names := make([]string, len(instruction.args), context.temp_allocator) for arg, index in instruction.args { if function_item.c_abi && index < len(param_fields) && types.is_record(param_fields[index].type, &emitter.module.types) { arg_names[index] = emit_pack_c_record_arg( emitter, instructions, arg, param_fields[index].type, instruction_index, index, ) } } result_abi := C_Record_ABI{} if function_item.c_abi { result_abi = c_record_abi(function_item.child, &emitter.module.types) } if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, " %%abi_result_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), result_abi.alignment) strings.write_string(&emitter.builder, " ") } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index) } else if !types.is_void(instruction.type) { fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index) } else { strings.write_string(&emitter.builder, " ") } strings.write_string(&emitter.builder, "call ") if !function_item.c_abi { strings.write_string(&emitter.builder, "fastcc ") } if function_item.c_abi { extension := c_abi_extension(function_item.child, &emitter.module.types) if len(extension) > 0 { fmt.sbprintf(&emitter.builder, "%s ", extension) } strings.write_string(&emitter.builder, c_abi_result_type(function_item.child, &emitter.module.types)) } else { strings.write_string(&emitter.builder, llvm_type(function_item.child, &emitter.module.types)) } if function_item.variadic { strings.write_string(&emitter.builder, " (") wrote_type := false if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, "ptr") wrote_type = true } for param in param_fields { if wrote_type { strings.write_string(&emitter.builder, ", ") } strings.write_string(&emitter.builder, c_abi_param_type(param.type, &emitter.module.types)) wrote_type = true } if wrote_type { strings.write_string(&emitter.builder, ", ") } strings.write_string(&emitter.builder, "...)") } strings.write_string(&emitter.builder, " ") write_operand(&emitter.builder, instructions, instruction.a, callee.type, &emitter.module.types) strings.write_string(&emitter.builder, "(") wrote_arg := false if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, "ptr sret(%s) align %d %%abi_result_slot%d", llvm_type(function_item.child, &emitter.module.types), result_abi.alignment, instruction_index) wrote_arg = true } for arg, index in instruction.args { if wrote_arg { strings.write_string(&emitter.builder, ", ") } arg_type := param_fields[index].type if index < len(param_fields) else instructions[arg].type fixed := index < len(param_fields) if function_item.c_abi && fixed && types.is_record(arg_type, &emitter.module.types) { fmt.sbprintf(&emitter.builder, "%s %s", c_abi_param_type(arg_type, &emitter.module.types), arg_names[index]) } else { fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types)) if function_item.c_abi && fixed { extension := c_abi_extension(arg_type, &emitter.module.types) if len(extension) > 0 { fmt.sbprintf(&emitter.builder, "%s ", extension) } } write_operand(&emitter.builder, instructions, arg, arg_type, &emitter.module.types) } wrote_arg = true } strings.write_string(&emitter.builder, ")\n") if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(function_item.child, &emitter.module.types), instruction_index) } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { emit_unpack_c_record( emitter, function_item.child, c_abi_result_type(function_item.child, &emitter.module.types), fmt.tprintf("%%abi_result%d", instruction_index), fmt.tprintf("%%v%d", instruction_index), 100000+instruction_index, ) } continue } if int(function_id) >= len(emitter.module.functions) { emit_recovery_value(emitter, instruction_index, instruction, "invalid function specialization") continue } target := emitter.module.functions[function_id] valid_args := (len(instruction.args) >= len(target.param_types) if target.variadic else len(instruction.args) == len(target.param_types)) && (!target.variadic || target.calling_convention == .C) if valid_args { for arg, index in instruction.args { expected := target.param_types[index] if index < len(target.param_types) && valid_instruction(instructions, arg) else (instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID) if index >= len(target.param_types) && (!types.is_c_vararg_type(expected, &emitter.module.types) || !types.equal(types.c_vararg_promotion(expected, emitter.module.target, &emitter.module.types), expected)) { valid_args = false break } if !valid_value(instructions, arg, expected, &emitter.module.types) { valid_args = false break } } } target_result := target.result if target.is_main { target_result = types.I32 } if !valid_args || !types.equal(instruction.type, target_result) { emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands") continue } arg_names := make([]string, len(instruction.args), context.temp_allocator) for arg, index in instruction.args { if target.calling_convention == .C && index < len(target.param_types) && types.is_record(target.param_types[index], &emitter.module.types) { arg_names[index] = emit_pack_c_record_arg( emitter, instructions, arg, target.param_types[index], instruction_index, index, ) } } result_abi := C_Record_ABI{} if target.calling_convention == .C { result_abi = c_record_abi(target.result, &emitter.module.types) } if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, " %%abi_result_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(target.result, &emitter.module.types), result_abi.alignment) strings.write_string(&emitter.builder, " ") } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { fmt.sbprintf(&emitter.builder, " %%abi_result%d = ", instruction_index) } else if !types.is_void(instruction.type) { fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index) } else { strings.write_string(&emitter.builder, " ") } strings.write_string(&emitter.builder, "call ") if target.calling_convention == .Brolang { strings.write_string(&emitter.builder, "fastcc ") } emit_function_result(&emitter.builder, target, &emitter.module.types) if target.variadic { strings.write_string(&emitter.builder, " (") wrote_type := false if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, "ptr") wrote_type = true } for param_type, index in target.param_types { if wrote_type || index > 0 { strings.write_string(&emitter.builder, ", ") } strings.write_string(&emitter.builder, c_abi_param_type(param_type, &emitter.module.types)) wrote_type = true } if wrote_type { strings.write_string(&emitter.builder, ", ") } strings.write_string(&emitter.builder, "...)") } fmt.sbprintf(&emitter.builder, " @%s(", target.link_name) wrote_arg := false if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, "ptr sret(%s) align %d %%abi_result_slot%d", llvm_type(target.result, &emitter.module.types), result_abi.alignment, instruction_index) wrote_arg = true } for arg, index in instruction.args { if wrote_arg { strings.write_string(&emitter.builder, ", ") } arg_type := target.param_types[index] if index < len(target.param_types) else instructions[arg].type fixed := index < len(target.param_types) if target.calling_convention == .C && fixed && types.is_record(arg_type, &emitter.module.types) { fmt.sbprintf(&emitter.builder, "%s %s", c_abi_param_type(arg_type, &emitter.module.types), arg_names[index]) } else { fmt.sbprintf(&emitter.builder, "%s ", llvm_type(arg_type, &emitter.module.types)) if target.calling_convention == .C && fixed { extension := c_abi_extension(arg_type, &emitter.module.types) if len(extension) > 0 { fmt.sbprintf(&emitter.builder, "%s ", extension) } } write_operand(&emitter.builder, instructions, arg, arg_type, &emitter.module.types) } wrote_arg = true } strings.write_string(&emitter.builder, ")\n") if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, " %%v%d = load %s, ptr %%abi_result_slot%d\n", instruction_index, llvm_type(target.result, &emitter.module.types), instruction_index) } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { emit_unpack_c_record( emitter, target.result, c_abi_result_type(target.result, &emitter.module.types), fmt.tprintf("%%abi_result%d", instruction_index), fmt.tprintf("%%v%d", instruction_index), 100000+instruction_index, ) } case .Not: if !valid_value(instructions, instruction.a, types.BOOL, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid '!' operand") continue } fmt.sbprintf(&emitter.builder, " %%v%d = xor i1 ", instruction_index) write_operand(&emitter.builder, instructions, instruction.a, types.BOOL, &emitter.module.types) strings.write_string(&emitter.builder, ", true\n") case .Compare: operand_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID if !valid_value(instructions, instruction.a, operand_type, &emitter.module.types) || !valid_value(instructions, instruction.b, operand_type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid comparison operand") continue } predicate := ir.Compare_Predicate(instruction.integer) type_name := llvm_type(operand_type, &emitter.module.types) if types.is_float(operand_type, emitter.module.target) { fmt.sbprintf(&emitter.builder, " %%v%d = fcmp %s %s ", instruction_index, float_predicate(predicate), type_name) } else { fmt.sbprintf(&emitter.builder, " %%v%d = icmp %s %s ", instruction_index, integer_predicate(predicate, types.is_signed(operand_type, emitter.module.target)), type_name) } write_operand(&emitter.builder, instructions, instruction.a, operand_type, &emitter.module.types) strings.write_string(&emitter.builder, ", ") write_operand(&emitter.builder, instructions, instruction.b, operand_type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") case .Label: fmt.sbprintf(&emitter.builder, "bro_block_%d:\n", instruction.integer) case .Br: fmt.sbprintf(&emitter.builder, " br label %%bro_block_%d\n", instruction.integer) case .Cond_Br: if !valid_value(instructions, instruction.a, types.BOOL, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " br label %%bro_block_%d\n", u32(instruction.target)) continue } strings.write_string(&emitter.builder, " br i1 ") write_operand(&emitter.builder, instructions, instruction.a, types.BOOL, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", label %%bro_block_%d, label %%bro_block_%d\n", instruction.integer, u32(instruction.target)) case .Trap: message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source") emit_trap_call(emitter, message) case .Return: if global_initializer { return_value = instruction.a continue } result_abi := c_record_abi(function.result, &emitter.module.types) if function.calling_convention == .C else C_Record_ABI{} if result_abi.kind == .Indirect { fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(function.result, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr %s\n ret void\n", sret_name) } else if result_abi.kind != .None && result_abi.kind != .Homogeneous_Float { abi_type := c_abi_result_type(function.result, &emitter.module.types) temp_alignment := max(result_abi.alignment, 8) fmt.sbprintf(&emitter.builder, " %%abi_return_value_slot%d = alloca %s, align %d\n", instruction_index, llvm_type(function.result, &emitter.module.types), result_abi.alignment) fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(function.result, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr %%abi_return_value_slot%d\n", instruction_index) fmt.sbprintf(&emitter.builder, " %%abi_return_slot%d = alloca %s, align %d\n", instruction_index, abi_type, temp_alignment) fmt.sbprintf(&emitter.builder, " store %s zeroinitializer, ptr %%abi_return_slot%d\n", abi_type, instruction_index) fmt.sbprintf( &emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr align %d %%abi_return_slot%d, ptr align %d %%abi_return_value_slot%d, i64 %d, i1 false)\n", temp_alignment, instruction_index, result_abi.alignment, instruction_index, result_abi.size, ) fmt.sbprintf(&emitter.builder, " %%abi_return%d = load %s, ptr %%abi_return_slot%d\n ret %s %%abi_return%d\n", instruction_index, abi_type, instruction_index, abi_type, instruction_index) } else { fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types)) write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types) strings.write_string(&emitter.builder, "\n") } after_return = true case .Return_Void: if global_initializer { continue } if function.is_main { strings.write_string(&emitter.builder, " ret i32 0\n") } else { strings.write_string(&emitter.builder, " ret void\n") } after_return = true } } return return_value } emit_globals :: proc(emitter: ^Emitter) { for global, global_id in emitter.module.globals { if global.external { if global.problematic { continue } duplicate := false for previous in emitter.module.globals[:global_id] { if previous.external && !previous.problematic && previous.link_name == global.link_name { duplicate = true break } } if duplicate { continue } fmt.sbprintf( &emitter.builder, "@%s = external %s %s\n", global.link_name, "global" if global.writable else "constant", llvm_type(global.type, &emitter.module.types), ) } else if global.is_static { fmt.sbprintf( &emitter.builder, "@bro.g.%d = internal constant %s ", global_id, llvm_type(global.type, &emitter.module.types), ) write_constant(&emitter.builder, global.static_value, global.type, &emitter.module.types) strings.write_string(&emitter.builder, "\n") } else { fmt.sbprintf( &emitter.builder, "@bro.g.%d = internal global %s zeroinitializer\n@bro.gstate.%d = internal global i8 0\n", global_id, llvm_type(global.type, &emitter.module.types), global_id, ) } } strings.write_string(&emitter.builder, "\n") } emit_types :: proc(emitter: ^Emitter) { for item, index in emitter.module.types.nodes { if item.kind != .Struct && item.kind != .Union { continue } id := types.DYNAMIC_START+types.Type(index) fmt.sbprintf(&emitter.builder, "%%bro.type.%d = type ", id) if item.opaque { strings.write_string(&emitter.builder, "opaque\n") continue } if item.kind == .Union { fields := types.fields_for(&emitter.module.types, id) carrier := types.INVALID carrier_size: u64 carrier_alignment := 0 for field in fields { field_alignment := types.alignment_of(field.type, &emitter.module.types, emitter.module.target) field_size := types.size(field.type, &emitter.module.types, emitter.module.target) if field_alignment > carrier_alignment || (field_alignment == carrier_alignment && field_size > carrier_size) { carrier = field.type carrier_size = field_size carrier_alignment = field_alignment } } total_size := types.size(id, &emitter.module.types, emitter.module.target) if !types.is_valid(carrier) { fmt.sbprintf(&emitter.builder, "[%d x i8]\n", total_size) continue } strings.write_string(&emitter.builder, "{ ") strings.write_string(&emitter.builder, llvm_type(carrier, &emitter.module.types)) if carrier_size < total_size { fmt.sbprintf(&emitter.builder, ", [%d x i8]", total_size-carrier_size) } strings.write_string(&emitter.builder, " }\n") continue } strings.write_string(&emitter.builder, "{ ") for field, field_index in types.fields_for(&emitter.module.types, id) { if field_index > 0 { strings.write_string(&emitter.builder, ", ") } strings.write_string(&emitter.builder, llvm_type(field.type, &emitter.module.types)) } strings.write_string(&emitter.builder, " }\n") } if len(emitter.module.types.nodes) > 0 { strings.write_string(&emitter.builder, "\n") } } emit_strings :: proc(emitter: ^Emitter) { for value, id in emitter.module.strings { fmt.sbprintf( &emitter.builder, "@bro.str.%d = private unnamed_addr constant [%d x i8] c\"", id, len(value)+1, ) emit_escaped_bytes(&emitter.builder, value) strings.write_string(&emitter.builder, "\\00\"\n") } if len(emitter.module.strings) > 0 { strings.write_string(&emitter.builder, "\n") } } emit_global_accessors :: proc(emitter: ^Emitter) { placeholder_function := ir.Function{result=types.I64} for global, global_id in emitter.module.globals { if global.is_static || global.external { continue } type_name := llvm_type(global.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, "define internal %s @bro.get.%d() ", type_name, global_id) strings.write_string(&emitter.builder, "{\nentry:\n") fmt.sbprintf( &emitter.builder, " %%state = load i8, ptr @bro.gstate.%d\n %%done = icmp eq i8 %%state, 2\n br i1 %%done, label %%ready, label %%check\n", global_id, ) strings.write_string(&emitter.builder, "check:\n %visiting = icmp eq i8 %state, 1\n br i1 %visiting, label %cycle, label %initialize\ncycle:\n") message_text := fmt.aprintf( "runtime trap: global initialization cycle involving '%s'", symbol.resolve(emitter.symbols, global.name), allocator=emitter.allocator, ) message := register_message(emitter, message_text) delete(message_text, emitter.allocator) emit_trap_call(emitter, message) strings.write_string(&emitter.builder, " unreachable\ninitialize:\n") fmt.sbprintf(&emitter.builder, " store i8 1, ptr @bro.gstate.%d\n", global_id) placeholder_function.result = global.type value := emit_instruction_stream(emitter, global.initializer, placeholder_function, true) fmt.sbprintf(&emitter.builder, " store %s ", type_name) write_operand(&emitter.builder, global.initializer, value, global.type, &emitter.module.types) fmt.sbprintf(&emitter.builder, ", ptr @bro.g.%d\n", global_id) fmt.sbprintf(&emitter.builder, " store i8 2, ptr @bro.gstate.%d\n", global_id) fmt.sbprintf(&emitter.builder, " ret %s ", type_name) write_operand(&emitter.builder, global.initializer, value, global.type, &emitter.module.types) strings.write_string(&emitter.builder, "\nready:\n") fmt.sbprintf(&emitter.builder, " %%value = load %s, ptr @bro.g.%d\n ret %s %%value\n}\n\n", type_name, global_id, type_name) } } emit_constructor :: proc(emitter: ^Emitter) { count := 0 for global in emitter.module.globals { if !global.is_static && !global.external && !global.problematic { count += 1 } } if count == 0 { return } strings.write_string( &emitter.builder, "@llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @bro.init, ptr null }]\n\n", ) strings.write_string(&emitter.builder, "define internal void @bro.init() {\nentry:\n") for global, global_id in emitter.module.globals { if !global.is_static && !global.external && !global.problematic { fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type, &emitter.module.types), global_id) } } strings.write_string(&emitter.builder, " ret void\n}\n\n") } emit_functions :: proc(emitter: ^Emitter) { for function, function_index in emitter.module.functions { if function.implementation == .Declaration { duplicate := false for previous in emitter.module.functions[:function_index] { if previous.link_name == function.link_name { duplicate = true break } } if duplicate { continue } } if function.implementation == .Declaration { strings.write_string(&emitter.builder, "declare ") } else { strings.write_string(&emitter.builder, "define ") if function.linkage == .Internal { strings.write_string(&emitter.builder, "internal ") } } if function.calling_convention == .Brolang { strings.write_string(&emitter.builder, "fastcc ") } emit_function_result(&emitter.builder, function, &emitter.module.types) fmt.sbprintf(&emitter.builder, " @%s(", function.link_name) result_abi := c_record_abi(function.result, &emitter.module.types) if function.calling_convention == .C else C_Record_ABI{} wrote_param := false if result_abi.kind == .Indirect { fmt.sbprintf( &emitter.builder, "ptr sret(%s) align %d", llvm_type(function.result, &emitter.module.types), result_abi.alignment, ) if function.implementation != .Declaration { strings.write_string(&emitter.builder, " %abi_sret") } wrote_param = true } for param_type, index in function.param_types { if wrote_param || index > 0 { strings.write_string(&emitter.builder, ", ") } type_name := c_abi_param_type(param_type, &emitter.module.types) if function.calling_convention == .C else llvm_type(param_type, &emitter.module.types) fmt.sbprintf(&emitter.builder, "%s", type_name) if function.calling_convention == .C && !types.is_record(param_type, &emitter.module.types) { extension := c_abi_extension(param_type, &emitter.module.types) if len(extension) > 0 { fmt.sbprintf(&emitter.builder, " %s", extension) } } if function.implementation != .Declaration { if function.calling_convention == .C && types.is_record(param_type, &emitter.module.types) { fmt.sbprintf(&emitter.builder, " %%abi_p%d", index) } else { fmt.sbprintf(&emitter.builder, " %%v%d", index) } } wrote_param = true } if function.variadic { if len(function.param_types) > 0 { strings.write_string(&emitter.builder, ", ") } strings.write_string(&emitter.builder, "...") } if function.implementation == .Declaration { strings.write_string(&emitter.builder, ")\n\n") continue } strings.write_string(&emitter.builder, ") {\nentry:\n") emit_entry_allocas(emitter, function.instructions) if function.calling_convention == .C { for param_type, index in function.param_types { if !types.is_record(param_type, &emitter.module.types) { continue } emit_unpack_c_record( emitter, param_type, c_abi_param_type(param_type, &emitter.module.types), fmt.tprintf("%%abi_p%d", index), fmt.tprintf("%%v%d", index), 200000+index, ) } } _ = emit_instruction_stream(emitter, function.instructions, function, sret_name="%abi_sret") strings.write_string(&emitter.builder, "}\n\n") } } emit_escaped_bytes :: proc(builder: ^strings.Builder, text: string) { for value in transmute([]byte)text { if value >= 32 && value <= 126 && value != '\\' && value != '"' { strings.write_byte(builder, value) } else { fmt.sbprintf(builder, "\\%02X", value) } } } emit_messages :: proc(emitter: ^Emitter) { for message, message_id in emitter.messages { fmt.sbprintf(&emitter.builder, "@bro.msg.%d = private unnamed_addr constant [%d x i8] c\"", message_id, len(message.text)) emit_escaped_bytes(&emitter.builder, message.text) strings.write_string(&emitter.builder, "\"\n") } strings.write_string(&emitter.builder, "\n") } emit_declarations :: proc(emitter: ^Emitter) { strings.write_string(&emitter.builder, "declare i64 @write(i32, ptr, i64)\ndeclare void @llvm.trap()\ndeclare void @llvm.memcpy.p0.p0.i64(ptr, ptr, i64, i1 immarg)\ndeclare void @llvm.memset.p0.i64(ptr, i8, i64, i1 immarg)\n") widths := [?]int{8, 16, 32, 64} overflow_intrinsics := [?]string{"sadd", "uadd", "ssub", "usub", "smul", "umul"} for bits in widths { for name in overflow_intrinsics { strings.write_string(&emitter.builder, "declare { i") fmt.sbprintf(&emitter.builder, "%d", bits) strings.write_string(&emitter.builder, ", i1 } @llvm.") strings.write_string(&emitter.builder, name) strings.write_string(&emitter.builder, ".with.overflow.i") fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits) } } strings.write_string( &emitter.builder, "\ndefine internal void @bro.trap(ptr %message, i64 %length) noreturn {\nentry:\n %written = call i64 @write(i32 2, ptr %message, i64 %length)\n call void @llvm.trap()\n unreachable\n}\n\n", ) } emit :: proc( module: ^ir.Module, diagnostics: ^source.Diagnostics, symbols: ^symbol.Table, allocator := context.allocator, ) -> string { emitter := Emitter{ module=module, diagnostics=diagnostics, symbols=symbols, builder=strings.builder_make(allocator), allocator=allocator, } emitter.messages.allocator = allocator defer { for message in emitter.messages { delete(message.text, allocator) } delete(emitter.messages) strings.builder_destroy(&emitter.builder) } strings.write_string(&emitter.builder, "; generated by brolang\n") fmt.sbprintf(&emitter.builder, "target datalayout = \"%s\"\n", target.llvm_data_layout(module.target)) fmt.sbprintf(&emitter.builder, "target triple = \"%s\"\n\n", target.llvm_triple(module.target)) emit_types(&emitter) emit_strings(&emitter) emit_globals(&emitter) emit_constructor(&emitter) emit_global_accessors(&emitter) emit_functions(&emitter) emit_messages(&emitter) emit_declarations(&emitter) return fmt.aprintf("%s", strings.to_string(emitter.builder), allocator=allocator) }