ir done'ish
This commit is contained in:
138
src/main.zig
138
src/main.zig
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user