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,