c interop type foundation

This commit is contained in:
2026-06-12 18:10:52 +02:00
parent d2f0d16795
commit 4e860b033e
27 changed files with 3523 additions and 380 deletions
+633 -71
View File
@@ -3,6 +3,7 @@ package llvm
import "../ir"
import "../source"
import "../symbol"
import "../target"
import "../types"
import "core:fmt"
import "core:mem"
@@ -21,11 +22,33 @@ Emitter :: struct {
allocator: mem.Allocator,
}
llvm_type :: proc(value: types.Type) -> string {
if value.kind == .Void {
llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
if types.is_void(value) {
return "void"
}
switch value.bits {
#partial switch types.kind(value, store) {
case .Pointer:
return "ptr"
case .Slice:
return "{ ptr, i64 }"
case .Array:
item, _ := types.node(store, value)
return fmt.tprintf("[%d x %s]", types.physical_count(value, store), llvm_type(item.child, store))
case .Optional:
item, _ := types.node(store, value)
if types.is_pointer(item.child, store) {
return "ptr"
}
return fmt.tprintf("{{ i1, %s }}", llvm_type(item.child, store))
case .Struct:
return fmt.tprintf("%%bro.type.%d", value)
}
selected := store.selected if store != nil else target.DEFAULT
repr := types.representation(value, 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"
@@ -33,15 +56,37 @@ llvm_type :: proc(value: types.Type) -> string {
}
}
function_result_type :: proc(function: ir.Function) -> string {
function_result_type :: proc(function: ir.Function, store: ^types.Store) -> string {
if function.is_main {
return "i32"
}
return llvm_type(function.result)
return llvm_type(function.result, store)
}
sentinel :: proc(value_type: types.Type) -> i64 {
switch value_type.bits {
c_abi_extension :: proc(value: types.Type, selected: target.Target) -> string {
if !types.is_concrete_integer(value) {
return ""
}
switch target.c_integer_extension(selected, types.bits(value, selected), types.is_signed(value, 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.selected)
if len(extension) > 0 {
fmt.sbprintf(builder, "%s ", extension)
}
}
strings.write_string(builder, function_result_type(function, store))
}
sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 {
switch types.bits(value_type, selected) {
case 8: return -86
case 16: return -21846
case 32: return -1431655766
@@ -53,29 +98,88 @@ valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: ir.Ins
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) -> bool {
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_concrete_integer(expected) ||
!types.is_runtime_value(expected, store) ||
!types.equal(instructions[value_id].type, expected) {
return false
}
switch instructions[value_id].op {
case .Param, .Const, .Load_Global, .Load, .Widen, .Neg_Checked, .Add_Checked, .Call:
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Widen, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true
case .Alloca, .Store, .Trap, .Return, .Return_Void:
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Trap, .Return, .Return_Void:
return false
}
return false
}
write_operand :: proc(builder: ^strings.Builder, instructions: []ir.Instruction, value_id: ir.Instruction_Id, expected: types.Type) {
if !valid_value(instructions, value_id, expected) {
fmt.sbprintf(builder, "%d", sentinel(expected))
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) {
if !types.is_concrete_scalar(value_type) {
strings.write_string(builder, "zeroinitializer")
return
}
selected := store.selected if store != nil else target.DEFAULT
if types.is_float(value_type, selected) {
text := ""
if types.bits(value_type, selected) == 32 {
bits := u32(value)
number := transmute(f32)bits
text = fmt.tprintf("%.9g", number)
} else {
number := transmute(f64)value
text = fmt.tprintf("%.17g", number)
}
strings.write_string(builder, text)
if !strings.contains(text, ".") && !strings.contains(text, "e") && !strings.contains(text, "E") {
strings.write_string(builder, ".0")
}
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.selected), expected, store)
return
}
value := instructions[value_id]
if value.op == .Const {
fmt.sbprintf(builder, "%d", value.integer)
write_constant(builder, value.integer, expected, store)
} else {
fmt.sbprintf(builder, "%%v%d", value_id)
}
@@ -126,24 +230,59 @@ emit_trap_call :: proc(emitter: ^Emitter, message_id: int) {
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_concrete_integer(instruction.type) {
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.target),
)
return
}
fmt.sbprintf(
&emitter.builder,
" %%v%d = add %s 0, %d\n",
" %%v%d = select i1 true, %s ",
instruction_id,
llvm_type(instruction.type),
sentinel(instruction.type),
llvm_type(instruction.type, &emitter.module.types),
)
write_constant(&emitter.builder, sentinel(instruction.type, 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.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) {
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, ", ")
}
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index]))
write_operand(builder, instructions, arg, param_types[index])
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index], store))
if c_abi {
extension := c_abi_extension(param_types[index], store.selected)
if len(extension) > 0 {
fmt.sbprintf(builder, "%s ", extension)
}
}
write_operand(builder, instructions, arg, param_types[index], store)
}
}
@@ -163,6 +302,102 @@ emit_instruction_stream :: proc(
}
switch instruction.op {
case .Param, .Const:
case .String:
string_id := int(instruction.integer)
if string_id < 0 || string_id >= len(emitter.module.strings) ||
!types.is_slice(instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid string literal")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(
&emitter.builder,
" %%string_ptr%d = insertvalue %s poison, ptr @bro.str.%d, 0\n",
instruction_index, type_name, string_id,
)
fmt.sbprintf(
&emitter.builder,
" %%v%d = insertvalue %s %%string_ptr%d, i64 %d, 1\n",
instruction_index, type_name, instruction_index, len(emitter.module.strings[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 {
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)
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
}
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) {
@@ -179,7 +414,7 @@ emit_instruction_stream :: proc(
&emitter.builder,
" %%v%d = load %s, ptr @bro.g.%d\n",
instruction_index,
llvm_type(global.type),
llvm_type(global.type, &emitter.module.types),
global_id,
)
} else {
@@ -187,20 +422,115 @@ emit_instruction_stream :: proc(
&emitter.builder,
" %%v%d = call %s @bro.get.%d()\n",
instruction_index,
llvm_type(global.type),
llvm_type(global.type, &emitter.module.types),
global_id,
)
}
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
}
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_concrete_integer(instruction.type) {
if !types.is_runtime_value(instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid allocation type")
continue
}
fmt.sbprintf(&emitter.builder, " %%v%d = alloca %s\n", instruction_index, llvm_type(instruction.type))
case .Load:
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) ||
instructions[instruction.a].op != .Alloca ||
!types.equal(instructions[instruction.a].type, instruction.type) {
!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.node(&emitter.module.types, container.type)
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 {
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(container.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
}
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
}
@@ -208,42 +538,191 @@ emit_instruction_stream :: proc(
&emitter.builder,
" %%v%d = load %s, ptr %%v%d\n",
instruction_index,
llvm_type(instruction.type),
llvm_type(instruction.type, &emitter.module.types),
instruction.a,
)
case .Store:
if !valid_instruction(instructions, instruction.a) ||
instructions[instruction.a].op != .Alloca ||
!types.equal(instructions[instruction.a].type, instruction.type) ||
!valid_value(instructions, instruction.b, instruction.type) {
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))
write_operand(&emitter.builder, instructions, instruction.b, instruction.type)
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 .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.node(&emitter.module.types, container.type)
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 {
fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(container.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
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 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 .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 .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.is_concrete_integer(instructions[instruction.a].type) ||
!types.is_concrete_integer(instruction.type) ||
instructions[instruction.a].type.bits >= instruction.type.bits {
!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
fmt.sbprintf(&emitter.builder, " %%v%d = sext %s ", instruction_index, llvm_type(from_type))
write_operand(&emitter.builder, instructions, instruction.a, from_type)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.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 .Neg_Checked:
if !valid_value(instructions, instruction.a, instruction.type) {
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)
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)
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, "{ ")
@@ -263,18 +742,27 @@ emit_instruction_stream :: proc(
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) ||
!valid_value(instructions, instruction.b, instruction.type) {
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
}
type_name := llvm_type(instruction.type)
type_name := llvm_type(instruction.type, &emitter.module.types)
if types.is_float(instruction.type, emitter.module.target) {
fmt.sbprintf(&emitter.builder, " %%v%d = fadd %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")
continue
}
intrinsic := "uadd" if types.is_unsigned(instruction.type, emitter.module.target) else "sadd"
fmt.sbprintf(&emitter.builder, " %%pair%d = call ", instruction_index)
strings.write_string(&emitter.builder, "{ ")
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.sadd.with.overflow.%s(%s ", type_name, type_name, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type)
fmt.sbprintf(&emitter.builder, "%s, i1 } @llvm.%s.with.overflow.%s(%s ", type_name, intrinsic, 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)
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, "{ ")
@@ -290,9 +778,20 @@ emit_instruction_stream :: proc(
instruction_index,
)
fmt.sbprintf(&emitter.builder, "overflow_trap%d:\n", instruction_index)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "signed integer addition overflow")
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "integer addition overflow")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\noverflow_continue%d:\n", instruction_index)
case .Pointer_Add:
item, ok := types.node(&emitter.module.types, instruction.type)
if !ok || item.kind != .Pointer || !item.many ||
!valid_value(instructions, instruction.a, instruction.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(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 || int(function_id) >= len(emitter.module.functions) {
@@ -303,7 +802,7 @@ emit_instruction_stream :: proc(
valid_args := len(instruction.args) == len(target.param_types)
if valid_args {
for arg, index in instruction.args {
if !valid_value(instructions, arg, target.param_types[index]) {
if !valid_value(instructions, arg, target.param_types[index], &emitter.module.types) {
valid_args = false
break
}
@@ -317,7 +816,7 @@ emit_instruction_stream :: proc(
emit_recovery_value(emitter, instruction_index, instruction, "invalid function call operands")
continue
}
if instruction.type.kind != .Void {
if !types.is_void(instruction.type) {
fmt.sbprintf(&emitter.builder, " %%v%d = ", instruction_index)
} else {
strings.write_string(&emitter.builder, " ")
@@ -326,8 +825,12 @@ emit_instruction_stream :: proc(
if target.calling_convention == .Brolang {
strings.write_string(&emitter.builder, "fastcc ")
}
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(target), target.link_name)
emit_call_args(&emitter.builder, instructions, instruction.args, target.param_types)
emit_function_result(&emitter.builder, target, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " @%s(", target.link_name)
emit_call_args(
&emitter.builder, instructions, instruction.args, target.param_types,
&emitter.module.types, target.calling_convention == .C,
)
strings.write_string(&emitter.builder, ")\n")
case .Trap:
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
@@ -337,8 +840,8 @@ emit_instruction_stream :: proc(
return_value = instruction.a
continue
}
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function))
write_operand(&emitter.builder, instructions, instruction.a, function.result)
fmt.sbprintf(&emitter.builder, " ret %s ", function_result_type(function, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, function.result, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
after_return = true
case .Return_Void:
@@ -361,17 +864,18 @@ emit_globals :: proc(emitter: ^Emitter) {
if global.is_static {
fmt.sbprintf(
&emitter.builder,
"@bro.g.%d = internal constant %s %d\n",
"@bro.g.%d = internal constant %s ",
global_id,
llvm_type(global.type),
global.static_value,
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 0\n@bro.gstate.%d = internal global i8 0\n",
"@bro.g.%d = internal global %s zeroinitializer\n@bro.gstate.%d = internal global i8 0\n",
global_id,
llvm_type(global.type),
llvm_type(global.type, &emitter.module.types),
global_id,
)
}
@@ -379,13 +883,53 @@ emit_globals :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, "\n")
}
emit_types :: proc(emitter: ^Emitter) {
for item, index in emitter.module.types.nodes {
if item.kind != .Struct {
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
}
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 {
continue
}
type_name := llvm_type(global.type)
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(
@@ -407,11 +951,11 @@ emit_global_accessors :: proc(emitter: ^Emitter) {
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)
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)
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)
}
@@ -434,7 +978,7 @@ emit_constructor :: proc(emitter: ^Emitter) {
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.problematic {
fmt.sbprintf(&emitter.builder, " %%g%d = call %s @bro.get.%d()\n", global_id, llvm_type(global.type), global_id)
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")
@@ -453,15 +997,25 @@ emit_functions :: proc(emitter: ^Emitter) {
if function.calling_convention == .Brolang {
strings.write_string(&emitter.builder, "fastcc ")
}
fmt.sbprintf(&emitter.builder, "%s @%s(", function_result_type(function), function.link_name)
emit_function_result(&emitter.builder, function, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " @%s(", function.link_name)
for param_type, index in function.param_types {
if index > 0 {
strings.write_string(&emitter.builder, ", ")
}
if function.implementation == .Declaration {
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type))
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types))
} else {
fmt.sbprintf(&emitter.builder, "%s %%v%d", llvm_type(param_type), index)
fmt.sbprintf(&emitter.builder, "%s", llvm_type(param_type, &emitter.module.types))
}
if function.calling_convention == .C {
extension := c_abi_extension(param_type, emitter.module.target)
if len(extension) > 0 {
fmt.sbprintf(&emitter.builder, " %s", extension)
}
}
if function.implementation != .Declaration {
fmt.sbprintf(&emitter.builder, " %%v%d", index)
}
}
if function.implementation == .Declaration {
@@ -503,6 +1057,10 @@ emit_declarations :: proc(emitter: ^Emitter) {
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
strings.write_string(&emitter.builder, "declare { i")
fmt.sbprintf(&emitter.builder, "%d", bits)
strings.write_string(&emitter.builder, ", i1 } @llvm.uadd.with.overflow.i")
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
strings.write_string(&emitter.builder, "declare { i")
fmt.sbprintf(&emitter.builder, "%d", bits)
strings.write_string(&emitter.builder, ", i1 } @llvm.ssub.with.overflow.i")
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
}
@@ -534,7 +1092,11 @@ emit :: proc(
strings.builder_destroy(&emitter.builder)
}
strings.write_string(&emitter.builder, "; generated by brolang\n\n")
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)