feat: emit LLVM IR and build native executables

This commit is contained in:
2026-10-25 02:05:26 +01:00
parent c01d935a7b
commit 45359c0cdf
10 changed files with 1267 additions and 9 deletions
+221
View File
@@ -0,0 +1,221 @@
import "@std/io"
import "@source/ir"
@hide
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
output :: state.output
type :: Type{ kind = instruction.type, shape = instruction.array_type }
result := fresh(state)
match instruction.op {
.constant: {
if (instruction.type == .void) return
try io.write_all(output, " store ")
try write_type(output, state.module, type)
try io.write_all(output, " ")
try write_constant(output, state.module, instruction.constant)
try io.print(", ptr %v{}\n", output, {u32(instruction.destination)})
return
}
.load_local: {
try check_local(state, instruction.slot, true, instruction.span)
try io.print(" %r{} = load ", output, {result})
try write_type(output, state.module, type)
try io.print(", ptr %l{}\n", output, {instruction.slot})
}
.store_local, .assign_local: {
if (instruction.op == .assign_local) try check_local(state, instruction.slot, false, instruction.span)
value :: try load(state, instruction.left)
try io.write_all(output, " store ")
try write_type(output, state.module, state.values[usize(instruction.left)])
try io.print(" %r{}, ptr %l{}\n", output, {value, instruction.slot})
if (instruction.op == .store_local) try io.print(" store i1 true, ptr %i{}\n", output, {instruction.slot})
return
}
.reset_local: {
try io.print(" store i1 false, ptr %i{}\n", output, {instruction.slot})
return
}
.load_global: {
pointer :: fresh(state)
position :: location(state.line_starts, instruction.span)
try io.print(" %r{} = call ptr @global{}(i64 {}, i64 {})\n", output, {pointer, instruction.slot, position.line, position.column})
try io.print(" %r{} = load ", output, {result})
try write_type(output, state.module, type)
try io.print(", ptr %r{}\n", output, {pointer})
}
.array_literal: {
for (0..instruction.arguments_count) |index| {
operand :: block.arguments.items[instruction.arguments_start + index]
value :: try load(state, operand)
pointer :: fresh(state)
try io.print(" %r{} = getelementptr ", output, {pointer})
try write_type(output, state.module, type)
try io.print(", ptr %v{}, i64 0, i64 {}\n store ", output, {u32(instruction.destination), index})
try write_type(output, state.module, state.values[usize(operand)])
try io.print(" %r{}, ptr %r{}\n", output, {value, pointer})
}
return
}
.index, .index_address: {
index :: try load(state, instruction.right)
base_type :: state.values[usize(instruction.left)]
shape :: state.module.arrays.items[usize(base_type.shape)]
failed :: fresh(state)
try io.print(" %r{} = icmp uge i64 %r{}, {}\n", output, {failed, index, shape.len})
try check_failure(state, failed, .index_out_of_bounds, instruction.span)
pointer :: fresh(state)
if (instruction.op == .index_address) {
base :: try load(state, instruction.left)
data :: fresh(state)
flag :: fresh(state)
address :: fresh(state)
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {data, base})
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, base})
try io.print(" %r{} = getelementptr ", output, {pointer})
try write_type(output, state.module, Type{ kind = .array, shape = base_type.shape })
try io.print(", ptr %r{}, i64 0, i64 %r{}\n", output, {data, index})
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
try io.print(" %r{} = insertvalue %address %r{}, ptr %r{}, 1\n", output, {result, address, flag})
} else {
try io.print(" %r{} = getelementptr ", output, {pointer})
try write_type(output, state.module, base_type)
try io.print(", ptr %v{}, i64 0, i64 %r{}\n %r{} = load ", output, {u32(instruction.left), index, result})
try write_type(output, state.module, type)
try io.print(", ptr %r{}\n", output, {pointer})
}
}
.address_local: {
address :: fresh(state)
try io.print(" %r{} = insertvalue %address poison, ptr %l{}, 0\n", output, {address, instruction.slot})
try io.print(" %r{} = insertvalue %address %r{}, ptr %i{}, 1\n", output, {result, address, instruction.slot})
}
.address_global: {
pointer :: fresh(state)
address :: fresh(state)
position :: location(state.line_starts, instruction.span)
try io.print(" %r{} = call ptr @global{}(i64 {}, i64 {})\n", output, {pointer, instruction.slot, position.line, position.column})
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
try io.print(" %r{} = insertvalue %address %r{}, ptr null, 1\n", output, {result, address})
}
.load_address, .store_address: {
address :: try load(state, instruction.left)
pointer :: fresh(state)
flag :: fresh(state)
position :: location(state.line_starts, instruction.span)
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {pointer, address})
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, address})
try io.print(" call void @check_init(ptr %r{}, i1 {}, i64 {}, i64 {})\n", output, {flag, instruction.op == .load_address, position.line, position.column})
if (instruction.op == .store_address) {
value :: try load(state, instruction.right)
try io.write_all(output, " store ")
try write_type(output, state.module, state.values[usize(instruction.right)])
try io.print(" %r{}, ptr %r{}\n", output, {value, pointer})
return
}
try io.print(" %r{} = load ", output, {result})
try write_type(output, state.module, type)
try io.print(", ptr %r{}\n", output, {pointer})
}
.call: {
callee :: try load(state, instruction.left)
# operands are frame snapshots; loading them before the call preserves evaluation order.
# argument registers are consecutive because load emits exactly one fresh register.
first_argument :: state.next_register
for (0..instruction.arguments_count) |index| {
_ = try load(state, block.arguments.items[instruction.arguments_start + index])
}
if (instruction.type != .void) try io.print(" %r{} = call ", output, {result})
else try io.write_all(output, " call ")
try write_type(output, state.module, type)
try io.print(" %r{}(", output, {callee})
for (0..instruction.arguments_count) |index| {
if (index != 0) try io.write_all(output, ", ")
operand :: block.arguments.items[instruction.arguments_start + index]
try write_type(output, state.module, state.values[usize(operand)])
try io.print(" %r{}", output, {first_argument + index})
}
try io.write_all(output, ")\n")
}
.negate: {
operand :: try load(state, instruction.left)
if (instruction.type == .float) {
try io.print(" %r{} = fneg double %r{}\n", output, {result, operand})
} else {
pair :: fresh(state)
overflow :: fresh(state)
try io.print(" %r{} = call {{ i64, i1 }} @llvm.ssub.with.overflow.i64(i64 0, i64 %r{})\n", output, {pair, operand})
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 1\n", output, {overflow, pair})
try check_failure(state, overflow, .integer_overflow, instruction.span)
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 0\n", output, {result, pair})
}
}
.logical_not: {
operand :: try load(state, instruction.left)
try io.print(" %r{} = xor i1 %r{}, true\n", output, {result, operand})
}
.add, .subtract, .multiply, .divide: {
left :: try load(state, instruction.left)
right :: try load(state, instruction.right)
if (instruction.type == .float) {
opcode []u8 :: match instruction.op {
.add: "fadd"
.subtract: "fsub"
.multiply: "fmul"
.divide: "fdiv"
else: unreachable
}
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
} else {
opcode []u8 :: match instruction.op {
.add: "add"
.subtract: "sub"
.multiply: "mul"
else: unreachable
}
sign []u8 :: if (instruction.type == .usize) "u" else "s"
pair :: fresh(state)
overflow :: fresh(state)
try io.print(" %r{} = call {{ i64, i1 }} @llvm.{s}{s}.with.overflow.i64(i64 %r{}, i64 %r{})\n", output, {pair, sign, opcode, left, right})
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 1\n", output, {overflow, pair})
try check_failure(state, overflow, .integer_overflow, instruction.span)
try io.print(" %r{} = extractvalue {{ i64, i1 }} %r{}, 0\n", output, {result, pair})
}
}
.equal, .not_equal, .less, .less_equal, .greater, .greater_equal: {
left :: try load(state, instruction.left)
right :: try load(state, instruction.right)
operand_type :: state.values[usize(instruction.left)]
if (operand_type.kind == .float) {
predicate []u8 :: match instruction.op {
.equal: "oeq"
.not_equal: "une"
.less: "olt"
.less_equal: "ole"
.greater: "ogt"
.greater_equal: "oge"
else: unreachable
}
try io.print(" %r{} = fcmp {s} double %r{}, %r{}\n", output, {result, predicate, left, right})
} else {
prefix []u8 := ""
if (instruction.op != .equal and instruction.op != .not_equal) {
if (operand_type.kind == .usize) prefix = "u"
else prefix = "s"
}
predicate []u8 :: match instruction.op {
.equal: "eq"
.not_equal: "ne"
.less: "lt"
.less_equal: "le"
.greater: "gt"
.greater_equal: "ge"
else: unreachable
}
try io.print(" %r{} = icmp {s}{s} ", output, {result, prefix, predicate})
try write_type(output, state.module, operand_type)
try io.print(" %r{}, %r{}\n", output, {left, right})
}
}
}
try store(state, instruction.destination, result)
}
+396
View File
@@ -0,0 +1,396 @@
import "@std/mem"
import "@std/io"
import "@std/enummap"
import "@std/arraylist"
import "@source/ir"
import "@source/diagnostics"
@hide
Type :: struct {
kind ir.Type = .void
shape ir.ArrayId = ir.no_array
}
@hide
State :: struct {
output io.Writer
module @ir.Module
line_starts []usize
values []mut Type
next_register usize = 0
}
#! writes a standalone native LLVM module; all temporary emitter storage is released before returning.
emit proc(
allocator mem.Allocator,
output io.Writer,
module @ir.Module,
diagnostic_store @diagnostics.Store,
source []u8,
path []u8,
) void ! (mem.AllocError | io.WriteError) {
line_starts := arraylist.init(usize, allocator)
defer arraylist.deinit(&line_starts)
try arraylist.append(&line_starts, usize(0))
for (source) |byte, index| {
if (byte == '\n') try arraylist.append(&line_starts, index + 1)
}
try io.write_all(output, runtime)
try io.write_all(output, "\n@path = private constant ")
try bytes_constant(output, path)
try io.write_all(output, "\n")
for (module.strings.items) |_, index| {
try io.print("@s{} = private constant ", output, {index})
try bytes_constant(output, ir.string_bytes(module, ir.StringId(index)))
try io.write_all(output, "\n")
}
for (diagnostic_store.list.items) |diagnostic, index| {
try io.print("@d{} = private constant ", output, {index})
try bytes_constant(output, enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?)
try io.write_all(output, "\n")
}
try emit_reporting(output, diagnostic_store, line_starts.items, path.len)
for (module.globals.items) |global, index| {
try io.print("@g{} = internal global ", output, {index})
storage_kind :: if (global.type == .void) ir.Type.invalid else global.type
try write_type(output, module, Type{ kind = storage_kind, shape = global.array_type })
try io.print(" zeroinitializer\n@gs{} = internal global i8 0\n", output, {index})
try emit_global(output, module, global, index)
}
for (module.arrays.items) |_, index| try emit_array_printer(output, module, ir.ArrayId(index))
for (module.functions.items) |function, index| {
values :: try mem.alloc(Type, allocator, function.value_count)
defer mem.free(allocator, values)
mem.set(values, Type{})
for (function.blocks.items) |block| {
for (block.instructions.items) |instruction| {
if (instruction.destination != ir.no_value) {
values[usize(instruction.destination)] = Type{ kind = instruction.type, shape = instruction.array_type }
}
}
}
state := State{ output = output, module = module, line_starts = line_starts.items, values = values }
try emit_function(&state, &function, index)
}
try io.write_all(output, "define i32 @main() {\nentry:\n")
if (module.entry) |entry| {
function :: module.functions.items[usize(entry)]
result :: Type{ kind = function.result, shape = function.result_array_type }
if (result.kind != .void) try io.write_all(output, " %result = ")
else try io.write_all(output, " ")
try io.write_all(output, "call ")
try write_type(output, module, result)
try io.print(" @f{}()\n", output, {u32(entry)})
if (result.kind != .void and result.kind != .invalid) {
try print_call(output, module, result, "%result")
try io.write_all(output, " call void @out(i32 1, ptr @newline, i64 1)\n")
}
try io.write_all(output, " call void @report(i64 -1)\n")
status i32 :: if (diagnostics.count(diagnostic_store) == 0) 0 else 1
try io.print(" ret i32 {}\n", output, {status})
} else {
try io.print(" call void @trap(i64 {}, i64 0, i64 0, ptr null, i64 0)\n unreachable\n", output, {u32(module.entry_diagnostic)})
}
try io.write_all(output, "}\n")
}
@hide:file
bytes_constant proc(output io.Writer, bytes []u8) void ! io.WriteError {
try io.print("[{} x i8] c\"", output, {bytes.len})
digits :: "0123456789ABCDEF"
for (bytes) |byte| {
escaped [3]u8 :: ['\\', digits[usize(divtrunc!(byte, 16))], digits[usize(rem!(byte, 16))]]
try io.write_all(output, (&escaped)[..])
}
try io.write_all(output, "\"")
}
@hide
write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteError {
match type.kind {
.int, .usize: try io.write_all(output, "i64")
.float: try io.write_all(output, "double")
.bool: try io.write_all(output, "i1")
.string: try io.write_all(output, "%string")
.function: try io.write_all(output, "ptr")
.address: try io.write_all(output, "%address")
.array: {
shape :: module.arrays.items[usize(type.shape)]
try io.print("[{} x ", output, {shape.len})
try write_type(output, module, Type{ kind = shape.element, shape = shape.element_array })
try io.write_all(output, "]")
}
.void: try io.write_all(output, "void")
# invalid storage only belongs to recovery paths that trap before reading it.
.invalid: try io.write_all(output, "i8")
}
}
@hide:file
local_type proc(function @ir.Function, index usize) Type {
type := Type{ kind = function.locals.items[index] }
if (type.kind == .void) type.kind = .invalid
if (type.kind == .array) {
for (function.array_storage.items) |storage| {
if (storage.local and usize(storage.index) == index) {
type.shape = storage.array_type
break
}
}
}
return type
}
@hide
Location :: struct {
line usize = 1
column usize = 1
}
@hide
location proc(line_starts []usize, span diagnostics.Span) Location {
offset :: usize(span.start)
line := usize(0)
end := line_starts.len
while (line + 1 < end) {
middle :: line + divtrunc!(end - line, 2)
if (line_starts[middle] <= offset) line = middle
else end = middle
}
return Location{ line = line + 1, column = offset - line_starts[line] + 1 }
}
@hide:file
emit_reporting proc(output io.Writer, diagnostic_store @diagnostics.Store, line_starts []usize, path_length usize) void ! io.WriteError {
try io.write_all(output, "define internal void @io_failure() noreturn {\nentry:\n")
try io.print(" %path_result = call i64 @write(i32 2, ptr @path, i64 {})\n", output, {path_length})
try io.write_all(output, " %message_result = call i64 @write(i32 2, ptr @write_error, i64 44)\n call void @exit(i32 2)\n unreachable\n}\n")
try io.write_all(output, "define internal void @diagnose(i64 %line, i64 %column, ptr %message, i64 %length, i1 %trap) {\nentry:\n")
try io.print(" call void @out(i32 2, ptr @path, i64 {})\n", output, {path_length})
try io.write_all(output,
` call void @out(i32 2, ptr @colon, i64 1)
` call void @print_integer(i32 2, i64 %line, i1 true)
` call void @out(i32 2, ptr @colon, i64 1)
` call void @print_integer(i32 2, i64 %column, i1 true)
` %label = select i1 %trap, ptr @runtime_trap, ptr @error
` %label_length = select i1 %trap, i64 16, i64 9
` call void @out(i32 2, ptr %label, i64 %label_length)
` call void @out(i32 2, ptr %message, i64 %length)
` call void @out(i32 2, ptr @newline, i64 1)
` ret void
`}
)
try io.write_all(output, "define internal void @report(i64 %trap) {\nentry:\n")
for (diagnostic_store.list.items) |diagnostic, index| {
position :: location(line_starts, diagnostic.span)
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, diagnostic.code)?
try io.print(" %t{} = icmp eq i64 %trap, {}\n", output, {index, index})
try io.print(" call void @diagnose(i64 {}, i64 {}, ptr @d{}, i64 {}, i1 %t{})\n", output, {
position.line, position.column, index, message.len, index,
})
}
try io.write_all(output, " ret void\n}\n")
}
@hide:file
emit_global proc(output io.Writer, module @ir.Module, global ir.Global, index usize) void ! io.WriteError {
type :: Type{ kind = global.type, shape = global.array_type }
try io.print("define internal ptr @global{}(i64 %line, i64 %column) {{\nentry:\n", output, {index})
try io.print(" %state = load i8, ptr @gs{}\n", output, {index})
try io.write_all(output,
` switch i8 %state, label %initialize [ i8 1, label %cycle i8 2, label %ready ]
`cycle:
` call void @trap(i64 -1, i64 %line, i64 %column, ptr @cycle, i64 27)
` unreachable
`initialize:
)
try io.print(" store i8 1, ptr @gs{}\n", output, {index})
if (type.kind == .void) {
try io.print(" call void @f{}()\n", output, {u32(global.initializer)})
} else {
try io.write_all(output, " %value = call ")
try write_type(output, module, type)
try io.print(" @f{}()\n store ", output, {u32(global.initializer)})
try write_type(output, module, type)
try io.print(" %value, ptr @g{}\n", output, {index})
}
try io.print(" store i8 2, ptr @gs{}\n br label %ready\nready:\n ret ptr @g{}\n}}\n", output, {index, index})
}
@hide:file
print_call proc(output io.Writer, module @ir.Module, type Type, operand []u8) void ! io.WriteError {
match type.kind {
.int, .usize: try io.print(" call void @print_integer(i32 1, i64 {s}, i1 {})\n", output, {operand, type.kind == .usize})
.float: try io.print(" call void @print_float(double {s})\n", output, {operand})
.bool: try io.print(" call void @print_bool(i1 {s})\n", output, {operand})
.string: try io.print(" call void @print_string(%string {s})\n", output, {operand})
.array: {
try io.print(" call void @print_array{}(", output, {u32(type.shape)})
try write_type(output, module, type)
try io.print(" {s})\n", output, {operand})
}
else: unreachable
}
}
@hide:file
emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void ! io.WriteError {
shape :: module.arrays.items[usize(id)]
type :: Type{ kind = .array, shape = id }
element :: Type{ kind = shape.element, shape = shape.element_array }
try io.print("define internal void @print_array{}(", output, {u32(id)})
try write_type(output, module, type)
try io.write_all(output, " %value) {\nentry:\n %storage = alloca ")
try write_type(output, module, type)
try io.write_all(output, "\n store ")
try write_type(output, module, type)
try io.write_all(output, " %value, ptr %storage\n call void @out(i32 1, ptr @open, i64 1)\n br label %loop\nloop:\n %index = phi i64 [ 0, %entry ], [ %next, %element ]\n")
try io.print(" %done = icmp eq i64 %index, {}\n", output, {shape.len})
try io.write_all(output, " br i1 %done, label %end, label %separator\nseparator:\n %first = icmp eq i64 %index, 0\n br i1 %first, label %element, label %comma\ncomma:\n call void @out(i32 1, ptr @separator, i64 2)\n br label %element\nelement:\n %pointer = getelementptr ")
try write_type(output, module, type)
try io.write_all(output, ", ptr %storage, i64 0, i64 %index\n %item = load ")
try write_type(output, module, element)
try io.write_all(output, ", ptr %pointer\n")
# functions may occur inside arrays without being printable source results.
if (element.kind != .function) try print_call(output, module, element, "%item")
else try io.write_all(output, " call void @exit(i32 2)\n")
try io.write_all(output, " %next = add i64 %index, 1\n br label %loop\nend:\n call void @out(i32 1, ptr @close, i64 1)\n ret void\n}\n")
}
@hide
fresh proc(state @mut State) usize {
value :: state.next_register
state.next_register += 1
return value
}
@hide
load proc(state @mut State, value ir.ValueId) usize ! io.WriteError {
result :: fresh(state)
try io.print(" %r{} = load ", state.output, {result})
try write_type(state.output, state.module, state.values[usize(value)])
try io.print(", ptr %v{}\n", state.output, {u32(value)})
return result
}
@hide
store proc(state @State, destination ir.ValueId, register usize) void ! io.WriteError {
if (destination == ir.no_value or state.values[usize(destination)].kind == .void) return
try io.write_all(state.output, " store ")
try write_type(state.output, state.module, state.values[usize(destination)])
try io.print(" %r{}, ptr %v{}\n", state.output, {register, u32(destination)})
}
@hide
check_local proc(state @State, slot u32, read bool, span diagnostics.Span) void ! io.WriteError {
position :: location(state.line_starts, span)
try io.print(" call void @check_init(ptr %i{}, i1 {}, i64 {}, i64 {})\n", state.output, {slot, read, position.line, position.column})
}
@hide:file
trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError {
position :: location(state.line_starts, span)
symbol []u8 :: match code {
.integer_overflow: "overflow"
.index_out_of_bounds: "bounds"
else: unreachable
}
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
length :: message.len
try io.print(" call void @trap(i64 -1, i64 {}, i64 {}, ptr @{s}, i64 {})\n unreachable\n", state.output, {position.line, position.column, symbol, length})
}
@hide
check_failure proc(state @mut State, condition usize, code diagnostics.Code, span diagnostics.Span) void ! io.WriteError {
label :: fresh(state)
try io.print(" br i1 %r{}, label %fail{}, label %ok{}\nfail{}:\n", state.output, {condition, label, label, label})
try trap(state, code, span)
try io.print("ok{}:\n", state.output, {label})
}
@hide
write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
match value.type {
.int: try io.print("{}", output, {value.data.integer})
.usize: try io.print("{}", output, {value.data.unsigned})
.float: {
bits :: ptrcast!(@u64, &value.data.real)^
try io.print("bitcast (i64 {} to double)", output, {bits})
}
.bool: try io.print("{}", output, {value.data.boolean})
.string: try io.print("{{ ptr @s{}, i64 {} }}", output, {u32(value.data.string), ir.string_bytes(module, value.data.string).len})
.function: try io.print("@f{}", output, {u32(value.data.function)})
.array: {
shape :: module.arrays.items[usize(value.array_type)]
width :: if (shape.element == .array) module.arrays.items[usize(shape.element_array)].cells else 1
try io.write_all(output, "[")
for (0..shape.len) |index| {
if (index != 0) try io.write_all(output, ", ")
element := ir.Value{}
if (shape.element == .array) {
element = ir.Value{ type = .array, array_type = shape.element_array, data = ir.ValueData{ elements = value.data.elements[index * width..(index + 1) * width] } }
} else element = value.data.elements[index]
try write_type(output, module, Type{ kind = element.type, shape = element.array_type })
try io.write_all(output, " ")
try write_constant(output, module, element)
}
try io.write_all(output, "]")
}
else: unreachable
}
}
@hide:file
emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.WriteError {
output :: state.output
try io.write_all(output, "define internal ")
try write_type(output, state.module, Type{ kind = function.result, shape = function.result_array_type })
try io.print(" @f{}(", output, {id})
for (function.parameters.items) |_, index| {
if (index != 0) try io.write_all(output, ", ")
try write_type(output, state.module, local_type(function, index))
try io.print(" %p{}", output, {index})
}
try io.write_all(output, ") {\nentry:\n")
for (function.locals.items) |_, index| {
type :: local_type(function, index)
try io.print(" %l{} = alloca ", output, {index})
try write_type(output, state.module, type)
try io.print("\n %i{} = alloca i1\n store i1 {}, ptr %i{}\n", output, {index, index < function.parameters.items.len, index})
if (index < function.parameters.items.len) {
try io.write_all(output, " store ")
try write_type(output, state.module, type)
try io.print(" %p{}, ptr %l{}\n", output, {index, index})
}
}
for (state.values) |type, index| {
if (type.kind == .void) continue
try io.print(" %v{} = alloca ", output, {index})
try write_type(output, state.module, type)
try io.write_all(output, "\n")
}
try io.print(" br label %b{}\n", output, {u32(function.entry)})
for (function.blocks.items) |block, index| {
try io.print("b{}:\n", output, {index})
for (block.instructions.items) |instruction| try emit_instruction(state, &block, instruction)
terminator :: block.terminator
match terminator.kind {
.return_value: {
if (terminator.value == ir.no_value or state.values[usize(terminator.value)].kind == .void) try io.write_all(output, " ret void\n")
else {
result :: try load(state, terminator.value)
try io.write_all(output, " ret ")
try write_type(output, state.module, state.values[usize(terminator.value)])
try io.print(" %r{}\n", output, {result})
}
}
.trap: try io.print(" call void @trap(i64 {}, i64 0, i64 0, ptr null, i64 0)\n unreachable\n", output, {u32(terminator.diagnostic)})
.jump: try io.print(" br label %b{}\n", output, {u32(terminator.target)})
.branch: {
condition :: try load(state, terminator.value)
try io.print(" br i1 %r{}, label %b{}, label %b{}\n", output, {condition, u32(terminator.target), u32(terminator.alternative)})
}
}
}
try io.write_all(output, "}\n")
}
+139
View File
@@ -0,0 +1,139 @@
@hide
runtime ::
`target triple = "arm64-apple-macosx11.0.0"
`%string = type { ptr, i64 }
`%address = type { ptr, ptr }
`declare i64 @write(i32, ptr, i64)
`declare ptr @__error()
`declare i32 @snprintf(ptr, i64, ptr, ...)
`declare void @exit(i32) noreturn
`declare { i64, i1 } @llvm.sadd.with.overflow.i64(i64, i64)
`declare { i64, i1 } @llvm.ssub.with.overflow.i64(i64, i64)
`declare { i64, i1 } @llvm.smul.with.overflow.i64(i64, i64)
`declare { i64, i1 } @llvm.uadd.with.overflow.i64(i64, i64)
`declare { i64, i1 } @llvm.usub.with.overflow.i64(i64, i64)
`declare { i64, i1 } @llvm.umul.with.overflow.i64(i64, i64)
`@fmt_int = private constant [5 x i8] c"%lld\00"
`@fmt_uint = private constant [5 x i8] c"%llu\00"
`@fmt_float = private constant [6 x i8] c"%.17g\00"
`@true = private constant [4 x i8] c"true"
`@false = private constant [5 x i8] c"false"
`@colon = private constant [1 x i8] c":"
`@error = private constant [9 x i8] c": error: "
`@runtime_trap = private constant [16 x i8] c": runtime trap: "
`@newline = private constant [1 x i8] c"\0A"
`@open = private constant [1 x i8] c"["
`@close = private constant [1 x i8] c"]"
`@separator = private constant [2 x i8] c", "
`@write_error = private constant [44 x i8] c": infrastructure error: cannot write output\0A"
`@read = private constant [42 x i8] c"local value was read before initialization"
`@assign = private constant [46 x i8] c"local value was assigned before initialization"
`@cycle = private constant [27 x i8] c"global initialization cycle"
`@overflow = private constant [27 x i8] c"integer arithmetic overflow"
`@bounds = private constant [19 x i8] c"index out of bounds"
`define internal void @out(i32 %fd, ptr %bytes, i64 %len) {
`entry:
` br label %loop
`loop:
` %offset = phi i64 [ 0, %entry ], [ %next, %progress ], [ %offset, %retry ]
` %done = icmp eq i64 %offset, %len
` br i1 %done, label %end, label %send
`send:
` %remaining = sub i64 %len, %offset
` %pointer = getelementptr i8, ptr %bytes, i64 %offset
` %count = call i64 @write(i32 %fd, ptr %pointer, i64 %remaining)
` %positive = icmp sgt i64 %count, 0
` br i1 %positive, label %progress, label %check
`progress:
` %next = add i64 %offset, %count
` br label %loop
`check:
` %negative = icmp slt i64 %count, 0
` br i1 %negative, label %errno, label %fail
`errno:
` %error_pointer = call ptr @__error()
` %error_value = load i32, ptr %error_pointer
` %interrupted = icmp eq i32 %error_value, 4
` br i1 %interrupted, label %retry, label %fail
`retry:
` br label %loop
`fail:
` call void @io_failure()
` unreachable
`end:
` ret void
`}
`define internal void @print_integer(i32 %fd, i64 %value, i1 %unsigned) {
`entry:
` %buffer = alloca [64 x i8], align 16
` %format = select i1 %unsigned, ptr @fmt_uint, ptr @fmt_int
` %count = call i32 (ptr, i64, ptr, ...) @snprintf(ptr %buffer, i64 64, ptr %format, i64 %value)
` %valid_low = icmp sge i32 %count, 0
` %valid_high = icmp slt i32 %count, 64
` %valid = and i1 %valid_low, %valid_high
` br i1 %valid, label %output, label %fail
`output:
` %length = zext i32 %count to i64
` call void @out(i32 %fd, ptr %buffer, i64 %length)
` ret void
`fail:
` call void @io_failure()
` unreachable
`}
`define internal void @print_float(double %value) {
`entry:
` %buffer = alloca [64 x i8], align 16
` %count = call i32 (ptr, i64, ptr, ...) @snprintf(ptr %buffer, i64 64, ptr @fmt_float, double %value)
` %valid_low = icmp sge i32 %count, 0
` %valid_high = icmp slt i32 %count, 64
` %valid = and i1 %valid_low, %valid_high
` br i1 %valid, label %output, label %fail
`output:
` %length = zext i32 %count to i64
` call void @out(i32 1, ptr %buffer, i64 %length)
` ret void
`fail:
` call void @io_failure()
` unreachable
`}
`define internal void @print_bool(i1 %value) {
`entry:
` %bytes = select i1 %value, ptr @true, ptr @false
` %length = select i1 %value, i64 4, i64 5
` call void @out(i32 1, ptr %bytes, i64 %length)
` ret void
`}
`define internal void @print_string(%string %value) {
`entry:
` %bytes = extractvalue %string %value, 0
` %length = extractvalue %string %value, 1
` call void @out(i32 1, ptr %bytes, i64 %length)
` ret void
`}
`define internal void @trap(i64 %id, i64 %line, i64 %column, ptr %message, i64 %length) noreturn {
`entry:
` call void @report(i64 %id)
` %generated = icmp eq i64 %id, -1
` br i1 %generated, label %new, label %end
`new:
` call void @diagnose(i64 %line, i64 %column, ptr %message, i64 %length, i1 true)
` br label %end
`end:
` call void @exit(i32 1)
` unreachable
`}
`define internal void @check_init(ptr %flag, i1 %read, i64 %line, i64 %column) {
`entry:
` %global = icmp eq ptr %flag, null
` br i1 %global, label %end, label %check
`check:
` %ready = load i1, ptr %flag
` br i1 %ready, label %end, label %fail
`fail:
` %message = select i1 %read, ptr @read, ptr @assign
` %length = select i1 %read, i64 42, i64 46
` call void @trap(i64 -1, i64 %line, i64 %column, ptr %message, i64 %length)
` unreachable
`end:
` ret void
`}
+88 -7
View File
@@ -1,3 +1,4 @@
import "@ffi/c"
import "@std/mem"
import "@std/arraylist"
import "@std/enummap"
@@ -13,6 +14,7 @@ import "@source/sema"
import "@source/ir"
import "@source/lower"
import "@source/interpreter"
import "@source/llvm"
typecheck :: import "@source/sema/check"
@@ -46,8 +48,13 @@ DriverError :: alias (
| io.CloseError
| io.ReadError
| io.WriteError
| process.RunError
| NativeBuildError
)
@hide:file
NativeBuildError :: enum { temporary_file_failed, compiler_failed }
main proc(init process.Init) i32 {
return command(mem.c_allocator, init.io, init.args) catch |err| {
if (init.args.values.len > 2) {
@@ -77,19 +84,93 @@ infrastructure_messages :: $enummap.init(DriverError, []u8, {
not_open_for_writing = "output is not writable",
write_failed = "cannot write output",
no_progress = "output writer made no progress",
spawn_failed = "cannot start native compiler",
wait_failed = "cannot wait for native compiler",
invalid_arguments = "invalid native compiler arguments",
temporary_file_failed = "cannot create temporary LLVM file",
compiler_failed = "native compiler failed",
})
@hide:file
command proc(allocator mem.Allocator, runtime io.Io, args process.Args) i32 ! DriverError {
if (args.values.len != 3) {
try io.print("usage: honey run <file>\n", io.stderr(runtime), {})
return 2
if (args.values.len == 3) {
if (mem.eql(args.values[1], "run")) return try run_file(allocator, runtime, args.values[2])
if (mem.eql(args.values[1], "emit-llvm")) return try emit_file(allocator, runtime, args.values[2])
}
if (!mem.eql(args.values[1], "run")) {
try io.print("usage: honey run <file>\n", io.stderr(runtime), {})
return 2
if (args.values.len == 5 or args.values.len == 6) {
if (mem.eql(args.values[1], "build") and mem.eql(args.values[3], "-o") and args.values[4].len != 0) {
optimization [;0]u8 := "-O0"
if (args.values.len == 6) {
optimization = args.values[5]
if (!mem.eql(optimization, "-O0") and !mem.eql(optimization, "-O2")) {
try usage(io.stderr(runtime))
return 2
}
}
return try build_file(allocator, runtime, args.values[2], args.values[4], optimization)
}
}
return try run_file(allocator, runtime, args.values[2])
try usage(io.stderr(runtime))
return 2
}
@hide:file
usage proc(output io.Writer) void ! io.WriteError {
try io.write_all(output, "usage: honey run <file>\n")
try io.write_all(output, " honey emit-llvm <file>\n")
try io.write_all(output, " honey build <file> -o <executable> [-O0|-O2]\n")
}
@hide:file
emit_file proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) i32 ! DriverError {
source := try read_source(allocator, runtime, path)
defer arraylist.deinit(&source)
diagnostic_store := diagnostics.init(allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile(allocator, source.items, &diagnostic_store)
defer ir.deinit(&module)
try llvm.emit(allocator, io.stdout(runtime), &module, &diagnostic_store, source.items, path)
try report_diagnostics(allocator, io.stderr(runtime), path, source.items, &diagnostic_store, null)
return if (diagnostics.count(&diagnostic_store) == 0) 0 else 1
}
@hide:file
build_file proc(
allocator mem.Allocator,
runtime io.Io,
path [;0]u8,
output [;0]u8,
optimization [;0]u8,
) i32 ! DriverError {
source := try read_source(allocator, runtime, path)
defer arraylist.deinit(&source)
diagnostic_store := diagnostics.init(allocator)
defer diagnostics.deinit(&diagnostic_store)
module := try compile(allocator, source.items, &diagnostic_store)
defer ir.deinit(&module)
template [18]mut u8 := undefined
memcopy!(template[..], "/tmp/honey-XXXXXX\0")
descriptor :: c.mkstemp(ptrcast!(template[..].ptr))
if (descriptor < 0) return .temporary_file_failed
defer _ = c.unlink(ptrcast!(template[..].ptr))
file :: io.File{ io = runtime, handle = io.Handle{ file_desc = descriptor } }
closing := false
defer {
if (!closing) io.close(file) catch |_| {}
}
try llvm.emit(allocator, io.writer(file), &module, &diagnostic_store, source.items, path)
closing = true
try io.close(file)
temporary [;0]u8 :: template[..].ptr[..17;0]
arguments [9][;0]u8 :: [
"clang", "-target", "arm64-apple-macosx11.0.0",
"-x", "ir", optimization, temporary, "-o", output,
]
status :: try process.run(allocator, arguments[..])
if (status != 0) return .compiler_failed
try report_diagnostics(allocator, io.stderr(runtime), path, source.items, &diagnostic_store, null)
return if (diagnostics.count(&diagnostic_store) == 0) 0 else 1
}
@hide:file