ir done'ish

This commit is contained in:
agra
2026-03-01 22:38:41 +02:00
parent 6a920dbd2c
commit f763765ea2
17 changed files with 1443 additions and 15017 deletions

206
src/target.zig Normal file
View File

@@ -0,0 +1,206 @@
const std = @import("std");
const llvm = @import("llvm_api.zig");
const c = llvm.c;
pub const TargetConfig = struct {
/// Target triple (e.g. "aarch64-apple-darwin"). Null = host default.
triple: ?[*:0]const u8 = null,
/// CPU name (e.g. "generic", "apple-m1"). Null = "generic".
cpu: ?[*:0]const u8 = null,
/// CPU features string (e.g. "+avx2"). Null = "".
features: ?[*:0]const u8 = null,
/// Optimization level.
opt_level: OptLevel = .default,
/// Library search paths (-L flags).
lib_paths: []const []const u8 = &.{},
/// Output path override.
output_path: ?[]const u8 = null,
/// Linker command (null = "cc" on Unix, "link.exe" on Windows).
linker: ?[]const u8 = null,
/// Sysroot for cross-compilation (passed as --sysroot to linker).
sysroot: ?[]const u8 = null,
pub const OptLevel = enum {
none,
less,
default,
aggressive,
pub fn toLLVM(self: OptLevel) c.LLVMCodeGenOptLevel {
return switch (self) {
.none => c.LLVMCodeGenLevelNone,
.less => c.LLVMCodeGenLevelLess,
.default => c.LLVMCodeGenLevelDefault,
.aggressive => c.LLVMCodeGenLevelAggressive,
};
}
};
/// Check if target triple indicates aarch64/arm64 (runtime check, not comptime).
pub fn isAarch64(self: TargetConfig) bool {
return self.tripleHasPrefix("aarch64", "arm64");
}
/// Check if target triple indicates x86_64/x86-64.
pub fn isX86_64(self: TargetConfig) bool {
return self.tripleHasPrefix("x86_64", "x86-64");
}
/// Check if target triple indicates Windows (contains "windows" or "win32").
pub fn isWindows(self: TargetConfig) bool {
return self.tripleContains("windows") or self.tripleContains("win32");
}
fn tripleHasPrefix(self: TargetConfig, prefix1: []const u8, prefix2: []const u8) bool {
if (self.triple) |t| {
const span = std.mem.span(t);
return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2);
}
const dt = c.LLVMGetDefaultTargetTriple();
defer c.LLVMDisposeMessage(dt);
const span = std.mem.span(dt);
return std.mem.startsWith(u8, span, prefix1) or std.mem.startsWith(u8, span, prefix2);
}
fn tripleContains(self: TargetConfig, needle: []const u8) bool {
if (self.triple) |t| {
return std.mem.indexOf(u8, std.mem.span(t), needle) != null;
}
const dt = c.LLVMGetDefaultTargetTriple();
defer c.LLVMDisposeMessage(dt);
return std.mem.indexOf(u8, std.mem.span(dt), needle) != null;
}
pub fn getCpu(self: TargetConfig) [*:0]const u8 {
return self.cpu orelse "generic";
}
pub fn getFeatures(self: TargetConfig) [*:0]const u8 {
return self.features orelse "";
}
pub fn getLinker(self: TargetConfig) []const u8 {
return self.linker orelse "cc";
}
};
/// Execute a precompiled object file in-process using LLVM's ORC JIT.
/// Takes ownership of obj_buf. Returns the exit code from main().
pub fn runJITFromObject(obj_buf: c.LLVMMemoryBufferRef) !u8 {
// Create LLJIT with default builder (no custom TM needed — .o is precompiled)
var jit: c.LLVMOrcLLJITRef = null;
var err = c.LLVMOrcCreateLLJIT(&jit, null);
if (err != null) {
const msg = c.LLVMGetErrorMessage(err);
defer c.LLVMDisposeErrorMessage(msg);
std.debug.print("JIT error: {s}\n", .{std.mem.span(msg)});
return error.CompileError;
}
defer _ = c.LLVMOrcDisposeLLJIT(jit);
// Add process symbols so JIT can find libc (printf, etc.)
const jd = c.LLVMOrcLLJITGetMainJITDylib(jit);
const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit);
var gen: c.LLVMOrcDefinitionGeneratorRef = null;
err = c.LLVMOrcCreateDynamicLibrarySearchGeneratorForProcess(&gen, prefix, null, null);
if (err != null) {
const msg = c.LLVMGetErrorMessage(err);
defer c.LLVMDisposeErrorMessage(msg);
std.debug.print("JIT symbol gen error: {s}\n", .{std.mem.span(msg)});
return error.CompileError;
}
c.LLVMOrcJITDylibAddGenerator(jd, gen);
// Add precompiled object file (transfers ownership of obj_buf)
err = c.LLVMOrcLLJITAddObjectFile(jit, jd, obj_buf);
if (err != null) {
const msg = c.LLVMGetErrorMessage(err);
defer c.LLVMDisposeErrorMessage(msg);
std.debug.print("JIT add object error: {s}\n", .{std.mem.span(msg)});
return error.CompileError;
}
// Look up the "main" function
var main_addr: c.LLVMOrcExecutorAddress = 0;
err = c.LLVMOrcLLJITLookup(jit, &main_addr, "main");
if (err != null) {
const msg = c.LLVMGetErrorMessage(err);
defer c.LLVMDisposeErrorMessage(msg);
std.debug.print("JIT lookup error: {s}\n", .{std.mem.span(msg)});
return error.CompileError;
}
// Cast to function pointer and call
const main_fn: *const fn () callconv(.c) i32 = @ptrFromInt(main_addr);
const result = main_fn();
return if (result >= 0 and result <= 255) @intCast(result) else 1;
}
pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, extra_objects: []const []const u8, output_bin: []const u8, libraries: []const []const u8, target_config: TargetConfig) !void {
var argv = std.ArrayList([]const u8).empty;
if (target_config.isWindows()) {
// Windows: MSVC-style linker flags
const linker = target_config.linker orelse "link.exe";
try argv.appendSlice(allocator, &.{ linker, output_obj });
for (extra_objects) |eo| try argv.append(allocator, eo);
try argv.append(allocator, try std.fmt.allocPrint(allocator, "/OUT:{s}", .{output_bin}));
for (target_config.lib_paths) |lp| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "/LIBPATH:{s}", .{lp}));
}
for (libraries) |lib| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "{s}.lib", .{lib}));
}
} else {
// Unix: cc-style linker flags
try argv.appendSlice(allocator, &.{ target_config.getLinker(), output_obj, "-o", output_bin });
for (extra_objects) |eo| try argv.append(allocator, eo);
if (target_config.sysroot) |sr| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "--sysroot={s}", .{sr}));
}
// User-supplied library paths first
for (target_config.lib_paths) |lp| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{lp}));
}
// Auto-detect host OS library paths when linking foreign libraries
if (libraries.len > 0 and target_config.triple == null) {
for (host_lib_paths) |path| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-L{s}", .{path}));
}
}
for (libraries) |lib| {
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib}));
}
}
const argv_slice = try argv.toOwnedSlice(allocator);
var child = std.process.spawn(io, .{
.argv = argv_slice,
}) catch return error.LinkError;
const result = child.wait(io) catch return error.LinkError;
if (result != .exited) return error.LinkError;
if (result.exited != 0) return error.LinkError;
}
/// Common library paths for the host OS, computed at comptime.
pub const host_lib_paths = blk: {
const builtin = @import("builtin");
var paths: []const []const u8 = &.{};
if (builtin.os.tag == .macos) {
if (builtin.cpu.arch == .aarch64) {
// Apple Silicon Homebrew
paths = &.{ "/opt/homebrew/lib", "/usr/local/lib" };
} else {
// Intel Mac Homebrew
paths = &.{"/usr/local/lib"};
}
} else if (builtin.os.tag == .linux) {
paths = &.{ "/usr/local/lib", "/usr/lib" };
}
break :blk paths;
};