sm
This commit is contained in:
@@ -215,6 +215,7 @@ pub const IfExpr = struct {
|
||||
pub const MatchExpr = struct {
|
||||
subject: *Node,
|
||||
arms: []const MatchArm,
|
||||
is_comptime: bool = false,
|
||||
};
|
||||
|
||||
pub const MatchArm = struct {
|
||||
|
||||
@@ -309,6 +309,8 @@ pub fn compileCWithEmcc(
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
infos: []const CImportInfo,
|
||||
target_config: @import("target.zig").TargetConfig,
|
||||
tmp_dir: []const u8,
|
||||
) ![]const []const u8 {
|
||||
var paths = std.ArrayList([]const u8).empty;
|
||||
var obj_idx: usize = 0;
|
||||
@@ -317,11 +319,15 @@ pub fn compileCWithEmcc(
|
||||
if (info.sources.len == 0) continue;
|
||||
|
||||
for (info.sources) |src| {
|
||||
const out_path = try std.fmt.allocPrint(allocator, "/tmp/sx_emcc_{d}.o", .{obj_idx});
|
||||
const out_path = try std.fmt.allocPrint(allocator, "{s}/sx_emcc_{d}.o", .{ tmp_dir, obj_idx });
|
||||
obj_idx += 1;
|
||||
|
||||
var argv = std.ArrayList([]const u8).empty;
|
||||
try argv.appendSlice(allocator, &.{ "emcc", "-c", "-O2", src, "-o", out_path });
|
||||
// wasm64: compile C sources with memory64 support
|
||||
if (target_config.isWasm64()) {
|
||||
try argv.append(allocator, "-sMEMORY64");
|
||||
}
|
||||
// Add include paths
|
||||
for (info.includes) |inc| {
|
||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-I{s}", .{dirName(inc)}));
|
||||
@@ -355,11 +361,12 @@ pub fn writeCObjectFiles(
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
obj_bufs: []c.LLVMMemoryBufferRef,
|
||||
tmp_dir: []const u8,
|
||||
) ![]const []const u8 {
|
||||
var paths = std.ArrayList([]const u8).empty;
|
||||
|
||||
for (obj_bufs, 0..) |buf, i| {
|
||||
const path = try std.fmt.allocPrint(allocator, "/tmp/sx_c_{d}.o", .{i});
|
||||
const path = try std.fmt.allocPrint(allocator, "{s}/sx_c_{d}.o", .{ tmp_dir, i });
|
||||
const start = c.LLVMGetBufferStart(buf);
|
||||
const size = c.LLVMGetBufferSize(buf);
|
||||
const data = @as([*]const u8, @ptrCast(start))[0..size];
|
||||
|
||||
14
src/core.zig
14
src/core.zig
@@ -106,6 +106,18 @@ pub const Compilation = struct {
|
||||
self.ir_emitter = emitter;
|
||||
}
|
||||
|
||||
/// Get link flags accumulated from #run build blocks.
|
||||
pub fn getBuildLinkFlags(self: *Compilation) []const []const u8 {
|
||||
if (self.ir_emitter) |*e| return e.build_config.link_flags.items;
|
||||
return &.{};
|
||||
}
|
||||
|
||||
/// Get output path set from #run build blocks, if any.
|
||||
pub fn getBuildOutputPath(self: *Compilation) ?[]const u8 {
|
||||
if (self.ir_emitter) |*e| return e.build_config.output_path;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Collect C import source info from the resolved AST.
|
||||
pub fn collectCImportSources(self: *Compilation) ![]c_import.CImportInfo {
|
||||
const root = self.resolved_root orelse self.root orelse return &.{};
|
||||
@@ -117,7 +129,7 @@ pub const Compilation = struct {
|
||||
const root = self.resolved_root orelse self.root orelse return ir.Module.init(self.allocator);
|
||||
var module = ir.Module.init(self.allocator);
|
||||
//TODO: find a better place for this
|
||||
if (self.target_config.isWasm()) {
|
||||
if (self.target_config.isWasm32()) {
|
||||
module.types.pointer_size = 4;
|
||||
}
|
||||
var lowering = ir.Lowering.init(&module);
|
||||
|
||||
@@ -86,6 +86,9 @@ pub const LLVMEmitter = struct {
|
||||
// Target configuration (stored for ABI decisions during emission)
|
||||
target_config: TargetConfig,
|
||||
|
||||
// Build configuration accumulated from #run blocks
|
||||
build_config: interp_mod.BuildConfig,
|
||||
|
||||
const PendingPhi = struct {
|
||||
phi: c.LLVMValueRef,
|
||||
block_id: BlockId, // the block this phi belongs to
|
||||
@@ -158,10 +161,12 @@ pub const LLVMEmitter = struct {
|
||||
.closure_struct_type = null,
|
||||
.field_name_arrays = std.AutoHashMap(u32, c.LLVMValueRef).init(alloc),
|
||||
.target_config = target_config,
|
||||
.build_config = .{},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *LLVMEmitter) void {
|
||||
self.build_config.deinit(self.alloc);
|
||||
self.ref_map.deinit();
|
||||
self.func_map.deinit();
|
||||
self.field_name_arrays.deinit();
|
||||
@@ -199,9 +204,9 @@ pub const LLVMEmitter = struct {
|
||||
|
||||
/// Compare IR typeSizeBytes against LLVMABISizeOfType for all user-defined types.
|
||||
fn verifySizes(self: *LLVMEmitter) void {
|
||||
// Skip for WASM: wasm32 has 4-byte pointers vs IR's assumed 8-byte,
|
||||
// Skip for wasm32: 4-byte pointers vs IR's assumed 8-byte,
|
||||
// so struct sizes will differ. LLVM handles emission correctly.
|
||||
if (self.target_config.isWasm()) return;
|
||||
if (self.target_config.isWasm32()) return;
|
||||
const dl = c.LLVMGetModuleDataLayout(self.llvm_module);
|
||||
if (dl == null) return;
|
||||
const type_count = self.ir_mod.types.infos.items.len;
|
||||
@@ -241,6 +246,7 @@ pub const LLVMEmitter = struct {
|
||||
// Run the side-effect function via interpreter
|
||||
const func_id = ir_inst.FuncId.fromIndex(@intCast(i));
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
_ = interp_inst.call(func_id, &.{}) catch {};
|
||||
// Write comptime output to stderr (same as old comptime VM)
|
||||
if (interp_inst.output.items.len > 0) {
|
||||
@@ -263,6 +269,7 @@ pub const LLVMEmitter = struct {
|
||||
// Evaluate comptime initializer if present
|
||||
if (global.comptime_func) |func_id| {
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
const result = interp_inst.call(func_id, &.{}) catch .void_val;
|
||||
const init_val = self.valueToLLVMConst(result, llvm_ty);
|
||||
c.LLVMSetInitializer(llvm_global, init_val);
|
||||
@@ -793,6 +800,7 @@ pub const LLVMEmitter = struct {
|
||||
const callee_func = &self.ir_mod.functions.items[call_op.callee.index()];
|
||||
if (callee_func.is_comptime and call_op.args.len == 0) {
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
defer interp_inst.deinit();
|
||||
if (interp_inst.call(call_op.callee, &.{})) |result| {
|
||||
if (result.asInt()) |v| {
|
||||
@@ -1427,7 +1435,7 @@ pub const LLVMEmitter = struct {
|
||||
const raw_ptr = c.LLVMBuildExtractValue(self.builder, str_val, 0, "str.ptr");
|
||||
const str_len = c.LLVMBuildExtractValue(self.builder, str_val, 1, "str.len");
|
||||
// On wasm32, count param is i32 (size_t)
|
||||
const count = if (self.target_config.isWasm())
|
||||
const count = if (self.target_config.isWasm32())
|
||||
c.LLVMBuildTrunc(self.builder, str_len, self.cached_i32, "len.tr")
|
||||
else
|
||||
str_len;
|
||||
@@ -2132,9 +2140,9 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMAddFunction(self.llvm_module, "free", fn_ty);
|
||||
}
|
||||
|
||||
/// Returns the LLVM type for C `size_t`: i32 on wasm32, i64 on 64-bit targets.
|
||||
/// Returns the LLVM type for C `size_t`: i32 on wasm32, i64 on 64-bit targets (including wasm64).
|
||||
fn sizeType(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
return if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64;
|
||||
return if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64;
|
||||
}
|
||||
|
||||
fn getMallocType(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
@@ -2540,7 +2548,7 @@ pub const LLVMEmitter = struct {
|
||||
.string => self.getStringStructType(),
|
||||
.any => self.getAnyStructType(),
|
||||
.noreturn => self.cached_void,
|
||||
.isize, .usize => if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64,
|
||||
.isize, .usize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64,
|
||||
else => self.toLLVMTypeInfo(ty),
|
||||
};
|
||||
}
|
||||
@@ -2667,7 +2675,7 @@ pub const LLVMEmitter = struct {
|
||||
// For now, use opaque ptr
|
||||
return self.cached_ptr;
|
||||
},
|
||||
.usize, .isize => if (self.target_config.isWasm()) self.cached_i32 else self.cached_i64,
|
||||
.usize, .isize => if (self.target_config.isWasm32()) self.cached_i32 else self.cached_i64,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2690,7 +2698,7 @@ pub const LLVMEmitter = struct {
|
||||
// WASM32: usize/isize are pointer-sized (i32 on wasm32).
|
||||
// Other integer types (s64, u64) keep their declared size — they represent
|
||||
// genuinely 64-bit values (SDL_WindowFlags, timestamps, etc.).
|
||||
if (self.target_config.isWasm()) {
|
||||
if (self.target_config.isWasm32()) {
|
||||
if (ir_ty == .usize or ir_ty == .isize) return self.cached_i32;
|
||||
return llvm_ty;
|
||||
}
|
||||
@@ -3007,6 +3015,13 @@ pub const LLVMEmitter = struct {
|
||||
return self.emitToFile(output_path, c.LLVMAssemblyFile);
|
||||
}
|
||||
|
||||
/// Emit the module as LLVM bitcode to disk (for emcc to recompile with a newer LLVM).
|
||||
pub fn emitBitcode(self: *LLVMEmitter, output_path: [*:0]const u8) !void {
|
||||
if (c.LLVMWriteBitcodeToFile(self.llvm_module, output_path) != 0) {
|
||||
return error.EmitFailed;
|
||||
}
|
||||
}
|
||||
|
||||
/// Dump the LLVM IR to a file for debugging.
|
||||
pub fn dumpIRToFile(self: *LLVMEmitter, path: [*:0]const u8) void {
|
||||
_ = c.LLVMPrintModuleToFile(self.llvm_module, path, null);
|
||||
|
||||
@@ -302,6 +302,9 @@ pub const BuiltinId = enum(u16) {
|
||||
type_of,
|
||||
alloc,
|
||||
dealloc,
|
||||
build_options,
|
||||
build_options_add_link_flag,
|
||||
build_options_set_output_path,
|
||||
};
|
||||
|
||||
pub const ProtocolCall = struct {
|
||||
|
||||
@@ -106,6 +106,18 @@ pub const InterpError = error{
|
||||
Unreachable,
|
||||
};
|
||||
|
||||
// ── BuildConfig ─────────────────────────────────────────────────────────
|
||||
// Mutable build configuration accumulated by #run blocks via BuildOptions methods.
|
||||
|
||||
pub const BuildConfig = struct {
|
||||
link_flags: std.ArrayList([]const u8) = .empty,
|
||||
output_path: ?[]const u8 = null,
|
||||
|
||||
pub fn deinit(self: *BuildConfig, alloc: Allocator) void {
|
||||
self.link_flags.deinit(alloc);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Interpreter ─────────────────────────────────────────────────────────
|
||||
|
||||
pub const Interpreter = struct {
|
||||
@@ -121,6 +133,9 @@ pub const Interpreter = struct {
|
||||
// Global values: evaluated comptime globals, indexed by GlobalId
|
||||
global_values: std.AutoHashMap(u32, Value),
|
||||
|
||||
// Mutable build configuration — set by LLVMEmitter, written by #run blocks
|
||||
build_config: ?*BuildConfig = null,
|
||||
|
||||
pub fn init(module: *const Module, alloc: Allocator) Interpreter {
|
||||
return .{
|
||||
.module = module,
|
||||
@@ -1242,6 +1257,30 @@ pub const Interpreter = struct {
|
||||
const f = val.asFloat() orelse return error.TypeError;
|
||||
return .{ .value = .{ .float = @floor(f) } };
|
||||
},
|
||||
.build_options => {
|
||||
// Returns a void sentinel — the "handle" to BuildConfig
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.build_options_add_link_flag => {
|
||||
// args: [opts_handle, flag_string]
|
||||
const str_val = frame.getRef(bi.args[1]);
|
||||
if (str_val.asString(self)) |s| {
|
||||
if (self.build_config) |bc| {
|
||||
bc.link_flags.append(self.alloc, self.alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
|
||||
}
|
||||
}
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.build_options_set_output_path => {
|
||||
// args: [opts_handle, path_string]
|
||||
const str_val = frame.getRef(bi.args[1]);
|
||||
if (str_val.asString(self)) |s| {
|
||||
if (self.build_config) |bc| {
|
||||
bc.output_path = self.alloc.dupe(u8, s) catch return error.CannotEvalComptime;
|
||||
}
|
||||
}
|
||||
return .{ .value = .void_val };
|
||||
},
|
||||
.cast, .type_of, .alloc, .dealloc => {
|
||||
return error.CannotEvalComptime;
|
||||
},
|
||||
|
||||
@@ -204,13 +204,15 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// ARCH: Architecture enum { aarch64; x86_64; wasm32; unknown; }
|
||||
// ARCH: Architecture enum { aarch64; x86_64; wasm32; wasm64; unknown; }
|
||||
const arch_name_id = self.module.types.internString("Architecture");
|
||||
if (self.module.types.findByName(arch_name_id)) |arch_ty| {
|
||||
const arch_info = self.module.types.get(arch_ty);
|
||||
if (arch_info == .@"enum") {
|
||||
const tag: u32 = if (tc.isWasm())
|
||||
const tag: u32 = if (tc.isWasm32())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "wasm32")
|
||||
else if (tc.isWasm64())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "wasm64")
|
||||
else if (tc.isAarch64())
|
||||
self.findVariantIndex(arch_info.@"enum".variants, "aarch64")
|
||||
else if (tc.isX86_64())
|
||||
@@ -221,8 +223,8 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
// POINTER_SIZE: s64 (4 for wasm, 8 otherwise)
|
||||
const ptr_size: i64 = if (tc.isWasm()) 4 else 8;
|
||||
// POINTER_SIZE: s64 (4 for wasm32, 8 for wasm64 and other 64-bit targets)
|
||||
const ptr_size: i64 = if (tc.isWasm32()) 4 else 8;
|
||||
self.comptime_constants.put("POINTER_SIZE", .{ .int_val = ptr_size }) catch {};
|
||||
}
|
||||
|
||||
@@ -2126,6 +2128,52 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a compile-time match expression for `inline if ... == { case ... }`.
|
||||
/// Returns the body of the matching arm, or null if the match can't be resolved.
|
||||
fn evalComptimeMatch(self: *Lowering, me: *const ast.MatchExpr) ?*const Node {
|
||||
// Subject must be a comptime constant identifier
|
||||
const name = switch (me.subject.data) {
|
||||
.identifier => |id| id.name,
|
||||
else => return null,
|
||||
};
|
||||
const cv = self.comptime_constants.get(name) orelse return null;
|
||||
|
||||
switch (cv) {
|
||||
.enum_tag => |et| {
|
||||
const enum_info = self.module.types.get(et.ty);
|
||||
if (enum_info != .@"enum") return null;
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern == null) continue; // default arm
|
||||
const variant_name = switch (arm.pattern.?.data) {
|
||||
.enum_literal => |el| el.name,
|
||||
else => continue,
|
||||
};
|
||||
const variant_idx = self.findVariantIndex(enum_info.@"enum".variants, variant_name);
|
||||
if (et.tag == variant_idx) return arm.body;
|
||||
}
|
||||
// No match — try default arm
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern == null) return arm.body;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
.int_val => |iv| {
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern == null) continue;
|
||||
const rhs_val: i64 = switch (arm.pattern.?.data) {
|
||||
.int_literal => |il| il.value,
|
||||
else => continue,
|
||||
};
|
||||
if (iv == rhs_val) return arm.body;
|
||||
}
|
||||
for (me.arms) |arm| {
|
||||
if (arm.pattern == null) return arm.body;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn lowerWhile(self: *Lowering, we: *const ast.WhileExpr) Ref {
|
||||
const header_bb = self.freshBlock("while.hdr");
|
||||
const body_bb = self.freshBlock("while.body");
|
||||
@@ -2240,6 +2288,14 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn lowerMatch(self: *Lowering, me: *const ast.MatchExpr) Ref {
|
||||
// inline if match: evaluate at compile time, only lower the matching arm
|
||||
if (me.is_comptime) {
|
||||
if (self.evalComptimeMatch(me)) |arm_body| {
|
||||
return self.lowerInlineBranch(arm_body);
|
||||
}
|
||||
// Couldn't evaluate — fall through to runtime
|
||||
}
|
||||
|
||||
const is_type_match = isTypeCategoryMatch(me);
|
||||
const subject = self.lowerExpr(me.subject);
|
||||
|
||||
@@ -3904,6 +3960,15 @@ pub const Lowering = struct {
|
||||
// Try to resolve the method by struct type name
|
||||
const struct_name = self.getStructTypeName(obj_ty);
|
||||
if (struct_name) |sname| {
|
||||
// Intercept BuildOptions compiler builtins
|
||||
if (std.mem.eql(u8, sname, "BuildOptions")) {
|
||||
if (std.mem.eql(u8, fa.field, "add_link_flag")) {
|
||||
return self.builder.callBuiltin(.build_options_add_link_flag, method_args.items, .void);
|
||||
} else if (std.mem.eql(u8, fa.field, "set_output_path")) {
|
||||
return self.builder.callBuiltin(.build_options_set_output_path, method_args.items, .void);
|
||||
}
|
||||
}
|
||||
|
||||
// Try direct qualified name: StructName.method
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ sname, fa.field }) catch fa.field;
|
||||
|
||||
@@ -7530,6 +7595,12 @@ pub const Lowering = struct {
|
||||
const oi = self.module.types.get(obj_ty);
|
||||
if (oi == .@"struct") {
|
||||
const struct_name = self.module.types.getString(oi.@"struct".name);
|
||||
// Intercept BuildOptions compiler builtins
|
||||
if (std.mem.eql(u8, struct_name, "BuildOptions")) {
|
||||
if (std.mem.eql(u8, cfa.field, "add_link_flag") or std.mem.eql(u8, cfa.field, "set_output_path")) {
|
||||
return .void;
|
||||
}
|
||||
}
|
||||
const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ struct_name, cfa.field }) catch cfa.field;
|
||||
if (self.resolveFuncByName(qualified)) |fid| {
|
||||
return self.module.functions.items[@intFromEnum(fid)].ret;
|
||||
|
||||
@@ -8,6 +8,7 @@ pub const c = @cImport({
|
||||
@cInclude("llvm-c/Orc.h");
|
||||
@cInclude("llvm-c/Error.h");
|
||||
@cInclude("llvm-c/BitReader.h");
|
||||
@cInclude("llvm-c/BitWriter.h");
|
||||
@cInclude("llvm-c/Linker.h");
|
||||
|
||||
// Clang shim for C header parsing + source compilation
|
||||
|
||||
80
src/main.zig
80
src/main.zig
@@ -34,7 +34,25 @@ pub fn main(init: std.process.Init) !void {
|
||||
if (std.mem.eql(u8, arg, "--target")) {
|
||||
i += 1;
|
||||
if (i >= args.len) { std.debug.print("error: --target requires a value\n", .{}); return; }
|
||||
target_config.triple = (try allocator.dupeZ(u8, args[i])).ptr;
|
||||
const raw = args[i];
|
||||
// Shorthand aliases for common targets
|
||||
const expanded = if (std.mem.eql(u8, raw, "wasm") or std.mem.eql(u8, raw, "wasm32") or std.mem.eql(u8, raw, "emscripten"))
|
||||
"wasm32-unknown-emscripten"
|
||||
else if (std.mem.eql(u8, raw, "wasm64"))
|
||||
"wasm64-unknown-emscripten"
|
||||
else if (std.mem.eql(u8, raw, "macos") or std.mem.eql(u8, raw, "macos-arm"))
|
||||
"aarch64-apple-macos"
|
||||
else if (std.mem.eql(u8, raw, "macos-x86"))
|
||||
"x86_64-apple-macos"
|
||||
else if (std.mem.eql(u8, raw, "linux") or std.mem.eql(u8, raw, "linux-x86"))
|
||||
"x86_64-unknown-linux-gnu"
|
||||
else if (std.mem.eql(u8, raw, "linux-arm"))
|
||||
"aarch64-unknown-linux-gnu"
|
||||
else if (std.mem.eql(u8, raw, "windows"))
|
||||
"x86_64-windows-msvc"
|
||||
else
|
||||
raw;
|
||||
target_config.triple = (try allocator.dupeZ(u8, expanded)).ptr;
|
||||
} else if (std.mem.eql(u8, arg, "--cpu")) {
|
||||
i += 1;
|
||||
if (i >= args.len) { std.debug.print("error: --cpu requires a value\n", .{}); return; }
|
||||
@@ -100,7 +118,6 @@ pub fn main(init: std.process.Init) !void {
|
||||
break :blk base;
|
||||
};
|
||||
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;
|
||||
} else if (std.mem.eql(u8, command, "ir-dump")) {
|
||||
@@ -219,17 +236,17 @@ fn compileCForJIT(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Compi
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
fn compileCForBuild(allocator: std.mem.Allocator, io: std.Io, comp: *sx.core.Compilation, tmp_dir: []const u8) ![]const []const u8 {
|
||||
const c_infos = try comp.collectCImportSources();
|
||||
if (c_infos.len == 0) return &.{};
|
||||
|
||||
// For Emscripten targets, use emcc to cross-compile C sources
|
||||
if (comp.target_config.isEmscripten()) {
|
||||
return try sx.c_import.compileCWithEmcc(allocator, io, c_infos);
|
||||
return try sx.c_import.compileCWithEmcc(allocator, io, c_infos, comp.target_config, tmp_dir);
|
||||
}
|
||||
|
||||
const obj_bufs = try sx.c_import.compileCToObjects(allocator, c_infos);
|
||||
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs);
|
||||
return try sx.c_import.writeCObjectFiles(allocator, io, obj_bufs, tmp_dir);
|
||||
}
|
||||
|
||||
fn parseOptLevel(s: []const u8) ?sx.target.TargetConfig.OptLevel {
|
||||
@@ -252,7 +269,7 @@ fn printUsage() void {
|
||||
\\ lsp Start language server (LSP)
|
||||
\\
|
||||
\\Options:
|
||||
\\ --target <triple> Target triple (default: host)
|
||||
\\ --target <target> Target triple or shorthand: wasm, macos, linux, windows (default: host)
|
||||
\\ --cpu <name> CPU name (default: generic)
|
||||
\\ --opt <level> Optimization: none/0, less/1, default/2, aggressive/3
|
||||
\\ -o <path> Output path
|
||||
@@ -408,7 +425,11 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
const root = comp.resolved_root orelse comp.root orelse return error.CompileError;
|
||||
const libs = try extractLibraries(allocator, root);
|
||||
|
||||
const obj_path = try std.fmt.allocPrintSentinel(allocator, "{s}.o", .{output_path}, 0);
|
||||
// Create temp directory for build artifacts
|
||||
const tmp_dir: []const u8 = ".sx-tmp";
|
||||
std.Io.Dir.createDirPath(.cwd(), io, tmp_dir) catch {};
|
||||
|
||||
const obj_path = try std.fmt.allocPrintSentinel(allocator, "{s}/main.o", .{tmp_dir}, 0);
|
||||
|
||||
// Cache: compute key and check for cached binary/.o
|
||||
const key = computeCacheKey(source, &comp.import_sources, target_config);
|
||||
@@ -453,15 +474,37 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
|
||||
// Compile C sources from #import c blocks to .o files
|
||||
timer.mark();
|
||||
const c_obj_paths = compileCForBuild(allocator, io, &comp) catch {
|
||||
const c_obj_paths = compileCForBuild(allocator, io, &comp, tmp_dir) catch {
|
||||
std.debug.print("error: C import compilation failed\n", .{});
|
||||
return error.CompileError;
|
||||
};
|
||||
timer.record("c-import");
|
||||
|
||||
// Merge build config (from #run blocks) with CLI config
|
||||
var merged_config = target_config;
|
||||
const build_flags = comp.getBuildLinkFlags();
|
||||
if (build_flags.len > 0) {
|
||||
var all_flags: std.ArrayList([]const u8) = .empty;
|
||||
for (target_config.extra_link_flags) |f| try all_flags.append(allocator, f);
|
||||
for (build_flags) |f| try all_flags.append(allocator, f);
|
||||
merged_config.extra_link_flags = try all_flags.toOwnedSlice(allocator);
|
||||
}
|
||||
// Override output path from #run if set (and no explicit -o was given on CLI)
|
||||
const final_output = if (target_config.output_path == null)
|
||||
(comp.getBuildOutputPath() orelse output_path)
|
||||
else
|
||||
output_path;
|
||||
|
||||
// Ensure output directory exists
|
||||
if (std.mem.lastIndexOfScalar(u8, final_output, '/')) |sep| {
|
||||
if (sep > 0) {
|
||||
std.Io.Dir.createDirPath(.cwd(), io, final_output[0..sep]) catch {};
|
||||
}
|
||||
}
|
||||
|
||||
// Link (sx .o + C .o files)
|
||||
timer.mark();
|
||||
sx.target.link(allocator, io, obj_path, c_obj_paths, output_path, libs, target_config) catch {
|
||||
sx.target.link(allocator, io, obj_path, c_obj_paths, final_output, libs, merged_config) catch {
|
||||
std.debug.print("error: linking failed\n", .{});
|
||||
return error.CompileError;
|
||||
};
|
||||
@@ -472,11 +515,16 @@ fn compileWithTimer(allocator: std.mem.Allocator, io: std.Io, input_path: []cons
|
||||
std.Io.Dir.copyFile(.cwd(), output_path, .cwd(), cache_bin, io, .{ .make_path = true }) catch {};
|
||||
}
|
||||
|
||||
// Clean up object files
|
||||
std.debug.print("compiled: {s}\n", .{final_output});
|
||||
|
||||
// Clean up temp directory and all build artifacts
|
||||
std.Io.Dir.deleteFile(.cwd(), io, obj_path) catch {};
|
||||
const shell_tmp = std.fmt.allocPrint(allocator, "{s}.shell.html", .{obj_path}) catch null;
|
||||
if (shell_tmp) |sp| std.Io.Dir.deleteFile(.cwd(), io, sp) catch {};
|
||||
for (c_obj_paths) |cop| {
|
||||
std.Io.Dir.deleteFile(.cwd(), io, cop) catch {};
|
||||
}
|
||||
std.Io.Dir.deleteDir(.cwd(), io, tmp_dir) catch {};
|
||||
}
|
||||
|
||||
fn runAOT(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, target_config: sx.target.TargetConfig, timer: *Timing, enable_cache: bool) !void {
|
||||
@@ -556,13 +604,21 @@ fn hasTopLevelRun(root: *const sx.ast.Node) bool {
|
||||
|
||||
fn extractLibraries(allocator: std.mem.Allocator, root: *const sx.ast.Node) ![]const []const u8 {
|
||||
var libs = std.ArrayList([]const u8).empty;
|
||||
var seen = std.StringHashMap(void).init(allocator);
|
||||
const addLib = struct {
|
||||
fn f(l: *std.ArrayList([]const u8), s: *std.StringHashMap(void), a: std.mem.Allocator, name: []const u8) !void {
|
||||
if (s.contains(name)) return;
|
||||
try s.put(name, {});
|
||||
try l.append(a, name);
|
||||
}
|
||||
}.f;
|
||||
for (root.data.root.decls) |decl| {
|
||||
switch (decl.data) {
|
||||
.library_decl => |ld| try libs.append(allocator, ld.lib_name),
|
||||
.library_decl => |ld| try addLib(&libs, &seen, allocator, ld.lib_name),
|
||||
.namespace_decl => |ns| {
|
||||
for (ns.decls) |nd| {
|
||||
switch (nd.data) {
|
||||
.library_decl => |ld| try libs.append(allocator, ld.lib_name),
|
||||
.library_decl => |ld| try addLib(&libs, &seen, allocator, ld.lib_name),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ pub const Parser = struct {
|
||||
const expr = try self.parseIfExpr();
|
||||
if (expr.data == .if_expr) {
|
||||
expr.data.if_expr.is_comptime = true;
|
||||
} else if (expr.data == .match_expr) {
|
||||
expr.data.match_expr.is_comptime = true;
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
@@ -1394,6 +1396,8 @@ pub const Parser = struct {
|
||||
const expr = try self.parseIfExpr();
|
||||
if (expr.data == .if_expr) {
|
||||
expr.data.if_expr.is_comptime = true;
|
||||
} else if (expr.data == .match_expr) {
|
||||
expr.data.match_expr.is_comptime = true;
|
||||
}
|
||||
try self.expectSemicolonAfter(expr);
|
||||
return expr;
|
||||
|
||||
@@ -58,6 +58,16 @@ pub const TargetConfig = struct {
|
||||
return self.tripleHasPrefix("wasm32", "wasm64");
|
||||
}
|
||||
|
||||
/// Check if target triple indicates wasm32 specifically (4-byte pointers, i32 size_t).
|
||||
pub fn isWasm32(self: TargetConfig) bool {
|
||||
return self.tripleHasPrefix("wasm32", "wasm32");
|
||||
}
|
||||
|
||||
/// Check if target triple indicates wasm64 specifically (8-byte pointers, i64 size_t).
|
||||
pub fn isWasm64(self: TargetConfig) bool {
|
||||
return self.tripleHasPrefix("wasm64", "wasm64");
|
||||
}
|
||||
|
||||
/// Check if target triple indicates macOS/Darwin.
|
||||
pub fn isMacOS(self: TargetConfig) bool {
|
||||
return self.tripleContains("darwin") or self.tripleContains("macos");
|
||||
@@ -177,9 +187,26 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
|
||||
// Skip -l flags for Emscripten: libraries like SDL3 are provided via
|
||||
// -sUSE_SDL=3, not -lSDL3. User provides everything via --lflags.
|
||||
|
||||
// Extra linker flags (e.g. -sUSE_SDL=3, -sUSE_WEBGL2=1, --preload-file)
|
||||
// wasm64: automatically add -sMEMORY64 for the linker
|
||||
if (target_config.isWasm64()) {
|
||||
try argv.append(allocator, "-sMEMORY64");
|
||||
}
|
||||
|
||||
// Use the built-in sx HTML shell template (write to temp file for emcc)
|
||||
if (std.mem.endsWith(u8, output_bin, ".html")) {
|
||||
const shell_html = @embedFile("wasm_shell.html");
|
||||
const shell_path = try std.fmt.allocPrint(allocator, "{s}.shell.html", .{output_obj});
|
||||
std.Io.Dir.writeFile(.cwd(), io, .{ .sub_path = shell_path, .data = shell_html }) catch {};
|
||||
try argv.appendSlice(allocator, &.{ "--shell-file", shell_path });
|
||||
}
|
||||
|
||||
// Extra linker flags (e.g. -sUSE_SDL=3, -sUSE_WEBGL2=1, --preload-file assets)
|
||||
// Split space-separated flags into individual argv entries.
|
||||
for (target_config.extra_link_flags) |flag| {
|
||||
try argv.append(allocator, flag);
|
||||
var it = std.mem.tokenizeScalar(u8, flag, ' ');
|
||||
while (it.next()) |part| {
|
||||
try argv.append(allocator, part);
|
||||
}
|
||||
}
|
||||
} else if (target_config.isWindows()) {
|
||||
// Windows: MSVC-style linker flags
|
||||
@@ -219,9 +246,12 @@ pub fn link(allocator: std.mem.Allocator, io: std.Io, output_obj: []const u8, ex
|
||||
try argv.append(allocator, try std.fmt.allocPrint(allocator, "-l{s}", .{lib}));
|
||||
}
|
||||
|
||||
// Extra linker flags
|
||||
// Extra linker flags — split space-separated flags into individual argv entries.
|
||||
for (target_config.extra_link_flags) |flag| {
|
||||
try argv.append(allocator, flag);
|
||||
var it = std.mem.tokenizeScalar(u8, flag, ' ');
|
||||
while (it.next()) |part| {
|
||||
try argv.append(allocator, part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
49
src/wasm_shell.html
Normal file
49
src/wasm_shell.html
Normal file
@@ -0,0 +1,49 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
||||
<title>sx</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
html,body{width:100%;height:100%;overflow:hidden;background:#1e1e24}
|
||||
canvas{display:block;width:100vw;height:100vh;outline:none}
|
||||
#overlay{position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#1e1e24;z-index:10;transition:opacity .4s}
|
||||
#overlay.hidden{opacity:0;pointer-events:none}
|
||||
#overlay .bar-track{width:min(280px,60vw);height:3px;background:#2a2a32;border-radius:2px;margin-top:18px}
|
||||
#overlay .bar-fill{height:100%;width:0%;background:#7c7cff;border-radius:2px;transition:width .15s}
|
||||
#overlay .status{color:#888;font:13px/1 -apple-system,system-ui,sans-serif;margin-top:10px;letter-spacing:.02em}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="overlay">
|
||||
<div class="bar-track"><div class="bar-fill" id="bar"></div></div>
|
||||
<div class="status" id="status">Loading…</div>
|
||||
</div>
|
||||
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
|
||||
<script>
|
||||
var bar=document.getElementById('bar');
|
||||
var status=document.getElementById('status');
|
||||
var overlay=document.getElementById('overlay');
|
||||
var Module={
|
||||
canvas:document.getElementById('canvas'),
|
||||
print:function(){console.log(Array.prototype.slice.call(arguments).join(' '))},
|
||||
printErr:function(){console.warn(Array.prototype.slice.call(arguments).join(' '))},
|
||||
setStatus:function(t){
|
||||
if(!t){overlay.classList.add('hidden');return}
|
||||
var m=t.match(/\((\d+(?:\.\d+)?)\/(\d+)\)/);
|
||||
if(m){bar.style.width=(parseInt(m[1])/parseInt(m[2])*100)+'%';status.textContent='Loading\u2026'}
|
||||
else{status.textContent=t}
|
||||
},
|
||||
totalDependencies:0,
|
||||
monitorRunDependencies:function(left){
|
||||
this.totalDependencies=Math.max(this.totalDependencies,left);
|
||||
this.setStatus(left?'Loading... ('+( this.totalDependencies-left)+'/'+this.totalDependencies+')':'');
|
||||
}
|
||||
};
|
||||
Module.setStatus('Loading\u2026');
|
||||
window.onerror=function(){Module.setStatus('Error — see console');};
|
||||
</script>
|
||||
{{{ SCRIPT }}}
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user