authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-08 17:58:46+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-12 18:40:01+01:00
log43d01ff69f6c6c46bef81dd4de2c78fb0a942b65
treef369cbfe7c6b9467374169f002a4394222d8d672
parent71baa5e769b3b82468736a60e0725a94da9be4e9
signaturelock-open Commit is signed but in an unrecognized format.

x86_64.Lower: replace slow stringToEnum call

Looking at a compilation of 'test/behavior/x86_64/unary.zig' in callgrind showed that a full 30% of the compiler runtime was spent in this `stringToEnum` call, so optimizing it was low-hanging fruit. We tried replacing it with nested `switch` statements using `inline else`, but that generated too much code; it didn't emit huge binaries or anything, but LLVM used a *ridiculous* amount of memory compiling it in some cases. The core problem here is that only a small subset of the cases are actually used (the rest fell through to an "error" path), but that subset is computed at comptime, so we must rely on the optimizer to eliminate the thousands of redundant cases. This would be solved by #21507. Instead, we pre-compute a lookup table at comptime. This table is pretty big (I guess a couple hundred k?), but only the "valid" subset of entries will be accessed in practice (unless a bug in the backend is hit), so it's not too awful on the cache; and it performs much better than the old `std.meta.stringToEnum` call.

1 files changed, 37 insertions(+), 11 deletions(-)

src/arch/x86_64/Lower.zig+37-11
......@@ -427,8 +427,35 @@ fn encode(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operan
427427 lower.result_insts_len += 1;
428428}
429429
430const inst_tags_len = @typeInfo(Mir.Inst.Tag).@"enum".fields.len;
431const inst_fixes_len = @typeInfo(Mir.Inst.Fixes).@"enum".fields.len;
432/// Lookup table, indexed by `@intFromEnum(inst.tag) * inst_fixes_len + @intFromEnum(fixes)`.
433/// The value is the resulting `Mnemonic`, or `null` if the combination is not valid.
434const mnemonic_table: [inst_tags_len * inst_fixes_len]?Mnemonic = table: {
435 @setEvalBranchQuota(80_000);
436 var table: [inst_tags_len * inst_fixes_len]?Mnemonic = undefined;
437 for (0..inst_fixes_len) |fixes_i| {
438 const fixes: Mir.Inst.Fixes = @enumFromInt(fixes_i);
439 const prefix, const suffix = affix: {
440 const pattern = if (std.mem.indexOfScalar(u8, @tagName(fixes), ' ')) |i|
441 @tagName(fixes)[i + 1 ..]
442 else
443 @tagName(fixes);
444 const wildcard_idx = std.mem.indexOfScalar(u8, pattern, '_').?;
445 break :affix .{ pattern[0..wildcard_idx], pattern[wildcard_idx + 1 ..] };
446 };
447 for (0..inst_tags_len) |inst_tag_i| {
448 const inst_tag: Mir.Inst.Tag = @enumFromInt(inst_tag_i);
449 const name = prefix ++ @tagName(inst_tag) ++ suffix;
450 const idx = inst_tag_i * inst_fixes_len + fixes_i;
451 table[idx] = if (@hasField(Mnemonic, name)) @field(Mnemonic, name) else null;
452 }
453 }
454 break :table table;
455};
456
430457fn generic(lower: *Lower, inst: Mir.Inst) Error!void {
431 @setEvalBranchQuota(2_800);
458 @setEvalBranchQuota(2_000);
432459 const fixes = switch (inst.ops) {
433460 .none => inst.data.none.fixes,
434461 .inst => inst.data.inst.fixes,
......@@ -457,19 +484,18 @@ fn generic(lower: *Lower, inst: Mir.Inst) Error!void {
457484 else
458485 .none,
459486 }, mnemonic: {
460 comptime var max_len = 0;
461 inline for (@typeInfo(Mnemonic).@"enum".fields) |field| max_len = @max(field.name.len, max_len);
462 var buf: [max_len]u8 = undefined;
463
487 if (mnemonic_table[@intFromEnum(inst.tag) * inst_fixes_len + @intFromEnum(fixes)]) |mnemonic| {
488 break :mnemonic mnemonic;
489 }
490 // This combination is invalid; make the theoretical mnemonic name and emit an error with it.
464491 const fixes_name = @tagName(fixes);
465492 const pattern = fixes_name[if (std.mem.indexOfScalar(u8, fixes_name, ' ')) |i| i + " ".len else 0..];
466493 const wildcard_index = std.mem.indexOfScalar(u8, pattern, '_').?;
467 const parts = .{ pattern[0..wildcard_index], @tagName(inst.tag), pattern[wildcard_index + "_".len ..] };
468 const err_msg = "unsupported mnemonic: ";
469 const mnemonic = std.fmt.bufPrint(&buf, "{s}{s}{s}", parts) catch
470 return lower.fail(err_msg ++ "'{s}{s}{s}'", parts);
471 break :mnemonic std.meta.stringToEnum(Mnemonic, mnemonic) orelse
472 return lower.fail(err_msg ++ "'{s}'", .{mnemonic});
494 return lower.fail("unsupported mnemonic: '{s}{s}{s}'", .{
495 pattern[0..wildcard_index],
496 @tagName(inst.tag),
497 pattern[wildcard_index + "_".len ..],
498 });
473499 }, switch (inst.ops) {
474500 .none => &.{},
475501 .inst => &.{