zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 80f79cc9e8ed02f631c9d59f68e5f20a909e85fc (tree)
parent cb38bd0a1436bd18de8ed57c45ffc890c8ddfb78
Author: Andrew Kelley <andrew@ziglang.org>
Date:   Mon, 25 Nov 2019 18:46:35 -0500

Merge branch 'fengb-wasi-run-tests'

closes #3730

Diffstat:
Mlib/std/build.zig | 8++++++++
Mlib/std/debug.zig | 3++-
Mlib/std/heap.zig | 189++++++++++++++++++++++++++++++++++++++-----------------------------------------
Mlib/std/os.zig | 44+++++++++++++++++++++++++++++++++++++++++++-
Mlib/std/os/bits/wasi.zig | 8+++++++-
Mlib/std/os/wasi.zig | 5+++++
Mlib/std/target.zig | 12++++++++++++
Msrc/analyze.cpp | 3++-
Msrc/ir.cpp | 8++++++++
Msrc/target.cpp | 6++++--
Mtest/compile_errors.zig | 19++++++++++++++++++-
Mtest/stage1/behavior/asm.zig | 3++-
Mtest/stage1/behavior/new_stack_call.zig | 3+++
13 files changed, 205 insertions(+), 106 deletions(-)

diff --git 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 @@ -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 @@ -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 inner_num_pages: usize = required_memory / mem.page_size; + if (required_memory % mem.page_size != 0) { + inner_num_pages += 1; + } + + const prev_page = @"llvm.wasm.memory.grow.i32"(0, @intCast(u32, inner_num_pages)); + if (prev_page == -1) { + return error.OutOfMemory; + } + + num_pages += inner_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 @@ -1527,7 +1527,22 @@ pub fn isatty(handle: fd_t) bool { return system.isatty(handle) != 0; } if (builtin.os == .wasi) { - @compileError("TODO implement std.os.isatty for WASI"); + var statbuf: fdstat_t = undefined; + const err = system.fd_fdstat_get(handle, &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; } if (builtin.os == .linux) { var wsz: linux.winsize = undefined; @@ -2720,6 +2735,20 @@ pub fn dl_iterate_phdr( pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { + if (comptime std.Target.current.getOs() == .wasi) { + var ts: timestamp_t = undefined; + switch (system.clock_time_get(@bitCast(u32, clk_id), 1, &ts)) { + 0 => { + tp.* = .{ + .tv_sec = @intCast(i64, ts / std.time.ns_per_s), + .tv_nsec = @intCast(isize, ts % std.time.ns_per_s), + }; + }, + EINVAL => return error.UnsupportedClock, + else => |err| return unexpectedErrno(err), + } + return; + } switch (errno(system.clock_gettime(clk_id, tp))) { 0 => return, EFAULT => unreachable, @@ -2729,6 +2758,19 @@ pub fn clock_gettime(clk_id: i32, tp: *timespec) ClockGetTimeError!void { } pub fn clock_getres(clk_id: i32, res: *timespec) ClockGetTimeError!void { + if (comptime std.Target.current.getOs() == .wasi) { + var ts: timestamp_t = undefined; + switch (system.clock_res_get(@bitCast(u32, clk_id), &ts)) { + 0 => res.* = .{ + .tv_sec = @intCast(i64, ts / std.time.ns_per_s), + .tv_nsec = @intCast(isize, ts % std.time.ns_per_s), + }, + EINVAL => return error.UnsupportedClock, + else => |err| return unexpectedErrno(err), + } + return; + } + switch (errno(system.clock_getres(clk_id, res))) { 0 => return, EFAULT => unreachable, diff --git 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 @@ -76,3 +76,8 @@ 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: errno_t) usize { + return r; +} diff --git a/lib/std/target.zig b/lib/std/target.zig @@ -607,10 +607,15 @@ pub const Target = union(enum) { } } + pub fn supportsNewStackCall(self: Target) bool { + return !self.isWasm(); + } + pub const Executor = union(enum) { native, qemu: []const u8, wine: []const u8, + wasmtime: []const u8, unavailable, }; @@ -649,6 +654,13 @@ pub const Target = union(enum) { } } + if (self.getOs() == .wasi) { + switch (self.getArchPtrBitWidth()) { + 32 => return Executor{ .wasmtime = "wasmtime" }, + else => return .unavailable, + } + } + return .unavailable; } }; diff --git a/src/analyze.cpp b/src/analyze.cpp @@ -978,7 +978,8 @@ bool want_first_arg_sret(CodeGen *g, FnTypeId *fn_type_id) { if (g->zig_target->arch == ZigLLVM_x86 || g->zig_target->arch == ZigLLVM_x86_64 || target_is_arm(g->zig_target) || - target_is_riscv(g->zig_target)) + target_is_riscv(g->zig_target) || + target_is_wasm(g->zig_target)) { X64CABIClass abi_class = type_c_abi_x86_64_class(g, fn_type_id->return_type); return abi_class == X64CABIClass_MEMORY || abi_class == X64CABIClass_MEMORY_nobyval; diff --git a/src/ir.cpp b/src/ir.cpp @@ -17095,6 +17095,14 @@ static IrInstruction *analyze_casted_new_stack(IrAnalyze *ira, IrInstructionCall if (call_instruction->new_stack == nullptr) return nullptr; + if (!call_instruction->is_async_call_builtin && + arch_stack_pointer_register_name(ira->codegen->zig_target->arch) == nullptr) + { + ir_add_error(ira, &call_instruction->base, + buf_sprintf("target arch '%s' does not support @newStackCall", + target_arch_name(ira->codegen->zig_target->arch))); + } + IrInstruction *new_stack = call_instruction->new_stack->child; if (type_is_invalid(new_stack->value->type)) return ira->codegen->invalid_instruction; diff --git a/src/target.cpp b/src/target.cpp @@ -1458,6 +1458,10 @@ const char *arch_stack_pointer_register_name(ZigLLVM_ArchType arch) { case ZigLLVM_mipsel: return "sp"; + case ZigLLVM_wasm32: + case ZigLLVM_wasm64: + return nullptr; // known to be not available + case ZigLLVM_amdgcn: case ZigLLVM_amdil: case ZigLLVM_amdil64: @@ -1491,8 +1495,6 @@ const char *arch_stack_pointer_register_name(ZigLLVM_ArchType arch) { case ZigLLVM_systemz: case ZigLLVM_tce: case ZigLLVM_tcele: - case ZigLLVM_wasm32: - case ZigLLVM_wasm64: case ZigLLVM_xcore: case ZigLLVM_ppc: case ZigLLVM_ppc64: diff --git a/test/compile_errors.zig b/test/compile_errors.zig @@ -2,6 +2,24 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.addCase(x: { + var tc = cases.create("@newStackCall on unsupported target", + \\export fn entry() void { + \\ var buf: [10]u8 align(16) = undefined; + \\ @newStackCall(&buf, foo); + \\} + \\fn foo() void {} + , "tmp.zig:3:5: error: target arch 'wasm32' does not support @newStackCall"); + tc.target = tests.Target{ + .Cross = tests.CrossTarget{ + .arch = .wasm32, + .os = .wasi, + .abi = .none, + }, + }; + break :x tc; + }); + cases.add( "incompatible sentinels", \\export fn entry1(ptr: [*:255]u8) [*:0]u8 { @@ -26,7 +44,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:8:35: note: destination array requires a terminating '0' sentinel, but source array has a terminating '255' sentinel", "tmp.zig:11:31: error: expected type '[2:0]u8', found '[2]u8'", "tmp.zig:11:31: note: destination array requires a terminating '0' sentinel", - ); cases.add( diff --git a/test/stage1/behavior/asm.zig b/test/stage1/behavior/asm.zig @@ -1,5 +1,6 @@ +const std = @import("std"); const config = @import("builtin"); -const expect = @import("std").testing.expect; +const expect = std.testing.expect; comptime { if (config.arch == config.Arch.x86_64 and config.os == config.Os.linux) { diff --git a/test/stage1/behavior/new_stack_call.zig b/test/stage1/behavior/new_stack_call.zig @@ -12,6 +12,9 @@ test "calling a function with a new stack" { // TODO: https://github.com/ziglang/zig/issues/3338 return error.SkipZigTest; } + if (comptime !std.Target.current.supportsNewStackCall()) { + return error.SkipZigTest; + } const arg = 1234;