Merge pull request #22098 from alexrp/wasm-generic-baseline

`std.Target`: Use `lime1` as wasm baseline model and `mvp` as generic model
This commit is contained in:
Alex Rønne Petersen 2025-01-23 18:41:11 +01:00 committed by GitHub
commit d916954bee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 226 additions and 20 deletions

View File

@ -594,15 +594,14 @@ pub fn build(b: *std.Build) !void {
fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void { fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
const semver = try std.SemanticVersion.parse(version); 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, .{ const exe = addCompilerStep(b, .{
.optimize = .ReleaseSmall, .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(); const exe_options = b.addOptions();
@ -644,6 +643,8 @@ fn addWasiUpdateStep(b: *std.Build, version: [:0]const u8) !void {
"wasm-opt", "wasm-opt",
"-Oz", "-Oz",
"--enable-bulk-memory", "--enable-bulk-memory",
"--enable-mutable-globals",
"--enable-nontrapping-float-to-int",
"--enable-sign-ext", "--enable-sign-ext",
}); });
run_opt.addArtifactArg(exe); run_opt.addArtifactArg(exe);

View File

@ -1958,7 +1958,7 @@ pub const Cpu = struct {
.x86_64 => &x86.cpu.x86_64, .x86_64 => &x86.cpu.x86_64,
.nvptx, .nvptx64 => &nvptx.cpu.sm_20, .nvptx, .nvptx64 => &nvptx.cpu.sm_20,
.ve => &ve.cpu.generic, .ve => &ve.cpu.generic,
.wasm32, .wasm64 => &wasm.cpu.generic, .wasm32, .wasm64 => &wasm.cpu.mvp,
.xcore => &xcore.cpu.generic, .xcore => &xcore.cpu.generic,
.xtensa => &xtensa.cpu.generic, .xtensa => &xtensa.cpu.generic,
@ -2012,6 +2012,7 @@ pub const Cpu = struct {
else => generic(arch), else => generic(arch),
}, },
.xcore => &xcore.cpu.xs1b_generic, .xcore => &xcore.cpu.xs1b_generic,
.wasm32, .wasm64 => &wasm.cpu.lime1,
else => generic(arch), else => generic(arch),
}; };

View File

@ -13,6 +13,7 @@ pub const Feature = enum {
multimemory, multimemory,
multivalue, multivalue,
mutable_globals, mutable_globals,
nontrapping_bulk_memory_len0,
nontrapping_fptoint, nontrapping_fptoint,
reference_types, reference_types,
relaxed_simd, relaxed_simd,
@ -70,6 +71,13 @@ pub const all_features = blk: {
.description = "Enable mutable globals", .description = "Enable mutable globals",
.dependencies = featureSet(&[_]Feature{}), .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)] = .{ result[@intFromEnum(Feature.nontrapping_fptoint)] = .{
.llvm_name = "nontrapping-fptoint", .llvm_name = "nontrapping-fptoint",
.description = "Enable non-trapping float-to-int conversion operators", .description = "Enable non-trapping float-to-int conversion operators",
@ -139,6 +147,18 @@ pub const cpu = struct {
.sign_ext, .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 = .{ pub const mvp: CpuModel = .{
.name = "mvp", .name = "mvp",
.llvm_name = "mvp", .llvm_name = "mvp",

View File

@ -4150,14 +4150,9 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye
.os_tag = .freestanding, .os_tag = .freestanding,
.cpu_features_add = std.Target.wasm.featureSet(&.{ .cpu_features_add = std.Target.wasm.featureSet(&.{
.atomics, .atomics,
.bulk_memory,
// .extended_const, not supported by Safari // .extended_const, not supported by Safari
.multivalue,
.mutable_globals,
.nontrapping_fptoint,
.reference_types, .reference_types,
//.relaxed_simd, not supported by Firefox or Safari //.relaxed_simd, not supported by Firefox or Safari
.sign_ext,
// observed to cause Error occured during wast conversion : // observed to cause Error occured during wast conversion :
// Unknown operator: 0xfd058 in Firefox 117 // Unknown operator: 0xfd058 in Firefox 117
//.simd128, //.simd128,

View File

@ -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. // When bulk_memory is enabled, we lower it to wasm's memcpy instruction.
// If not, we lower it ourselves manually // If not, we lower it ourselves manually
if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory)) { 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(dst);
try cg.lowerToStack(src); try cg.lowerToStack(src);
try cg.emitWValue(len); try cg.emitWValue(len);
try cg.addExtended(.memory_copy); try cg.addExtended(.memory_copy);
if (!len0_ok) {
try cg.endBlock();
}
return; 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. // When bulk_memory is enabled, we lower it to wasm's memset instruction.
// If not, we lower it ourselves. // If not, we lower it ourselves.
if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory) and abi_size == 1) { 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.lowerToStack(ptr);
try cg.emitWValue(value); try cg.emitWValue(value);
try cg.emitWValue(len); try cg.emitWValue(len);
try cg.addExtended(.memory_fill); try cg.addExtended(.memory_fill);
if (!len0_ok) {
try cg.endBlock();
}
return; return;
} }

View File

@ -2826,6 +2826,7 @@ pub const Feature = packed struct(u8) {
multimemory, multimemory,
multivalue, multivalue,
@"mutable-globals", @"mutable-globals",
@"nontrapping-bulk-memory-len0",
@"nontrapping-fptoint", @"nontrapping-fptoint",
@"reference-types", @"reference-types",
@"relaxed-simd", @"relaxed-simd",
@ -2835,14 +2836,44 @@ pub const Feature = packed struct(u8) {
@"shared-mem", @"shared-mem",
pub fn fromCpuFeature(feature: std.Target.wasm.Feature) Tag { 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 { pub fn toCpuFeature(tag: Tag) ?std.Target.wasm.Feature {
return if (@intFromEnum(tag) < @typeInfo(std.Target.wasm.Feature).@"enum".fields.len) return switch (tag) {
@enumFromInt(@intFromEnum(tag)) .atomics => .atomics,
else .@"bulk-memory" => .bulk_memory,
null; .@"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"); pub const format = @compileError("use @tagName instead");

View File

@ -109,7 +109,8 @@ int main(int argc, char **argv) {
FILE *out = fopen(argv[2], "wb"); FILE *out = fopen(argv[2], "wb");
if (out == NULL) panic("unable to open output file"); 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 <stdint.h>\n"
"#include <stdlib.h>\n" "#include <stdlib.h>\n"
"#include <string.h>\n" "#include <string.h>\n"
@ -273,6 +274,47 @@ int main(int argc, char **argv) {
" return dst;\n" " return dst;\n"
"}\n" "}\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" "static uint32_t memory_grow(uint8_t **m, uint32_t *p, uint32_t *c, uint32_t n) {\n"
" uint8_t *new_m = *m;\n" " uint8_t *new_m = *m;\n"
" uint32_t r = *p;\n" " uint32_t r = *p;\n"
@ -2074,14 +2116,61 @@ int main(int argc, char **argv) {
case WasmOpcode_prefixed: case WasmOpcode_prefixed:
switch (InputStream_readLeb128_u32(&in)) { switch (InputStream_readLeb128_u32(&in)) {
case WasmPrefixedOpcode_i32_trunc_sat_f32_s: 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: 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: 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: 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: 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: 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: 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: 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: case WasmPrefixedOpcode_memory_init:
(void)InputStream_readLeb128_u32(&in); (void)InputStream_readLeb128_u32(&in);

View File

@ -1033,6 +1033,27 @@ const llvm_targets = [_]LlvmTarget{
.zig_name = "wasm", .zig_name = "wasm",
.llvm_name = "WebAssembly", .llvm_name = "WebAssembly",
.td_name = "WebAssembly.td", .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", .zig_name = "x86",