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.
This commit is contained in:
Alex Rønne Petersen 2025-01-22 02:56:53 +01:00
parent 280ced66eb
commit ea1502974d
No known key found for this signature in database
5 changed files with 60 additions and 30 deletions

View File

@ -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),
});

View File

@ -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",

View File

@ -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;
}

View File

@ -2826,6 +2826,7 @@ pub const Feature = packed struct(u8) {
multimemory,
multivalue,
@"mutable-globals",
@"nontrapping-bulk-memory-len0",
@"nontrapping-fptoint",
@"reference-types",
@"relaxed-simd",

View File

@ -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,