Commit ea1502974d

Alex Rønne Petersen <alex@alexrp.com>
2025-01-22 02:56:53
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.
1 parent 280ced6
Changed files (5)
lib
std
Target
src
arch
link
tools
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",
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);
-
-        // 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);
+        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);
 
-        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);
-
-        // 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);
+        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);
 
-        try cg.endBlock();
+        if (!len0_ok) {
+            try cg.endBlock();
+        }
 
         return;
     }
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",
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,
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),
     });