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:
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;