Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -194,11 +194,20 @@ pub fn build(b: *std.Build) void {
lib_shared.installHeader(b.path("include/zwasm.h"), "zwasm.h");

// Static library (libzwasm.a)
//
// single_threaded = true eliminates the wasm-threads atomic.wait/notify
// paths that pull in std.Thread / std.Io.Threaded. The static lib is
// intended for App-Store-eligible iOS / watchOS / tvOS apps (no JIT,
// no shared memory, single-threaded wasm execution), so dropping the
// threading paths costs no functionality. Required for the
// arm64_32-apple-watchos build because std.Io.Threaded does not
// compile under ILP32 (u64 → usize narrowing errors).
const lib_static_mod = b.createModule(.{
.root_source_file = b.path("src/c_api.zig"),
.target = target,
.optimize = if (lib_optimize) optimize else if (optimize == .Debug) .ReleaseSafe else optimize,
.link_libc = true,
.single_threaded = true,
.pic = if (enable_pic) true else null,
});
lib_static_mod.addOptions("build_options", options);
Expand Down
105 changes: 105 additions & 0 deletions src/c_api.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,111 @@ const types = @import("types.zig");
const WasmModule = types.WasmModule;
const WasiOptions = types.WasiOptions;

// On arm64_32-apple-watchos (32-bit-pointer aarch64) Zig 0.16's default
// panic / std.debug machinery hits `u64 → usize` narrowing errors in
// std.Io.Threaded.dirReadDarwin et al. Even `std.debug.simple_panic`
// still routes through `lockStderr → std_options.debug_io →
// debug_threaded_io.io()` which pulls in the whole std.Io.Threaded
// module. Same applies to std.log.defaultLog. We override `panic` and
// `std_options.logFn` so the static-lib build for watchos compiles
// without any Zig-stdlib patches. Harmless on other targets — zwasm
// surfaces all errors through the C-ABI `zwasm_last_error_message()`
// channel, never via stderr.
fn traping_panic(_: []const u8, _: ?usize) noreturn {
@trap();
}
pub const panic = struct {
pub const call = traping_panic;
pub fn sentinelMismatch(_: anytype, _: anytype) noreturn {
@trap();
}
pub fn unwrapError(_: anyerror) noreturn {
@trap();
}
pub fn outOfBounds(_: usize, _: usize) noreturn {
@trap();
}
pub fn startGreaterThanEnd(_: usize, _: usize) noreturn {
@trap();
}
pub fn inactiveUnionField(_: anytype, _: anytype) noreturn {
@trap();
}
pub fn sliceCastLenRemainder(_: usize) noreturn {
@trap();
}
pub fn reachedUnreachable() noreturn {
@trap();
}
pub fn unwrapNull() noreturn {
@trap();
}
pub fn castToNull() noreturn {
@trap();
}
pub fn incorrectAlignment() noreturn {
@trap();
}
pub fn invalidErrorCode() noreturn {
@trap();
}
pub fn integerOutOfBounds() noreturn {
@trap();
}
pub fn integerOverflow() noreturn {
@trap();
}
pub fn shlOverflow() noreturn {
@trap();
}
pub fn shrOverflow() noreturn {
@trap();
}
pub fn divideByZero() noreturn {
@trap();
}
pub fn exactDivisionRemainder() noreturn {
@trap();
}
pub fn integerPartOutOfBounds() noreturn {
@trap();
}
pub fn corruptSwitch() noreturn {
@trap();
}
pub fn shiftRhsTooBig() noreturn {
@trap();
}
pub fn invalidEnumValue() noreturn {
@trap();
}
pub fn forLenMismatch() noreturn {
@trap();
}
pub fn copyLenMismatch() noreturn {
@trap();
}
pub fn memcpyAlias() noreturn {
@trap();
}
pub fn noreturnReturned() noreturn {
@trap();
}
};

fn noopLog(
comptime _: std.log.Level,
comptime _: @EnumLiteral(),
comptime _: []const u8,
_: anytype,
) void {}

pub const std_options: std.Options = .{
.allow_stack_tracing = false,
.networking = false,
.logFn = noopLog,
};

/// Convert isize (C intptr_t) to platform File.Handle.
fn isizeToHandle(v: isize) std.Io.File.Handle {
if (builtin.os.tag == .windows) {
Expand Down
11 changes: 9 additions & 2 deletions src/guard.zig
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,18 @@ const Ucontext = switch (builtin.os.tag) {
/// Guard region size: 4 GiB + 64 KiB.
/// This ensures any 32-bit index (0..0xFFFFFFFF) + small offset (up to 64 KiB)
/// falls within the mapped region (data + guard).
pub const GUARD_SIZE: usize = 4 * 1024 * 1024 * 1024 + 64 * 1024;
///
/// On ILP32 targets (arm64_32-apple-watchos) `usize` is 32-bit and these
/// values overflow comptime. Guard memory is only used when `jitSupported()`
/// returns true, which is false for watchos — so on 32-bit targets we set
/// the constants to zero placeholders to satisfy the type checker. Any
/// runtime call into the GuardedMem path on a 32-bit platform would be a
/// bug: `addMemory` in src/store.zig predicates on `jitSupported()`.
pub const GUARD_SIZE: usize = if (@sizeOf(usize) >= 8) 4 * 1024 * 1024 * 1024 + 64 * 1024 else 0;

/// Total virtual reservation: data capacity + guard.
/// Data capacity matches Wasm max 4 GiB. Guard provides PROT_NONE safety zone.
pub const TOTAL_RESERVATION: usize = 8 * 1024 * 1024 * 1024 + 64 * 1024;
pub const TOTAL_RESERVATION: usize = if (@sizeOf(usize) >= 8) 8 * 1024 * 1024 * 1024 + 64 * 1024 else 0;

/// Recovery information for signal handler.
/// Set before calling JIT code, cleared after.
Expand Down
11 changes: 9 additions & 2 deletions src/memory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,12 @@ pub const Memory = struct {
const len = self.data.items.len;
if (overflow != 0 or len < @sizeOf(T) or effective > len - @sizeOf(T)) return error.OutOfBoundsMemoryAccess;

const ptr: *const [@sizeOf(T)]u8 = @ptrCast(&self.data.items[effective]);
// After the bounds check, `effective` fits in usize because `len`
// is usize. The explicit @intCast is needed on ILP32 targets
// (arm64_32-apple-watchos) where usize is 32-bit but `effective`
// is u64.
const effective_usize: usize = @intCast(effective);
const ptr: *const [@sizeOf(T)]u8 = @ptrCast(&self.data.items[effective_usize]);
return switch (T) {
u8, u16, u32, u64, i8, i16, i32, i64 => mem.readInt(T, ptr, .little),
u128 => mem.readInt(u128, ptr, .little),
Expand All @@ -193,7 +198,9 @@ pub const Memory = struct {
const len = self.data.items.len;
if (overflow != 0 or len < @sizeOf(T) or effective > len - @sizeOf(T)) return error.OutOfBoundsMemoryAccess;

const ptr: *[@sizeOf(T)]u8 = @ptrCast(&self.data.items[effective]);
// See Memory.read above for why this cast is required on ILP32.
const effective_usize: usize = @intCast(effective);
const ptr: *[@sizeOf(T)]u8 = @ptrCast(&self.data.items[effective_usize]);
switch (T) {
u8, u16, u32, u64, i8, i16, i32, i64 => mem.writeInt(T, ptr, value, .little),
u128 => mem.writeInt(u128, ptr, value, .little),
Expand Down
12 changes: 12 additions & 0 deletions src/types.zig
Original file line number Diff line number Diff line change
Expand Up @@ -468,8 +468,20 @@ pub const WasmModule = struct {
// stand up a private `std.Io.Threaded` owned by this module.
// Acquired early — applyWasiOptions's addPreopenPath needs io to open
// host directories cross-platform (Zig 0.16's `std.Io.Dir.openDir`).
//
// On ILP32 targets (arm64_32-apple-watchos) `std.Io.Threaded` itself
// doesn't compile (u64 syscall returns vs 32-bit usize), so we only
// expose the auto-init path on 64-bit targets. On 32-bit watchos the
// caller MUST supply `config.io` if they want WASI host directories
// — which they won't, because WatchKit apps don't get filesystem
// access. Workloads without WASI (the case for wasm-benchmark) never
// dereference the io vtable, so leaving it default-init'd is OK.
const io: std.Io = blk: {
if (config.io) |io_val| break :blk io_val;
if (@sizeOf(usize) < 8) {
self.owned_io = null;
break :blk @as(std.Io, undefined);
}
const threaded = try allocator.create(std.Io.Threaded);
errdefer allocator.destroy(threaded);
threaded.* = std.Io.Threaded.init(allocator, .{});
Expand Down
6 changes: 5 additions & 1 deletion src/vm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4008,9 +4008,13 @@ pub const Vm = struct {
const byte_count = N * @sizeOf(NarrowT);
const effective, const ov = @addWithOverflow(ma.offset, base);
if (ov != 0 or m.data.items.len < byte_count or effective > m.data.items.len - byte_count) return error.OutOfBoundsMemoryAccess;
// After the bounds check `effective` fits in usize because
// `m.data.items.len` is usize. Explicit cast needed on ILP32
// targets (arm64_32-apple-watchos).
const effective_usize: usize = @intCast(effective);
var narrow: [N]NarrowT = undefined;
for (&narrow, 0..) |*n, i| {
const ptr: *const [@sizeOf(NarrowT)]u8 = @ptrCast(&m.data.items[effective + i * @sizeOf(NarrowT)]);
const ptr: *const [@sizeOf(NarrowT)]u8 = @ptrCast(&m.data.items[effective_usize + i * @sizeOf(NarrowT)]);
n.* = std.mem.readInt(NarrowT, ptr, .little);
}
// Extend to wide
Expand Down
Loading