Skip to content

Commit 4e9102f

Browse files
committed
x86_64: implement loop_switch_br and switch_dispatch
1 parent 9db70ba commit 4e9102f

File tree

2 files changed

+157
-36
lines changed

2 files changed

+157
-36
lines changed

src/arch/x86_64/CodeGen.zig

Lines changed: 157 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ frame_allocs: std.MultiArrayList(FrameAlloc) = .{},
105105
free_frame_indices: std.AutoArrayHashMapUnmanaged(FrameIndex, void) = .{},
106106
frame_locs: std.MultiArrayList(Mir.FrameLoc) = .{},
107107

108-
loop_repeat_info: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
108+
loops: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
109109
/// The state to restore before branching.
110110
state: State,
111111
/// The branch target.
@@ -219,6 +219,38 @@ pub const MCValue = union(enum) {
219219
reserved_frame: FrameIndex,
220220
air_ref: Air.Inst.Ref,
221221

222+
fn isModifiable(mcv: MCValue) bool {
223+
return switch (mcv) {
224+
.none,
225+
.unreach,
226+
.dead,
227+
.undef,
228+
.immediate,
229+
.register_offset,
230+
.eflags,
231+
.register_overflow,
232+
.lea_symbol,
233+
.lea_direct,
234+
.lea_got,
235+
.lea_tlv,
236+
.lea_frame,
237+
.elementwise_regs_then_frame,
238+
.reserved_frame,
239+
.air_ref,
240+
=> false,
241+
.register,
242+
.register_pair,
243+
.memory,
244+
.load_symbol,
245+
.load_got,
246+
.load_direct,
247+
.load_tlv,
248+
.indirect,
249+
=> true,
250+
.load_frame => |frame_addr| !frame_addr.index.isNamed(),
251+
};
252+
}
253+
222254
fn isMemory(mcv: MCValue) bool {
223255
return switch (mcv) {
224256
.memory, .indirect, .load_frame => true,
@@ -822,7 +854,7 @@ pub fn generate(
822854
function.frame_allocs.deinit(gpa);
823855
function.free_frame_indices.deinit(gpa);
824856
function.frame_locs.deinit(gpa);
825-
function.loop_repeat_info.deinit(gpa);
857+
function.loops.deinit(gpa);
826858
var block_it = function.blocks.valueIterator();
827859
while (block_it.next()) |block| block.deinit(gpa);
828860
function.blocks.deinit(gpa);
@@ -2156,18 +2188,20 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
21562188
const air_tags = self.air.instructions.items(.tag);
21572189

21582190
self.arg_index = 0;
2159-
for (body) |inst| {
2160-
wip_mir_log.debug("{}", .{self.fmtAir(inst)});
2161-
verbose_tracking_log.debug("{}", .{self.fmtTracking()});
2191+
for (body) |inst| switch (air_tags[@intFromEnum(inst)]) {
2192+
.arg => {
2193+
wip_mir_log.debug("{}", .{self.fmtAir(inst)});
2194+
verbose_tracking_log.debug("{}", .{self.fmtTracking()});
21622195

2163-
const old_air_bookkeeping = self.air_bookkeeping;
2164-
try self.inst_tracking.ensureUnusedCapacity(self.gpa, 1);
2165-
switch (air_tags[@intFromEnum(inst)]) {
2166-
.arg => try self.airArg(inst),
2167-
else => break,
2168-
}
2169-
self.checkInvariantsAfterAirInst(inst, old_air_bookkeeping);
2170-
}
2196+
const old_air_bookkeeping = self.air_bookkeeping;
2197+
try self.inst_tracking.ensureUnusedCapacity(self.gpa, 1);
2198+
2199+
try self.airArg(inst);
2200+
2201+
self.checkInvariantsAfterAirInst(inst, old_air_bookkeeping);
2202+
},
2203+
else => break,
2204+
};
21712205

21722206
if (self.arg_index == 0) try self.airDbgVarArgs();
21732207
self.arg_index = 0;
@@ -2256,7 +2290,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
22562290
.block => try self.airBlock(inst),
22572291
.br => try self.airBr(inst),
22582292
.repeat => try self.airRepeat(inst),
2259-
.switch_dispatch => return self.fail("TODO implement `switch_dispatch`", .{}),
2293+
.switch_dispatch => try self.airSwitchDispatch(inst),
22602294
.trap => try self.airTrap(),
22612295
.breakpoint => try self.airBreakpoint(),
22622296
.ret_addr => try self.airRetAddr(inst),
@@ -2345,7 +2379,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
23452379
.field_parent_ptr => try self.airFieldParentPtr(inst),
23462380

23472381
.switch_br => try self.airSwitchBr(inst),
2348-
.loop_switch_br => return self.fail("TODO implement `loop_switch_br`", .{}),
2382+
.loop_switch_br => try self.airLoopSwitchBr(inst),
23492383
.slice_ptr => try self.airSlicePtr(inst),
23502384
.slice_len => try self.airSliceLen(inst),
23512385

@@ -13637,14 +13671,13 @@ fn airLoop(self: *Self, inst: Air.Inst.Index) !void {
1363713671
self.scope_generation += 1;
1363813672
const state = try self.saveState();
1363913673

13640-
try self.loop_repeat_info.putNoClobber(self.gpa, inst, .{
13674+
try self.loops.putNoClobber(self.gpa, inst, .{
1364113675
.state = state,
1364213676
.jmp_target = @intCast(self.mir_instructions.len),
1364313677
});
13644-
defer assert(self.loop_repeat_info.remove(inst));
13678+
defer assert(self.loops.remove(inst));
1364513679

1364613680
try self.genBody(body);
13647-
1364813681
self.finishAirBookkeeping();
1364913682
}
1365013683

@@ -13685,10 +13718,8 @@ fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) !
1368513718
self.finishAirBookkeeping();
1368613719
}
1368713720

13688-
fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
13721+
fn lowerSwitchBr(self: *Self, inst: Air.Inst.Index, switch_br: Air.UnwrappedSwitch, condition: MCValue) !void {
1368913722
const zcu = self.pt.zcu;
13690-
const switch_br = self.air.unwrapSwitch(inst);
13691-
const condition = try self.resolveInst(switch_br.operand);
1369213723
const condition_ty = self.typeOf(switch_br.operand);
1369313724
const liveness = try self.liveness.getSwitchBr(self.gpa, inst, switch_br.cases_len + 1);
1369413725
defer self.gpa.free(liveness.deaths);
@@ -13699,13 +13730,6 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
1369913730
else => unreachable,
1370013731
};
1370113732

13702-
// If the condition dies here in this switch instruction, process
13703-
// that death now instead of later as this has an effect on
13704-
// whether it needs to be spilled in the branches
13705-
if (self.liveness.operandDies(inst, 0)) {
13706-
if (switch_br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);
13707-
}
13708-
1370913733
self.scope_generation += 1;
1371013734
const state = try self.saveState();
1371113735

@@ -13810,11 +13834,111 @@ fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
1381013834
.close_scope = true,
1381113835
});
1381213836
}
13837+
}
13838+
13839+
fn airSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
13840+
const switch_br = self.air.unwrapSwitch(inst);
13841+
const condition = try self.resolveInst(switch_br.operand);
13842+
13843+
// If the condition dies here in this switch instruction, process
13844+
// that death now instead of later as this has an effect on
13845+
// whether it needs to be spilled in the branches
13846+
if (self.liveness.operandDies(inst, 0)) {
13847+
if (switch_br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);
13848+
}
13849+
13850+
try self.lowerSwitchBr(inst, switch_br, condition);
1381313851

1381413852
// We already took care of pl_op.operand earlier, so there's nothing left to do
1381513853
self.finishAirBookkeeping();
1381613854
}
1381713855

13856+
fn airLoopSwitchBr(self: *Self, inst: Air.Inst.Index) !void {
13857+
const switch_br = self.air.unwrapSwitch(inst);
13858+
const condition = try self.resolveInst(switch_br.operand);
13859+
13860+
const mat_cond = if (condition.isModifiable() and
13861+
self.reuseOperand(inst, switch_br.operand, 0, condition))
13862+
condition
13863+
else mat_cond: {
13864+
const mat_cond = try self.allocRegOrMem(inst, true);
13865+
try self.genCopy(self.typeOf(switch_br.operand), mat_cond, condition, .{});
13866+
break :mat_cond mat_cond;
13867+
};
13868+
self.inst_tracking.putAssumeCapacityNoClobber(inst, InstTracking.init(mat_cond));
13869+
13870+
// If the condition dies here in this switch instruction, process
13871+
// that death now instead of later as this has an effect on
13872+
// whether it needs to be spilled in the branches
13873+
if (self.liveness.operandDies(inst, 0)) {
13874+
if (switch_br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);
13875+
}
13876+
13877+
self.scope_generation += 1;
13878+
const state = try self.saveState();
13879+
13880+
try self.loops.putNoClobber(self.gpa, inst, .{
13881+
.state = state,
13882+
.jmp_target = @intCast(self.mir_instructions.len),
13883+
});
13884+
defer assert(self.loops.remove(inst));
13885+
13886+
// Stop tracking block result without forgetting tracking info
13887+
try self.freeValue(mat_cond);
13888+
13889+
try self.lowerSwitchBr(inst, switch_br, mat_cond);
13890+
13891+
try self.processDeath(inst);
13892+
self.finishAirBookkeeping();
13893+
}
13894+
13895+
fn airSwitchDispatch(self: *Self, inst: Air.Inst.Index) !void {
13896+
const br = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
13897+
13898+
const block_ty = self.typeOfIndex(br.block_inst);
13899+
const block_tracking = self.inst_tracking.getPtr(br.block_inst).?;
13900+
const loop_data = self.loops.getPtr(br.block_inst).?;
13901+
done: {
13902+
try self.getValue(block_tracking.short, null);
13903+
const src_mcv = try self.resolveInst(br.operand);
13904+
13905+
if (self.reuseOperandAdvanced(inst, br.operand, 0, src_mcv, br.block_inst)) {
13906+
try self.getValue(block_tracking.short, br.block_inst);
13907+
// .long = .none to avoid merging operand and block result stack frames.
13908+
const current_tracking: InstTracking = .{ .long = .none, .short = src_mcv };
13909+
try current_tracking.materializeUnsafe(self, br.block_inst, block_tracking.*);
13910+
for (current_tracking.getRegs()) |src_reg| self.register_manager.freeReg(src_reg);
13911+
break :done;
13912+
}
13913+
13914+
try self.getValue(block_tracking.short, br.block_inst);
13915+
const dst_mcv = block_tracking.short;
13916+
try self.genCopy(block_ty, dst_mcv, try self.resolveInst(br.operand), .{});
13917+
break :done;
13918+
}
13919+
13920+
// Process operand death so that it is properly accounted for in the State below.
13921+
if (self.liveness.operandDies(inst, 0)) {
13922+
if (br.operand.toIndex()) |op_inst| try self.processDeath(op_inst);
13923+
}
13924+
13925+
try self.restoreState(loop_data.state, &.{}, .{
13926+
.emit_instructions = true,
13927+
.update_tracking = false,
13928+
.resurrect = false,
13929+
.close_scope = false,
13930+
});
13931+
13932+
// Emit a jump with a relocation. It will be patched up after the block ends.
13933+
// Leave the jump offset undefined
13934+
_ = try self.asmJmpReloc(loop_data.jmp_target);
13935+
13936+
// Stop tracking block result without forgetting tracking info
13937+
try self.freeValue(block_tracking.short);
13938+
13939+
self.finishAirBookkeeping();
13940+
}
13941+
1381813942
fn performReloc(self: *Self, reloc: Mir.Inst.Index) void {
1381913943
const next_inst: u32 = @intCast(self.mir_instructions.len);
1382013944
switch (self.mir_instructions.items(.tag)[reloc]) {
@@ -13891,7 +14015,7 @@ fn airBr(self: *Self, inst: Air.Inst.Index) !void {
1389114015

1389214016
fn airRepeat(self: *Self, inst: Air.Inst.Index) !void {
1389314017
const loop_inst = self.air.instructions.items(.data)[@intFromEnum(inst)].repeat.loop_inst;
13894-
const repeat_info = self.loop_repeat_info.get(loop_inst).?;
14018+
const repeat_info = self.loops.get(loop_inst).?;
1389514019
try self.restoreState(repeat_info.state, &.{}, .{
1389614020
.emit_instructions = true,
1389714021
.update_tracking = false,
@@ -19578,7 +19702,10 @@ fn typeOf(self: *Self, inst: Air.Inst.Ref) Type {
1957819702
fn typeOfIndex(self: *Self, inst: Air.Inst.Index) Type {
1957919703
const pt = self.pt;
1958019704
const zcu = pt.zcu;
19581-
return self.air.typeOfIndex(inst, &zcu.intern_pool);
19705+
return switch (self.air.instructions.items(.tag)[@intFromEnum(inst)]) {
19706+
.loop_switch_br => self.typeOf(self.air.unwrapSwitch(inst).operand),
19707+
else => self.air.typeOfIndex(inst, &zcu.intern_pool),
19708+
};
1958219709
}
1958319710

1958419711
fn intCompilerRtAbiName(int_bits: u32) u8 {

test/behavior/switch_loop.zig

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ const expect = std.testing.expect;
44

55
test "simple switch loop" {
66
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
7-
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
87
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
98
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
109
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
@@ -30,7 +29,6 @@ test "simple switch loop" {
3029

3130
test "switch loop with ranges" {
3231
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
33-
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
3432
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
3533
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
3634
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
@@ -53,7 +51,6 @@ test "switch loop with ranges" {
5351

5452
test "switch loop on enum" {
5553
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
56-
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
5754
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
5855
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
5956
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
@@ -79,7 +76,6 @@ test "switch loop on enum" {
7976

8077
test "switch loop on tagged union" {
8178
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
82-
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
8379
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
8480
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
8581
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
@@ -113,7 +109,6 @@ test "switch loop on tagged union" {
113109

114110
test "switch loop dispatching instructions" {
115111
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
116-
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
117112
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
118113
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
119114
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
@@ -165,7 +160,6 @@ test "switch loop dispatching instructions" {
165160

166161
test "switch loop with pointer capture" {
167162
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
168-
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
169163
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
170164
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
171165
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO

0 commit comments

Comments
 (0)