From e800ea0fdd4ecbe2e28867550a8c36715f3c5ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 30 Nov 2024 10:20:20 +0100 Subject: [PATCH 1/6] wasm: Add a check for zero length around uses of memory.copy/memory.fill. Apparently the WebAssembly spec requires these instructions to trap if the computed memory access could be out of bounds, even if the length is zero. Really a rather bizarre design choice. --- src/arch/wasm/CodeGen.zig | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 14bc0b2e88b7acd9c1db6c2b63492f8859768f13..144c85bd9ae0b6cd04e758f2c6182907389b8b0f 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1591,10 +1591,28 @@ 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)) { + 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); + + try cg.endBlock(); + return; } @@ -4782,10 +4800,27 @@ 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) { + 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); + + try cg.endBlock(); + return; } -- 2.54.0 From 206373ca61ec6803e979aad9491e1da16cbf94b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 29 Nov 2024 01:22:56 +0100 Subject: [PATCH 2/6] std.Target: Use mvp as the generic CPU model for wasm32/wasm64. As discussed in #21818, generic is a poor baseline model because that model is a moving target in LLVM. Instead, use mvp, which has no features enabled. --- lib/std/Target.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index accb00098dd2d4620af87fe76d925baceb7318ea..2bb8a3361a80e81c5bee8a6fcbd13b4711d35368 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.generic, else => generic(arch), }; -- 2.54.0 From 280ced66eb712f5c74835b10fa6ba0bd915ee96b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 29 Nov 2024 01:31:49 +0100 Subject: [PATCH 3/6] std.Target: Define and use lime1 as the baseline CPU model for WebAssembly. See: https://github.com/WebAssembly/tool-conventions/pull/235 This is not *quite* using the same features as the spec'd lime1 model because LLVM 19 doesn't have the level of feature granularity that we need for that. This will be fixed once we upgrade to LLVM 20. Part of #21818. --- build.zig | 14 +++++++------- lib/std/Target.zig | 2 +- lib/std/Target/wasm.zig | 12 ++++++++++++ src/Compilation.zig | 5 ----- tools/update_cpu_features.zig | 14 ++++++++++++++ 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/build.zig b/build.zig index a513a993998fc5d87ea7362d3418d9313b7c47be..24d30f2ec2fc09c4642b208c8782e8b3612403c2 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_fptoint` is not supported by `wasm2c`. + .cpu_features = "baseline-extended_const-nontrapping_fptoint", + }) catch unreachable), }); const exe_options = b.addOptions(); @@ -644,6 +643,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { "wasm-opt", "-Oz", "--enable-bulk-memory", + "--enable-mutable-globals", "--enable-sign-ext", }); run_opt.addArtifactArg(exe); diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 2bb8a3361a80e81c5bee8a6fcbd13b4711d35368..ce6eaa9f2f58c799d3afc60e9bc8b0a2ed58a7de 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2012,7 +2012,7 @@ pub const Cpu = struct { else => generic(arch), }, .xcore => &xcore.cpu.xs1b_generic, - .wasm32, .wasm64 => &wasm.cpu.generic, + .wasm32, .wasm64 => &wasm.cpu.lime1, else => generic(arch), }; diff --git a/lib/std/Target/wasm.zig b/lib/std/Target/wasm.zig index 0507333d960c2560839778760cc8bc379d903a35..eba4f9f01fa2b742b49e3c72b3bdd000948d4df4 100644 --- a/lib/std/Target/wasm.zig +++ b/lib/std/Target/wasm.zig @@ -139,6 +139,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 b85033264ac50c3db199ddaaafb2c06b91410b7b..ac8fb8a59bcee43bd4d1d31b6c0efb35d7fc80e1 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/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 065d40d3f4b4c3bfbe11afdbc63003cd220330f3..5a4e06354f652490a62c95f6e4655426cae3c3f0 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1033,6 +1033,20 @@ const llvm_targets = [_]LlvmTarget{ .zig_name = "wasm", .llvm_name = "WebAssembly", .td_name = "WebAssembly.td", + .extra_cpus = &.{ + .{ + .llvm_name = null, + .zig_name = "lime1", + .features = &.{ + "bulk_memory", + "extended_const", + "multivalue", + "mutable_globals", + "nontrapping_fptoint", + "sign_ext", + }, + }, + }, }, .{ .zig_name = "x86", -- 2.54.0 From ea1502974dd991c0ed4c58dd54fb96cf7080f293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 22 Jan 2025 02:56:53 +0100 Subject: [PATCH 4/6] wasm: Add a nontrapping_bulk_memory_len0 feature. This will mainly be used when targeting our wasm2c implementation which has no problem with zero-length bulk memory operations, as a non-standard extension. --- build.zig | 7 ++-- lib/std/Target/wasm.zig | 8 +++++ src/arch/wasm/CodeGen.zig | 67 +++++++++++++++++++++-------------- src/link/Wasm.zig | 1 + tools/update_cpu_features.zig | 7 ++++ 5 files changed, 60 insertions(+), 30 deletions(-) diff --git a/build.zig b/build.zig index 24d30f2ec2fc09c4642b208c8782e8b3612403c2..34078937ad23f15063e701149758a51040165fd2 100644 --- a/build.zig +++ b/build.zig @@ -598,9 +598,10 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { .optimize = .ReleaseSmall, .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_fptoint` is not supported by `wasm2c`. - .cpu_features = "baseline-extended_const-nontrapping_fptoint", + // * `extended_const` is not supported by the `wasm-opt` version in CI. + // * `nontrapping_fptoint` is not supported by `wasm2c`. + // * `nontrapping_bulk_memory_len0` is supported by `wasm2c`. + .cpu_features = "baseline-extended_const-nontrapping_fptoint+nontrapping_bulk_memory_len0", }) catch unreachable), }); diff --git a/lib/std/Target/wasm.zig b/lib/std/Target/wasm.zig index eba4f9f01fa2b742b49e3c72b3bdd000948d4df4..d5d4dc8f7e49a00680deb8a444b13f695edd6fa2 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", diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 144c85bd9ae0b6cd04e758f2c6182907389b8b0f..51019969f696dcbf3b55ede6bf8ea37157219cea 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1591,27 +1591,34 @@ 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)) { - try cg.startBlock(.block, .empty); + const len0_ok = std.Target.wasm.featureSetHas(cg.target.cpu.features, .nontrapping_bulk_memory_len0); - // 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); + 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); - try cg.endBlock(); + if (!len0_ok) { + try cg.endBlock(); + } return; } @@ -4800,26 +4807,32 @@ 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) { - try cg.startBlock(.block, .empty); + const len0_ok = std.Target.wasm.featureSetHas(cg.target.cpu.features, .nontrapping_bulk_memory_len0); - // 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); + 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); - try cg.endBlock(); + if (!len0_ok) { + try cg.endBlock(); + } return; } diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index cf1a560fb3080ceaa8678ae3814495d80051353a..9da66563e98e4f585399e3898db717098b28a8ab 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", diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 5a4e06354f652490a62c95f6e4655426cae3c3f0..8f126db9f421358f39ec6371f302c5096bd251b1 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1033,6 +1033,13 @@ 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, -- 2.54.0 From 35e804bfb8c57e29eda8ecb18f671b720f469af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 22 Jan 2025 03:44:24 +0100 Subject: [PATCH 5/6] wasm2c: Implement nontrapping_fptoint support. --- build.zig | 4 +-- stage1/wasm2c.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 34078937ad23f15063e701149758a51040165fd2..a524af2923c43b67ed0ef3b22c3b9e552478d42c 100644 --- a/build.zig +++ b/build.zig @@ -599,9 +599,8 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { .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_fptoint` is not supported by `wasm2c`. // * `nontrapping_bulk_memory_len0` is supported by `wasm2c`. - .cpu_features = "baseline-extended_const-nontrapping_fptoint+nontrapping_bulk_memory_len0", + .cpu_features = "baseline-extended_const+nontrapping_bulk_memory_len0", }) catch unreachable), }); @@ -645,6 +644,7 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { "-Oz", "--enable-bulk-memory", "--enable-mutable-globals", + "--enable-nontrapping-float-to-int", "--enable-sign-ext", }); run_opt.addArtifactArg(exe); diff --git a/stage1/wasm2c.c b/stage1/wasm2c.c index 48484f571283909f526e1278267a9a53a0acb63a..425cc682b8d753098d5ee3f47f603e02dea10e6d 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); -- 2.54.0 From 68c6a88770420b897467073ec328ddc2bb3317d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 22 Jan 2025 21:15:25 +0100 Subject: [PATCH 6/6] link.Wasm.Feature: Make fromCpuFeature() and toCpuFeature() less cute. This is more verbose, but at least we now get a compile error instead of UB when a new feature is added to std.Target.wasm.Feature but not to link.Wasm.Feature. --- src/link/Wasm.zig | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 9da66563e98e4f585399e3898db717098b28a8ab..643c9ea9520464f972347367d4e54f7ba5843d8e 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -2836,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"); -- 2.54.0