commit d916954bee0f477bcada0693d4aa952197cf1eef (tree)
parent f3c29dcb247be5e50d2d05f9fb2e814ebf115a86
Author: Alex Rønne Petersen <alex@alexrp.com>
Date: Thu, 23 Jan 2025 18:41:11 +0100
Merge pull request #22098 from alexrp/wasm-generic-baseline
`std.Target`: Use `lime1` as wasm baseline model and `mvp` as generic model
Diffstat:
8 files changed, 226 insertions(+), 20 deletions(-)
diff --git 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_bulk_memory_len0` is supported by `wasm2c`.
+ .cpu_features = "baseline-extended_const+nontrapping_bulk_memory_len0",
+ }) catch unreachable),
});
const exe_options = b.addOptions();
@@ -644,6 +643,8 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
"wasm-opt",
"-Oz",
"--enable-bulk-memory",
+ "--enable-mutable-globals",
+ "--enable-nontrapping-float-to-int",
"--enable-sign-ext",
});
run_opt.addArtifactArg(exe);
diff --git 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.lime1,
else => generic(arch),
};
diff --git 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",
@@ -139,6 +147,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
@@ -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/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig
@@ -1591,10 +1591,35 @@ 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)) {
+ 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);
+
+ if (!len0_ok) {
+ try cg.endBlock();
+ }
+
return;
}
@@ -4782,10 +4807,33 @@ 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) {
+ 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);
+
+ if (!len0_ok) {
+ try cg.endBlock();
+ }
+
return;
}
diff --git 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",
@@ -2835,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");
diff --git 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 <math.h>\n"
+ fputs("#include <float.h>\n"
+ "#include <math.h>\n"
"#include <stdint.h>\n"
"#include <stdlib.h>\n"
"#include <string.h>\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);
diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig
@@ -1033,6 +1033,27 @@ 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,
+ .zig_name = "lime1",
+ .features = &.{
+ "bulk_memory",
+ "extended_const",
+ "multivalue",
+ "mutable_globals",
+ "nontrapping_fptoint",
+ "sign_ext",
+ },
+ },
+ },
},
.{
.zig_name = "x86",