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 14bc0b2e88..144c85bd9a 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; } 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 accb00098d..2bb8a3361a 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), }; 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 a513a99399..24d30f2ec2 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 2bb8a3361a..ce6eaa9f2f 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 0507333d96..eba4f9f01f 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 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/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 065d40d3f4..5a4e06354f 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", 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 24d30f2ec2..34078937ad 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 eba4f9f01f..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", diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 144c85bd9a..51019969f6 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 cf1a560fb3..9da66563e9 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 5a4e06354f..8f126db9f4 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, 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 34078937ad..a524af2923 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 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); 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 9da66563e9..643c9ea952 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");