2120 lines
94 KiB
Odin
2120 lines
94 KiB
Odin
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..<int(item.count) {
|
|
if !hfa_walk(item.child, store, scalar, count, depth+1) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
if item.kind != .Struct {
|
|
return false
|
|
}
|
|
for field in types.fields_for(store, value) {
|
|
if !hfa_walk(field.type, store, scalar, count, depth+1) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
c_record_abi :: proc(value: types.Type, store: ^types.Store) -> 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, .Union_Tag, .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
|
|
// Set after any terminator (`ret`, `br`, conditional `br`). Code reachable
|
|
// only by falling off a terminator is dead; it needs a fresh label to form a
|
|
// well-formed basic block — unless the next instruction is already a `.Label`,
|
|
// which opens its own block (the normal terminator-then-label sequence).
|
|
after_terminator := false
|
|
for instruction, instruction_index in instructions {
|
|
instruction_id := ir.instruction_id(instruction_index)
|
|
if after_terminator {
|
|
if instruction.op != .Label {
|
|
fmt.sbprintf(&emitter.builder, "recover_after_return_%d:\n", instruction_index)
|
|
}
|
|
after_terminator = 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)
|
|
// A void-payload variant carries no value (`T{ variant }`): only the tag
|
|
// is stored, so there is no operand to validate or write.
|
|
void_payload := field_index >= 0 && field_index < len(fields) && types.is_void(fields[field_index].type)
|
|
if field_index < 0 || field_index >= len(fields) ||
|
|
(!void_payload && !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)
|
|
// A tagged union writes the discriminant (the tag enum member matching the
|
|
// active variant's name) at offset 0, then the payload after it; untagged
|
|
// unions write the payload at offset 0.
|
|
payload_ptr := fmt.tprintf("%%union_slot%d", instruction_index)
|
|
tag_enum := types.union_tag_enum(instruction.type, &emitter.module.types)
|
|
if types.is_valid(tag_enum) {
|
|
tag_value: i128 = 0
|
|
for member in types.enum_members_for(&emitter.module.types, tag_enum) {
|
|
if member.name == fields[field_index].name {
|
|
tag_value = member.value
|
|
break
|
|
}
|
|
}
|
|
fmt.sbprintf(&emitter.builder, " store %s %d, ptr %%union_slot%d\n", llvm_type(tag_enum, &emitter.module.types), tag_value, instruction_index)
|
|
if !void_payload {
|
|
offset := types.union_payload_offset(instruction.type, &emitter.module.types, emitter.module.target)
|
|
fmt.sbprintf(&emitter.builder, " %%union_payload%d = getelementptr i8, ptr %%union_slot%d, i64 %d\n", instruction_index, instruction_index, offset)
|
|
payload_ptr = fmt.tprintf("%%union_payload%d", instruction_index)
|
|
}
|
|
}
|
|
if !void_payload {
|
|
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 %s\n", payload_ptr)
|
|
}
|
|
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 %d\n", instruction_index, instruction.a, types.union_payload_offset(base_type, &emitter.module.types, emitter.module.target))
|
|
} 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 .Union_Tag:
|
|
// `instruction.a` is the address of a tagged union; the discriminant lives at
|
|
// offset 0, so load the tag enum (`instruction.type`) straight from it.
|
|
if !valid_instruction(instructions, instruction.a) {
|
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid union tag base")
|
|
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)
|
|
after_terminator = true
|
|
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))
|
|
after_terminator = true
|
|
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))
|
|
after_terminator = true
|
|
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_terminator = 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_terminator = 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
|
|
}
|
|
// A tagged union lays out `{ tag, [pad], carrier, [pad] }`; the explicit i8
|
|
// padding makes the LLVM type's size and field offsets match the byte offsets
|
|
// used by construction and field access. Untagged unions have offset 0 and no tag.
|
|
payload_offset := types.union_payload_offset(id, &emitter.module.types, emitter.module.target)
|
|
strings.write_string(&emitter.builder, "{ ")
|
|
if types.is_tagged_union(id, &emitter.module.types) {
|
|
tag_type := types.union_tag_enum(id, &emitter.module.types)
|
|
tag_size := types.size(tag_type, &emitter.module.types, emitter.module.target)
|
|
strings.write_string(&emitter.builder, llvm_type(tag_type, &emitter.module.types))
|
|
if payload_offset > tag_size {
|
|
fmt.sbprintf(&emitter.builder, ", [%d x i8]", payload_offset-tag_size)
|
|
}
|
|
strings.write_string(&emitter.builder, ", ")
|
|
}
|
|
strings.write_string(&emitter.builder, llvm_type(carrier, &emitter.module.types))
|
|
used := payload_offset + carrier_size
|
|
if used < total_size {
|
|
fmt.sbprintf(&emitter.builder, ", [%d x i8]", total_size-used)
|
|
}
|
|
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)
|
|
}
|