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
+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