diff --git a/build.zig b/build.zig index a513a99399..a524af2923 100644 --- a/build.zig +++ b/build.zig @@ -594,15 +594,14 @@ pub fn build(b: *std.Build) !void { fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { const semver = try std.SemanticVersion.parse(version); - var target_query: std.Target.Query = .{ - .cpu_arch = .wasm32, - .os_tag = .wasi, - }; - target_query.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.bulk_memory)); - const exe = addCompilerStep(b, .{ .optimize = .ReleaseSmall, - .target = b.resolveTargetQuery(target_query), + .target = b.resolveTargetQuery(std.Target.Query.parse(.{ + .arch_os_abi = "wasm32-wasi", + // * `extended_const` is not supported by the `wasm-opt` version in CI. + // * `nontrapping_bulk_memory_len0` is supported by `wasm2c`. + .cpu_features = "baseline-extended_const+nontrapping_bulk_memory_len0", + }) catch unreachable), }); const exe_options = b.addOptions(); @@ -644,6 +643,8 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { "wasm-opt", "-Oz", "--enable-bulk-memory", + "--enable-mutable-globals", + "--enable-nontrapping-float-to-int", "--enable-sign-ext", }); run_opt.addArtifactArg(exe); diff --git a/lib/std/Target.zig b/lib/std/Target.zig index accb00098d..ce6eaa9f2f 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1958,7 +1958,7 @@ pub const Cpu = struct { .x86_64 => &x86.cpu.x86_64, .nvptx, .nvptx64 => &nvptx.cpu.sm_20, .ve => &ve.cpu.generic, - .wasm32, .wasm64 => &wasm.cpu.generic, + .wasm32, .wasm64 => &wasm.cpu.mvp, .xcore => &xcore.cpu.generic, .xtensa => &xtensa.cpu.generic, @@ -2012,6 +2012,7 @@ pub const Cpu = struct { else => generic(arch), }, .xcore => &xcore.cpu.xs1b_generic, + .wasm32, .wasm64 => &wasm.cpu.lime1, else => generic(arch), }; diff --git a/lib/std/Target/wasm.zig b/lib/std/Target/wasm.zig index 0507333d96..d5d4dc8f7e 100644 --- a/lib/std/Target/wasm.zig +++ b/lib/std/Target/wasm.zig @@ -13,6 +13,7 @@ pub const Feature = enum { multimemory, multivalue, mutable_globals, + nontrapping_bulk_memory_len0, nontrapping_fptoint, reference_types, relaxed_simd, @@ -70,6 +71,13 @@ pub const all_features = blk: { .description = "Enable mutable globals", .dependencies = featureSet(&[_]Feature{}), }; + result[@intFromEnum(Feature.nontrapping_bulk_memory_len0)] = .{ + .llvm_name = null, + .description = "Bulk memory operations with a zero length do not trap", + .dependencies = featureSet(&[_]Feature{ + .bulk_memory, + }), + }; result[@intFromEnum(Feature.nontrapping_fptoint)] = .{ .llvm_name = "nontrapping-fptoint", .description = "Enable non-trapping float-to-int conversion operators", @@ -139,6 +147,18 @@ pub const cpu = struct { .sign_ext, }), }; + pub const lime1: CpuModel = .{ + .name = "lime1", + .llvm_name = null, + .features = featureSet(&[_]Feature{ + .bulk_memory, + .extended_const, + .multivalue, + .mutable_globals, + .nontrapping_fptoint, + .sign_ext, + }), + }; pub const mvp: CpuModel = .{ .name = "mvp", .llvm_name = "mvp", diff --git a/src/Compilation.zig b/src/Compilation.zig index b85033264a..ac8fb8a59b 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -4150,14 +4150,9 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye .os_tag = .freestanding, .cpu_features_add = std.Target.wasm.featureSet(&.{ .atomics, - .bulk_memory, // .extended_const, not supported by Safari - .multivalue, - .mutable_globals, - .nontrapping_fptoint, .reference_types, //.relaxed_simd, not supported by Firefox or Safari - .sign_ext, // observed to cause Error occured during wast conversion : // Unknown operator: 0xfd058 in Firefox 117 //.simd128, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 14bc0b2e88..51019969f6 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1591,10 +1591,35 @@ fn memcpy(cg: *CodeGen, dst: WValue, src: WValue, len: WValue) !void { // When bulk_memory is enabled, we lower it to wasm's memcpy instruction. // If not, we lower it ourselves manually if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory)) { + const len0_ok = std.Target.wasm.featureSetHas(cg.target.cpu.features, .nontrapping_bulk_memory_len0); + + if (!len0_ok) { + try cg.startBlock(.block, .empty); + + // Even if `len` is zero, the spec requires an implementation to trap if `src + len` or + // `dst + len` are out of memory bounds. This can easily happen in Zig in a case such + // as: + // + // const dst: [*]u8 = undefined; + // const src: [*]u8 = undefined; + // var len: usize = runtime_zero(); + // @memcpy(dst[0..len], src[0..len]); + // + // So explicitly avoid using `memory.copy` in the `len == 0` case. Lovely design. + try cg.emitWValue(len); + try cg.addTag(.i32_eqz); + try cg.addLabel(.br_if, 0); + } + try cg.lowerToStack(dst); try cg.lowerToStack(src); try cg.emitWValue(len); try cg.addExtended(.memory_copy); + + if (!len0_ok) { + try cg.endBlock(); + } + return; } @@ -4782,10 +4807,33 @@ fn memset(cg: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue) // When bulk_memory is enabled, we lower it to wasm's memset instruction. // If not, we lower it ourselves. if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory) and abi_size == 1) { + const len0_ok = std.Target.wasm.featureSetHas(cg.target.cpu.features, .nontrapping_bulk_memory_len0); + + if (!len0_ok) { + try cg.startBlock(.block, .empty); + + // Even if `len` is zero, the spec requires an implementation to trap if `ptr + len` is + // out of memory bounds. This can easily happen in Zig in a case such as: + // + // const ptr: [*]u8 = undefined; + // var len: usize = runtime_zero(); + // @memset(ptr[0..len], 42); + // + // So explicitly avoid using `memory.fill` in the `len == 0` case. Lovely design. + try cg.emitWValue(len); + try cg.addTag(.i32_eqz); + try cg.addLabel(.br_if, 0); + } + try cg.lowerToStack(ptr); try cg.emitWValue(value); try cg.emitWValue(len); try cg.addExtended(.memory_fill); + + if (!len0_ok) { + try cg.endBlock(); + } + return; } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index cf1a560fb3..643c9ea952 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2826,6 +2826,7 @@ pub const Feature = packed struct(u8) { multimemory, multivalue, @"mutable-globals", + @"nontrapping-bulk-memory-len0", @"nontrapping-fptoint", @"reference-types", @"relaxed-simd", @@ -2835,14 +2836,44 @@ pub const Feature = packed struct(u8) { @"shared-mem", pub fn fromCpuFeature(feature: std.Target.wasm.Feature) Tag { - return @enumFromInt(@intFromEnum(feature)); + return switch (feature) { + .atomics => .atomics, + .bulk_memory => .@"bulk-memory", + .exception_handling => .@"exception-handling", + .extended_const => .@"extended-const", + .half_precision => .@"half-precision", + .multimemory => .multimemory, + .multivalue => .multivalue, + .mutable_globals => .@"mutable-globals", + .nontrapping_bulk_memory_len0 => .@"nontrapping-bulk-memory-len0", // Zig extension. + .nontrapping_fptoint => .@"nontrapping-fptoint", + .reference_types => .@"reference-types", + .relaxed_simd => .@"relaxed-simd", + .sign_ext => .@"sign-ext", + .simd128 => .simd128, + .tail_call => .@"tail-call", + }; } pub fn toCpuFeature(tag: Tag) ?std.Target.wasm.Feature { - return if (@intFromEnum(tag) < @typeInfo(std.Target.wasm.Feature).@"enum".fields.len) - @enumFromInt(@intFromEnum(tag)) - else - null; + return switch (tag) { + .atomics => .atomics, + .@"bulk-memory" => .bulk_memory, + .@"exception-handling" => .exception_handling, + .@"extended-const" => .extended_const, + .@"half-precision" => .half_precision, + .multimemory => .multimemory, + .multivalue => .multivalue, + .@"mutable-globals" => .mutable_globals, + .@"nontrapping-bulk-memory-len0" => .nontrapping_bulk_memory_len0, // Zig extension. + .@"nontrapping-fptoint" => .nontrapping_fptoint, + .@"reference-types" => .reference_types, + .@"relaxed-simd" => .relaxed_simd, + .@"sign-ext" => .sign_ext, + .simd128 => .simd128, + .@"tail-call" => .tail_call, + .@"shared-mem" => null, // Linker-only feature. + }; } pub const format = @compileError("use @tagName instead"); diff --git a/stage1/wasm2c.c b/stage1/wasm2c.c index 48484f5712..425cc682b8 100644 --- a/stage1/wasm2c.c +++ b/stage1/wasm2c.c @@ -109,7 +109,8 @@ int main(int argc, char **argv) { FILE *out = fopen(argv[2], "wb"); if (out == NULL) panic("unable to open output file"); - fputs("#include \n" + fputs("#include \n" + "#include \n" "#include \n" "#include \n" "#include \n" @@ -273,6 +274,47 @@ int main(int argc, char **argv) { " return dst;\n" "}\n" "\n" + "static uint32_t i32_trunc_sat_f32(const float src) {\n" + " if (isnan(src)) return 0;\n" + " if (isinf(src)) return (uint32_t)(signbit(src) == 0 ? INT32_MAX : INT32_MIN);\n" + " return (uint32_t)(int32_t)src;\n" + "}\n" + "static uint32_t u32_trunc_sat_f32(const float src) {\n" + " if (isnan(src)) return 0;\n" + " if (isinf(src)) return signbit(src) == 0 ? UINT32_MAX : 0;\n" + " return (uint32_t)src;\n" + "}\n" + "static uint32_t i32_trunc_sat_f64(const double src) {\n" + " if (isnan(src)) return 0;\n" + " if (isinf(src)) return (uint32_t)(signbit(src) == 0 ? INT32_MAX : INT32_MIN);\n" + " return (uint32_t)(int32_t)src;\n" + "}\n" + "static uint32_t u32_trunc_sat_f64(const double src) {\n" + " if (isnan(src)) return 0;\n" + " if (isinf(src)) return signbit(src) == 0 ? UINT32_MAX : 0;\n" + " return (uint32_t)src;\n" + "}\n" + "static uint64_t i64_trunc_sat_f32(const float src) {\n" + " if (isnan(src)) return 0;\n" + " if (isinf(src)) return (uint64_t)(signbit(src) == 0 ? INT64_MAX : INT64_MIN);\n" + " return (uint64_t)(int64_t)src;\n" + "}\n" + "static uint64_t u64_trunc_sat_f32(const float src) {\n" + " if (isnan(src)) return 0;\n" + " if (isinf(src)) return signbit(src) == 0 ? UINT64_MAX : 0;\n" + " return (uint64_t)src;\n" + "}\n" + "static uint64_t i64_trunc_sat_f64(const double src) {\n" + " if (isnan(src)) return 0;\n" + " if (isinf(src)) return (uint64_t)(signbit(src) == 0 ? INT64_MAX : INT64_MIN);\n" + " return (uint64_t)(int64_t)src;\n" + "}\n" + "static uint64_t u64_trunc_sat_f64(const double src) {\n" + " if (isnan(src)) return 0;\n" + " if (isinf(src)) return signbit(src) == 0 ? UINT64_MAX : 0;\n" + " return (uint64_t)src;\n" + "}\n" + "\n" "static uint32_t memory_grow(uint8_t **m, uint32_t *p, uint32_t *c, uint32_t n) {\n" " uint8_t *new_m = *m;\n" " uint32_t r = *p;\n" @@ -2074,14 +2116,61 @@ int main(int argc, char **argv) { case WasmOpcode_prefixed: switch (InputStream_readLeb128_u32(&in)) { case WasmPrefixedOpcode_i32_trunc_sat_f32_s: + if (unreachable_depth == 0) { + uint32_t lhs = FuncGen_stackPop(&fg); + FuncGen_stackPush(&fg, out, WasmValType_i32); + fprintf(out, "i32_trunc_sat_f32(l%" PRIu32 ");\n", lhs); + } + break; case WasmPrefixedOpcode_i32_trunc_sat_f32_u: + if (unreachable_depth == 0) { + uint32_t lhs = FuncGen_stackPop(&fg); + FuncGen_stackPush(&fg, out, WasmValType_i32); + fprintf(out, "u32_trunc_sat_f32(l%" PRIu32 ");\n", lhs); + } + break; case WasmPrefixedOpcode_i32_trunc_sat_f64_s: + if (unreachable_depth == 0) { + uint32_t lhs = FuncGen_stackPop(&fg); + FuncGen_stackPush(&fg, out, WasmValType_i32); + fprintf(out, "i32_trunc_sat_f64(l%" PRIu32 ");\n", lhs); + } + break; case WasmPrefixedOpcode_i32_trunc_sat_f64_u: + if (unreachable_depth == 0) { + uint32_t lhs = FuncGen_stackPop(&fg); + FuncGen_stackPush(&fg, out, WasmValType_i32); + fprintf(out, "u32_trunc_sat_f64(l%" PRIu32 ");\n", lhs); + } + break; case WasmPrefixedOpcode_i64_trunc_sat_f32_s: + if (unreachable_depth == 0) { + uint32_t lhs = FuncGen_stackPop(&fg); + FuncGen_stackPush(&fg, out, WasmValType_i32); + fprintf(out, "i64_trunc_sat_f32(l%" PRIu32 ");\n", lhs); + } + break; case WasmPrefixedOpcode_i64_trunc_sat_f32_u: + if (unreachable_depth == 0) { + uint32_t lhs = FuncGen_stackPop(&fg); + FuncGen_stackPush(&fg, out, WasmValType_i32); + fprintf(out, "u64_trunc_sat_f32(l%" PRIu32 ");\n", lhs); + } + break; case WasmPrefixedOpcode_i64_trunc_sat_f64_s: + if (unreachable_depth == 0) { + uint32_t lhs = FuncGen_stackPop(&fg); + FuncGen_stackPush(&fg, out, WasmValType_i32); + fprintf(out, "i64_trunc_sat_f64(l%" PRIu32 ");\n", lhs); + } + break; case WasmPrefixedOpcode_i64_trunc_sat_f64_u: - if (unreachable_depth == 0) panic("unimplemented opcode"); + if (unreachable_depth == 0) { + uint32_t lhs = FuncGen_stackPop(&fg); + FuncGen_stackPush(&fg, out, WasmValType_i32); + fprintf(out, "u64_trunc_sat_f64(l%" PRIu32 ");\n", lhs); + } + break; case WasmPrefixedOpcode_memory_init: (void)InputStream_readLeb128_u32(&in); diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 065d40d3f4..8f126db9f4 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1033,6 +1033,27 @@ const llvm_targets = [_]LlvmTarget{ .zig_name = "wasm", .llvm_name = "WebAssembly", .td_name = "WebAssembly.td", + .extra_features = &.{ + .{ + .zig_name = "nontrapping_bulk_memory_len0", + .desc = "Bulk memory operations with a zero length do not trap", + .deps = &.{"bulk_memory"}, + }, + }, + .extra_cpus = &.{ + .{ + .llvm_name = null, + .zig_name = "lime1", + .features = &.{ + "bulk_memory", + "extended_const", + "multivalue", + "mutable_globals", + "nontrapping_fptoint", + "sign_ext", + }, + }, + }, }, .{ .zig_name = "x86",