Skip to content

Commit 43d01ff

Browse files
committed
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 parent 71baa5e commit 43d01ff

File tree

1 file changed

+37
-11
lines changed

1 file changed

+37
-11
lines changed

src/arch/x86_64/Lower.zig

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,35 @@ fn encode(lower: *Lower, prefix: Prefix, mnemonic: Mnemonic, ops: []const Operan
427427
lower.result_insts_len += 1;
428428
}
429429

430+
const inst_tags_len = @typeInfo(Mir.Inst.Tag).@"enum".fields.len;
431+
const 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.
434+
const 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+
430457
fn 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 => &.{

0 commit comments

Comments
 (0)