diff --git a/lib/std/build.zig b/lib/std/build.zig index 4d2fbabcae..9bac20df4b 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1064,6 +1064,9 @@ pub const LibExeObjStep = struct { /// Uses system QEMU installation to run cross compiled foreign architecture build artifacts. enable_qemu: bool = false, + /// Uses system Wasmtime installation to run cross compiled wasm/wasi build artifacts. + enable_wasmtime: bool = false, + /// After following the steps in https://github.com/ziglang/zig/wiki/Updating-libc#glibc, /// this will be the directory $glibc-build-dir/install/glibcs /// Given the example of the aarch64 target, this is the directory @@ -1863,6 +1866,11 @@ pub const LibExeObjStep = struct { try zig_args.append(bin_name); try zig_args.append("--test-cmd-bin"); }, + .wasmtime => |bin_name| if (self.enable_wasmtime) { + try zig_args.append("--test-cmd"); + try zig_args.append(bin_name); + try zig_args.append("--test-cmd-bin"); + }, } for (self.packages.toSliceConst()) |pkg| { zig_args.append("--pkg-begin") catch unreachable; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 9df7370ad0..226574b3dd 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -213,7 +213,8 @@ pub fn assert(ok: bool) void { pub fn panic(comptime format: []const u8, args: ...) noreturn { @setCold(true); - const first_trace_addr = @returnAddress(); + // TODO: remove conditional once wasi / LLVM defines __builtin_return_address + const first_trace_addr = if (builtin.os == .wasi) null else @returnAddress(); panicExtra(null, first_trace_addr, format, args); } diff --git a/lib/std/heap.zig b/lib/std/heap.zig index dae836611e..259c29f6e6 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -33,11 +33,19 @@ fn cShrink(self: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new /// This allocator makes a syscall directly for every allocation and free. /// Thread-safe and lock-free. -pub const page_allocator = &page_allocator_state; +pub const page_allocator = if (std.Target.current.isWasm()) + &wasm_page_allocator_state +else + &page_allocator_state; + var page_allocator_state = Allocator{ .reallocFn = PageAllocator.realloc, .shrinkFn = PageAllocator.shrink, }; +var wasm_page_allocator_state = Allocator{ + .reallocFn = WasmPageAllocator.realloc, + .shrinkFn = WasmPageAllocator.shrink, +}; /// Deprecated. Use `page_allocator`. pub const direct_allocator = page_allocator; @@ -238,6 +246,88 @@ const PageAllocator = struct { } }; +// TODO Exposed LLVM intrinsics is a bug +// See: https://github.com/ziglang/zig/issues/2291 +extern fn @"llvm.wasm.memory.size.i32"(u32) u32; +extern fn @"llvm.wasm.memory.grow.i32"(u32, u32) i32; + +/// TODO: make this re-use freed pages, and cooperate with other callers of these global intrinsics +/// by better utilizing the return value of grow() +const WasmPageAllocator = struct { + var start_ptr: [*]u8 = undefined; + var num_pages: usize = 0; + var end_index: usize = 0; + + comptime { + if (builtin.arch != .wasm32) { + @compileError("WasmPageAllocator is only available for wasm32 arch"); + } + } + + fn alloc(allocator: *Allocator, size: usize, alignment: u29) ![]u8 { + const addr = @ptrToInt(start_ptr) + end_index; + const adjusted_addr = mem.alignForward(addr, alignment); + const adjusted_index = end_index + (adjusted_addr - addr); + const new_end_index = adjusted_index + size; + + if (new_end_index > num_pages * mem.page_size) { + const required_memory = new_end_index - (num_pages * mem.page_size); + + var num_pages: usize = required_memory / mem.page_size; + if (required_memory % mem.page_size != 0) { + num_pages += 1; + } + + const prev_page = @"llvm.wasm.memory.grow.i32"(0, @intCast(u32, num_pages)); + if (prev_page == -1) { + return error.OutOfMemory; + } + + num_pages += num_pages; + } + + const result = start_ptr[adjusted_index..new_end_index]; + end_index = new_end_index; + + return result; + } + + // Check if memory is the last "item" and is aligned correctly + fn is_last_item(memory: []u8, alignment: u29) bool { + return memory.ptr == start_ptr + end_index - memory.len and mem.alignForward(@ptrToInt(memory.ptr), alignment) == @ptrToInt(memory.ptr); + } + + fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { + // Initialize start_ptr at the first realloc + if (num_pages == 0) { + start_ptr = @intToPtr([*]u8, @intCast(usize, @"llvm.wasm.memory.size.i32"(0)) * mem.page_size); + } + + if (is_last_item(old_mem, new_align)) { + const start_index = end_index - old_mem.len; + const new_end_index = start_index + new_size; + + if (new_end_index > num_pages * mem.page_size) { + _ = try alloc(allocator, new_end_index - end_index, new_align); + } + const result = start_ptr[start_index..new_end_index]; + + end_index = new_end_index; + return result; + } else if (new_size <= old_mem.len and new_align <= old_align) { + return error.OutOfMemory; + } else { + const result = try alloc(allocator, new_size, new_align); + mem.copy(u8, result, old_mem); + return result; + } + } + + fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { + return old_mem[0..new_size]; + } +}; + pub const HeapAllocator = switch (builtin.os) { .windows => struct { allocator: Allocator, @@ -487,103 +577,6 @@ pub const FixedBufferAllocator = struct { } }; -// TODO Exposed LLVM intrinsics is a bug -// See: https://github.com/ziglang/zig/issues/2291 -extern fn @"llvm.wasm.memory.size.i32"(u32) u32; -extern fn @"llvm.wasm.memory.grow.i32"(u32, u32) i32; - -pub const wasm_allocator = &wasm_allocator_state.allocator; -var wasm_allocator_state = WasmAllocator{ - .allocator = Allocator{ - .reallocFn = WasmAllocator.realloc, - .shrinkFn = WasmAllocator.shrink, - }, - .start_ptr = undefined, - .num_pages = 0, - .end_index = 0, -}; - -const WasmAllocator = struct { - allocator: Allocator, - start_ptr: [*]u8, - num_pages: usize, - end_index: usize, - - comptime { - if (builtin.arch != .wasm32) { - @compileError("WasmAllocator is only available for wasm32 arch"); - } - } - - fn alloc(allocator: *Allocator, size: usize, alignment: u29) ![]u8 { - const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); - - const addr = @ptrToInt(self.start_ptr) + self.end_index; - const adjusted_addr = mem.alignForward(addr, alignment); - const adjusted_index = self.end_index + (adjusted_addr - addr); - const new_end_index = adjusted_index + size; - - if (new_end_index > self.num_pages * mem.page_size) { - const required_memory = new_end_index - (self.num_pages * mem.page_size); - - var num_pages: usize = required_memory / mem.page_size; - if (required_memory % mem.page_size != 0) { - num_pages += 1; - } - - const prev_page = @"llvm.wasm.memory.grow.i32"(0, @intCast(u32, num_pages)); - if (prev_page == -1) { - return error.OutOfMemory; - } - - self.num_pages += num_pages; - } - - const result = self.start_ptr[adjusted_index..new_end_index]; - self.end_index = new_end_index; - - return result; - } - - // Check if memory is the last "item" and is aligned correctly - fn is_last_item(allocator: *Allocator, memory: []u8, alignment: u29) bool { - const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); - return memory.ptr == self.start_ptr + self.end_index - memory.len and mem.alignForward(@ptrToInt(memory.ptr), alignment) == @ptrToInt(memory.ptr); - } - - fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 { - const self = @fieldParentPtr(WasmAllocator, "allocator", allocator); - - // Initialize start_ptr at the first realloc - if (self.num_pages == 0) { - self.start_ptr = @intToPtr([*]u8, @intCast(usize, @"llvm.wasm.memory.size.i32"(0)) * mem.page_size); - } - - if (is_last_item(allocator, old_mem, new_align)) { - const start_index = self.end_index - old_mem.len; - const new_end_index = start_index + new_size; - - if (new_end_index > self.num_pages * mem.page_size) { - _ = try alloc(allocator, new_end_index - self.end_index, new_align); - } - const result = self.start_ptr[start_index..new_end_index]; - - self.end_index = new_end_index; - return result; - } else if (new_size <= old_mem.len and new_align <= old_align) { - return error.OutOfMemory; - } else { - const result = try alloc(allocator, new_size, new_align); - mem.copy(u8, result, old_mem); - return result; - } - } - - fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 { - return old_mem[0..new_size]; - } -}; - pub const ThreadSafeFixedBufferAllocator = blk: { if (builtin.single_threaded) { break :blk FixedBufferAllocator; diff --git a/lib/std/os.zig b/lib/std/os.zig index 7fcdb7f3ff..a616fbc49f 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -1527,7 +1527,7 @@ pub fn isatty(handle: fd_t) bool { return system.isatty(handle) != 0; } if (builtin.os == .wasi) { - @compileError("TODO implement std.os.isatty for WASI"); + return system.isatty(handle); } if (builtin.os == .linux) { var wsz: linux.winsize = undefined; diff --git a/lib/std/os/bits/wasi.zig b/lib/std/os/bits/wasi.zig index 93d2a82fde..139418ded2 100644 --- a/lib/std/os/bits/wasi.zig +++ b/lib/std/os/bits/wasi.zig @@ -138,7 +138,7 @@ pub const FDFLAG_NONBLOCK: fdflags_t = 0x0004; pub const FDFLAG_RSYNC: fdflags_t = 0x0008; pub const FDFLAG_SYNC: fdflags_t = 0x0010; -const fdstat_t = extern struct { +pub const fdstat_t = extern struct { fs_filetype: filetype_t, fs_flags: fdflags_t, fs_rights_base: rights_t, @@ -298,6 +298,7 @@ pub const subscription_t = extern struct { }; pub const timestamp_t = u64; +pub const time_t = i64; // match https://github.com/CraneStation/wasi-libc pub const userdata_t = u64; @@ -305,3 +306,8 @@ pub const whence_t = u8; pub const WHENCE_CUR: whence_t = 0; pub const WHENCE_END: whence_t = 1; pub const WHENCE_SET: whence_t = 2; + +pub const timespec = extern struct { + tv_sec: time_t, + tv_nsec: isize, +}; diff --git a/lib/std/os/wasi.zig b/lib/std/os/wasi.zig index 7661be1f85..1ffc9610f0 100644 --- a/lib/std/os/wasi.zig +++ b/lib/std/os/wasi.zig @@ -76,3 +76,54 @@ pub extern "wasi_unstable" fn sched_yield() errno_t; pub extern "wasi_unstable" fn sock_recv(sock: fd_t, ri_data: *const iovec_t, ri_data_len: usize, ri_flags: riflags_t, ro_datalen: *usize, ro_flags: *roflags_t) errno_t; pub extern "wasi_unstable" fn sock_send(sock: fd_t, si_data: *const ciovec_t, si_data_len: usize, si_flags: siflags_t, so_datalen: *usize) errno_t; pub extern "wasi_unstable" fn sock_shutdown(sock: fd_t, how: sdflags_t) errno_t; + +/// Get the errno from a syscall return value, or 0 for no error. +pub fn getErrno(r: usize) usize { + const signed_r = @bitCast(isize, r); + return if (signed_r > -4096 and signed_r < 0) @intCast(usize, -signed_r) else 0; +} + +pub fn clock_getres(clock_id: i32, res: *timespec) errno_t { + var ts: timestamp_t = undefined; + const err = clock_res_get(@bitCast(u32, clock_id), &ts); + if (err != 0) { + return err; + } + res.* = .{ + .tv_sec = @intCast(i64, ts / std.time.ns_per_s), + .tv_nsec = @intCast(isize, ts % std.time.ns_per_s), + }; + return 0; +} + +pub fn clock_gettime(clock_id: i32, tp: *timespec) errno_t { + var ts: timestamp_t = undefined; + const err = clock_time_get(@bitCast(u32, clock_id), 1, &ts); + if (err != 0) { + return err; + } + tp.* = .{ + .tv_sec = @intCast(i64, ts / std.time.ns_per_s), + .tv_nsec = @intCast(isize, ts % std.time.ns_per_s), + }; + return 0; +} + +pub fn isatty(fd: fd_t) bool { + var statbuf: fdstat_t = undefined; + const err = fd_fdstat_get(fd, &statbuf); + if (err != 0) { + // errno = err; + return false; + } + + // A tty is a character device that we can't seek or tell on. + if (statbuf.fs_filetype != FILETYPE_CHARACTER_DEVICE or + (statbuf.fs_rights_base & (RIGHT_FD_SEEK | RIGHT_FD_TELL)) != 0) + { + // errno = ENOTTY; + return false; + } + + return true; +} diff --git a/lib/std/target.zig b/lib/std/target.zig index 5c0ac3d905..9652d5b6e5 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -611,6 +611,7 @@ pub const Target = union(enum) { native, qemu: []const u8, wine: []const u8, + wasmtime: []const u8, unavailable, }; @@ -649,6 +650,13 @@ pub const Target = union(enum) { } } + if (self.isWasm()) { + switch (self.getArchPtrBitWidth()) { + 32 => return Executor{ .wasmtime = "wasmtime" }, + else => return .unavailable, + } + } + return .unavailable; } };