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

View File

@@ -1,9 +1,6 @@
const std = @import("std");
const sx = @import("sx");
/// Feature flag: use the IR pipeline (parse → lower → IR → LLVM) instead of AST-based codegen.
const USE_IR_PIPELINE = true;
pub fn main(init: std.process.Init) !void {
const allocator = init.arena.allocator();
const io = init.io;
@@ -24,7 +21,7 @@ pub fn main(init: std.process.Init) !void {
// Parse flags and positional arguments
var input_path: ?[]const u8 = null;
var target_config = sx.codegen.TargetConfig{};
var target_config = sx.target.TargetConfig{};
var lib_paths = std.ArrayList([]const u8).empty;
var show_timing: bool = false;
var explicit_opt: bool = false;
@@ -140,25 +137,12 @@ pub fn main(init: std.process.Init) !void {
}
// Cache MISS — codegen + emit .o to memory (verify skipped: JIT catches errors)
if (USE_IR_PIPELINE) {
comp.generateCodeViaIR() catch { comp.renderErrors(); return; };
} else {
comp.generateCode() catch { comp.renderErrors(); return; };
}
comp.generateCode() catch { comp.renderErrors(); return; };
timer.record("codegen");
timer.mark();
const buf = if (USE_IR_PIPELINE) blk2: {
comp.ir_emitter.?.verifyWithMessage() catch return;
break :blk2 comp.ir_emitter.?.emitObjectToMemory() catch return;
} else
(emit_blk: {
var cg = &comp.cg.?;
break :emit_blk cg.emitObjectToMemory() catch {
comp.renderErrors();
return;
};
});
comp.ir_emitter.?.verifyWithMessage() catch return;
const buf = comp.ir_emitter.?.emitObjectToMemory() catch return;
timer.record("emit");
// Save .o to cache (extract data before JIT takes ownership)
@@ -175,10 +159,25 @@ pub fn main(init: std.process.Init) !void {
defer c_handle.unload(io);
timer.record("c-import");
// dlopen #library dependencies so JIT can resolve foreign symbols
const libs = extractLibraries(allocator, root) catch return;
var lib_handles = std.ArrayList(*anyopaque).empty;
defer {
for (lib_handles.items) |h| _ = std.c.dlclose(h);
}
for (libs) |lib_name| {
if (loadLibrary(allocator, lib_name, target_config.lib_paths)) |handle| {
lib_handles.append(allocator, handle) catch {};
} else {
const e = std.c.dlerror();
if (e) |msg| std.debug.print("warning: could not load library '{s}': {s}\n", .{ lib_name, std.mem.span(msg) });
}
}
// JIT from precompiled object (relocation only, no IR compilation)
sx.llvm_api.initNativeTarget();
timer.mark();
const exit_code = sx.codegen.CodeGen.runJITFromObject(obj_buf) catch {
const exit_code = sx.target.runJITFromObject(obj_buf) catch {
// JIT failed — fall back to AOT
timer.record("jit-fail");
runAOT(allocator, io, path, target_config, &timer, enable_cache) catch return;
@@ -212,7 +211,7 @@ fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Com
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
}
fn parseOptLevel(s: []const u8) ?sx.codegen.TargetConfig.OptLevel {
fn parseOptLevel(s: []const u8) ?sx.target.TargetConfig.OptLevel {
if (std.mem.eql(u8, s, "none") or std.mem.eql(u8, s, "0")) return .none;
if (std.mem.eql(u8, s, "less") or std.mem.eql(u8, s, "1")) return .less;
if (std.mem.eql(u8, s, "default") or std.mem.eql(u8, s, "2")) return .default;
@@ -294,7 +293,7 @@ fn readSource(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8)
return try allocator.dupeZ(u8, source_bytes);
}
fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing) !sx.core.Compilation {
fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing) !sx.core.Compilation {
timer.mark();
const source = try readSource(allocator, io, input_path);
timer.record("read");
@@ -311,20 +310,11 @@ fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const
timer.record("imports");
timer.mark();
if (USE_IR_PIPELINE) {
comp.generateCodeViaIR() catch { comp.renderErrors(); return error.CompileError; };
} else {
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
}
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
timer.record("codegen");
timer.mark();
if (USE_IR_PIPELINE) {
comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError;
} else {
var cg = &comp.cg.?;
cg.verify() catch { comp.renderErrors(); return error.CompileError; };
}
comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError;
timer.record("verify");
return comp;
@@ -348,18 +338,14 @@ fn dumpSxIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !v
std.debug.print("{s}", .{result.items});
}
fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig) !void {
var timer = Timing.init(false);
var comp = try compilePipeline(allocator, io, input_path, target_config, &timer);
defer comp.deinit();
if (USE_IR_PIPELINE) {
comp.ir_emitter.?.printIR();
} else {
comp.cg.?.printIR();
}
comp.ir_emitter.?.printIR();
}
fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig) !void {
fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig) !void {
var timer = Timing.init(false);
var comp = try compilePipeline(allocator, io, input_path, target_config, &timer);
defer comp.deinit();
@@ -368,21 +354,17 @@ fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, tar
break :blk try std.fmt.allocPrint(allocator, "{s}.s", .{name});
};
const asm_path_z = try allocator.dupeZ(u8, asm_path);
if (USE_IR_PIPELINE) {
comp.ir_emitter.?.emitAssembly(asm_path_z.ptr) catch return error.CompileError;
} else {
comp.cg.?.emitAssembly(asm_path_z.ptr) catch { comp.renderErrors(); return error.CompileError; };
}
comp.ir_emitter.?.emitAssembly(asm_path_z.ptr) catch return error.CompileError;
std.debug.print("emitted: {s}\n", .{asm_path});
}
fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig, show_timing: bool, enable_cache: bool) !void {
fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.target.TargetConfig, show_timing: bool, enable_cache: bool) !void {
var timer = Timing.init(show_timing);
try compileWithTimer(allocator, io, input_path, output_path, target_config, &timer, enable_cache);
timer.printAll();
}
fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, enable_cache: bool) !void {
fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing, enable_cache: bool) !void {
// Phase A: read + parse + resolveImports (fast: ~0.5ms)
timer.mark();
const source = try readSource(allocator, io, input_path);
@@ -430,29 +412,15 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
} else {
// Cache MISS — full codegen + emit
timer.mark();
if (USE_IR_PIPELINE) {
comp.generateCodeViaIR() catch { comp.renderErrors(); return error.CompileError; };
} else {
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
}
comp.generateCode() catch { comp.renderErrors(); return error.CompileError; };
timer.record("codegen");
timer.mark();
if (USE_IR_PIPELINE) {
comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError;
} else {
var cg = &comp.cg.?;
cg.verify() catch { comp.renderErrors(); return error.CompileError; };
}
comp.ir_emitter.?.verifyWithMessage() catch return error.CompileError;
timer.record("verify");
timer.mark();
if (USE_IR_PIPELINE) {
comp.ir_emitter.?.emitObject(obj_path.ptr) catch return error.CompileError;
} else {
var cg = &comp.cg.?;
cg.emitObject(obj_path.ptr) catch { comp.renderErrors(); return error.CompileError; };
}
comp.ir_emitter.?.emitObject(obj_path.ptr) catch return error.CompileError;
timer.record("emit");
// Save .o to cache
@@ -471,7 +439,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
// Link (sx .o + C .o files)
timer.mark();
sx.codegen.CodeGen.link(allocator, io, obj_path, c_obj_paths, output_path, libs, target_config) catch {
sx.target.link(allocator, io, obj_path, c_obj_paths, output_path, libs, target_config) catch {
std.debug.print("error: linking failed\n", .{});
return error.CompileError;
};
@@ -489,7 +457,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
}
}
fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, enable_cache: bool) !void {
fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing, enable_cache: bool) !void {
const tmp_bin = if (comptime @import("builtin").os.tag == .windows) "sx_run_tmp.exe" else "/tmp/sx_run_tmp";
try compileWithTimer(allocator, io, input_path, tmp_bin, target_config, timer, enable_cache);
defer {
@@ -518,7 +486,7 @@ fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, targ
// --- Cache helpers ---
fn computeCacheKey(source: [:0]const u8, import_sources: *const std.StringHashMap([:0]const u8), target_config: sx.codegen.TargetConfig) u64 {
fn computeCacheKey(source: [:0]const u8, import_sources: *const std.StringHashMap([:0]const u8), target_config: sx.target.TargetConfig) u64 {
const Wyhash = std.hash.Wyhash;
var key = Wyhash.hash(0, source);
@@ -583,6 +551,40 @@ fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]c
return try libs.toOwnedSlice(allocator);
}
/// Try to dlopen a library by name, searching user paths, host paths, and common naming conventions.
fn loadLibrary(allocator: std.mem.Allocator, lib_name: []const u8, user_lib_paths: []const []const u8) ?*anyopaque {
const is_macos = comptime @import("builtin").os.tag == .macos;
const suffixes: []const []const u8 = if (is_macos) &.{ ".dylib", ".so" } else &.{ ".so", ".dylib" };
// Search paths: user-supplied first, then host defaults
const search_paths = comptime blk: {
var paths: []const []const u8 = &.{};
for (sx.target.host_lib_paths) |p| {
paths = paths ++ .{p};
}
break :blk paths;
};
// Try each path with each suffix
const all_paths = [_][]const []const u8{ user_lib_paths, search_paths };
for (&all_paths) |paths| {
for (paths) |dir| {
for (suffixes) |sfx| {
const full = std.fmt.allocPrintSentinel(allocator, "{s}/lib{s}{s}", .{ dir, lib_name, sfx }, 0) catch continue;
if (std.c.dlopen(full.ptr, .{ .NOW = true })) |h| return h;
}
}
}
// Fallback: bare name (let dlopen search its default paths)
for (suffixes) |sfx| {
const bare = std.fmt.allocPrintSentinel(allocator, "lib{s}{s}", .{ lib_name, sfx }, 0) catch continue;
if (std.c.dlopen(bare.ptr, .{ .NOW = true })) |h| return h;
}
return null;
}
// Simple timing helper — records stage durations and prints a summary table.
const Timing = struct {
const max_entries = 16;