import c
This commit is contained in:
77
src/main.zig
77
src/main.zig
@@ -25,7 +25,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
var lib_paths = std.ArrayList([]const u8).empty;
|
||||
var show_timing: bool = false;
|
||||
var explicit_opt: bool = false;
|
||||
var no_cache: bool = false;
|
||||
var enable_cache: bool = false;
|
||||
|
||||
var i: usize = 2;
|
||||
while (i < args.len) : (i += 1) {
|
||||
@@ -60,8 +60,8 @@ pub fn main(init: std.process.Init) !void {
|
||||
target_config.sysroot = args[i];
|
||||
} else if (std.mem.eql(u8, arg, "--time")) {
|
||||
show_timing = true;
|
||||
} else if (std.mem.eql(u8, arg, "--no-cache")) {
|
||||
no_cache = true;
|
||||
} else if (std.mem.eql(u8, arg, "--cache")) {
|
||||
enable_cache = true;
|
||||
} else if (std.mem.startsWith(u8, arg, "-L")) {
|
||||
if (arg.len > 2) {
|
||||
try lib_paths.append(allocator, arg[2..]);
|
||||
@@ -87,7 +87,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
|
||||
if (std.mem.eql(u8, command, "build")) {
|
||||
const output_name = target_config.output_path orelse deriveOutputName(path);
|
||||
compile(allocator, io, path, output_name, target_config, show_timing, no_cache) catch return;
|
||||
compile(allocator, io, path, output_name, target_config, show_timing, enable_cache) catch return;
|
||||
std.debug.print("compiled: {s}\n", .{output_name});
|
||||
} else if (std.mem.eql(u8, command, "ir")) {
|
||||
emitIR(allocator, io, path, target_config) catch return;
|
||||
@@ -117,7 +117,7 @@ pub fn main(init: std.process.Init) !void {
|
||||
// Cache check — use .o files (precompiled object, skip IR compilation in JIT)
|
||||
// Disable caching for files with top-level #run (side effects lost on cache hit)
|
||||
const root = comp.resolved_root orelse comp.root orelse return;
|
||||
const use_cache = !no_cache and !hasTopLevelRun(root);
|
||||
const use_cache = enable_cache and !hasTopLevelRun(root);
|
||||
const key = computeCacheKey(source, &comp.import_sources, target_config);
|
||||
const cache_obj = cachePath(allocator, key, "o") catch return;
|
||||
|
||||
@@ -151,13 +151,19 @@ pub fn main(init: std.process.Init) !void {
|
||||
break :blk buf;
|
||||
};
|
||||
|
||||
// Compile C sources natively and dlopen before JIT
|
||||
timer.mark();
|
||||
var c_handle = compileCForJIT(allocator, io, &comp) catch { comp.renderErrors(); return; };
|
||||
defer c_handle.unload(io);
|
||||
timer.record("c-import");
|
||||
|
||||
// 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 {
|
||||
// JIT failed — fall back to AOT
|
||||
timer.record("jit-fail");
|
||||
runAOT(allocator, io, path, target_config, &timer, no_cache) catch return;
|
||||
runAOT(allocator, io, path, target_config, &timer, enable_cache) catch return;
|
||||
timer.printAll();
|
||||
return;
|
||||
};
|
||||
@@ -170,6 +176,24 @@ pub fn main(init: std.process.Init) !void {
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile C sources from #import c blocks and dlopen them for JIT.
|
||||
fn compileCForJIT(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Compilation) !sx.c_import.CImportHandle {
|
||||
const c_infos = try comp.collectCImportSources();
|
||||
if (c_infos.len == 0) return .{ .allocator = allocator };
|
||||
|
||||
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos);
|
||||
return try sx.c_import.loadCObjectsForJIT(allocator, io, obj_bufs);
|
||||
}
|
||||
|
||||
/// Compile C sources from #import c blocks to .o files for linking.
|
||||
fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Compilation) ![]const []const u8 {
|
||||
const c_infos = try comp.collectCImportSources();
|
||||
if (c_infos.len == 0) return &.{};
|
||||
|
||||
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos);
|
||||
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
|
||||
}
|
||||
|
||||
fn parseOptLevel(s: []const u8) ?sx.codegen.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;
|
||||
@@ -197,7 +221,7 @@ fn printUsage() void {
|
||||
\\ -L <path> Library search path (repeatable)
|
||||
\\ --linker <cmd> Linker command (default: cc)
|
||||
\\ --sysroot <path> Sysroot for cross-compilation
|
||||
\\ --no-cache Disable build caching
|
||||
\\ --cache Enable build caching
|
||||
\\ --time Show compilation timing breakdown
|
||||
\\
|
||||
, .{});
|
||||
@@ -232,8 +256,8 @@ fn runLsp(allocator: std.mem.Allocator, io: std.Io) void {
|
||||
fn deriveOutputName(input_path: []const u8) []const u8 {
|
||||
// Get basename (strip directory)
|
||||
var start: usize = 0;
|
||||
for (input_path, 0..) |ch, i| {
|
||||
if (ch == '/' or ch == '\\') start = i + 1;
|
||||
for (input_path, 0..) |ch, idx| {
|
||||
if (ch == '/' or ch == '\\') start = idx + 1;
|
||||
}
|
||||
const basename = input_path[start..];
|
||||
// Strip .sx extension
|
||||
@@ -300,13 +324,13 @@ fn emitAsm(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, tar
|
||||
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, no_cache: bool) !void {
|
||||
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 {
|
||||
var timer = Timing.init(show_timing);
|
||||
try compileWithTimer(allocator, io, input_path, output_path, target_config, &timer, no_cache);
|
||||
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, no_cache: bool) !void {
|
||||
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 {
|
||||
// Phase A: read + parse + resolveImports (fast: ~0.5ms)
|
||||
timer.mark();
|
||||
const source = try readSource(allocator, io, input_path);
|
||||
@@ -336,7 +360,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
const cache_bin = try cachePath(allocator, key, "bin");
|
||||
|
||||
// Level 1: Try cached binary (skip everything — no codegen, no link)
|
||||
if (!no_cache) bin_cache: {
|
||||
if (enable_cache) bin_cache: {
|
||||
std.Io.Dir.copyFile(.cwd(), cache_bin, .cwd(), output_path, io, .{}) catch break :bin_cache;
|
||||
timer.record("cache");
|
||||
return;
|
||||
@@ -344,7 +368,7 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
|
||||
// Level 2: Try cached .o (skip codegen+emit, still need link)
|
||||
const used_obj_cache = blk: {
|
||||
if (no_cache) break :blk false;
|
||||
if (!enable_cache) break :blk false;
|
||||
std.Io.Dir.copyFile(.cwd(), cache_obj, .cwd(), obj_path, io, .{}) catch break :blk false;
|
||||
break :blk true;
|
||||
};
|
||||
@@ -367,31 +391,42 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
timer.record("emit");
|
||||
|
||||
// Save .o to cache
|
||||
if (!no_cache) {
|
||||
if (enable_cache) {
|
||||
std.Io.Dir.copyFile(.cwd(), obj_path, .cwd(), cache_obj, io, .{ .make_path = true }) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// Link
|
||||
// Compile C sources from #import c blocks to .o files
|
||||
timer.mark();
|
||||
sx.codegen.CodeGen.link(allocator, io, obj_path, output_path, libs, target_config) catch {
|
||||
const c_obj_paths = compileCForBuild(allocator, io, &comp) catch {
|
||||
std.debug.print("error: C import compilation failed\n", .{});
|
||||
return error.CompileError;
|
||||
};
|
||||
timer.record("c-import");
|
||||
|
||||
// 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 {
|
||||
std.debug.print("error: linking failed\n", .{});
|
||||
return error.CompileError;
|
||||
};
|
||||
timer.record("link");
|
||||
|
||||
// Save linked binary to cache
|
||||
if (!no_cache) {
|
||||
if (enable_cache) {
|
||||
std.Io.Dir.copyFile(.cwd(), output_path, .cwd(), cache_bin, io, .{ .make_path = true }) catch {};
|
||||
}
|
||||
|
||||
// Clean up object file
|
||||
// Clean up object files
|
||||
std.Io.Dir.deleteFile(.cwd(), io, obj_path) catch {};
|
||||
for (c_obj_paths) |cop| {
|
||||
std.Io.Dir.deleteFile(.cwd(), io, cop) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.TargetConfig, timer: *Timing, no_cache: bool) !void {
|
||||
fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.codegen.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, no_cache);
|
||||
try compileWithTimer(allocator, io, input_path, tmp_bin, target_config, timer, enable_cache);
|
||||
defer {
|
||||
std.Io.Dir.deleteFile(.cwd(), io, tmp_bin) catch {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user