authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-02-17 01:27:19+01:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2024-02-25 11:22:10+01:00
loge5c439a16ddb34e3b59e3e6998aa7dc61f652d88
tree3c2c0f83afe51857c41f4e12ed82877c9728450d
parenta76d8ca29b98c4898d1db7db85ee1e6a781b1c0d

x86_64: implement optional comparisons

Closes #18959

3 files changed, 186 insertions(+), 96 deletions(-)

src/arch/x86_64/CodeGen.zig+86-26
......@@ -12396,9 +12396,36 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void {
1239612396fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
1239712397 const mod = self.bin_file.comp.module.?;
1239812398 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
12399 const ty = self.typeOf(bin_op.lhs);
12399 var ty = self.typeOf(bin_op.lhs);
12400 var null_compare: ?Mir.Inst.Index = null;
1240012401
1240112402 const result: Condition = result: {
12403 try self.spillEflagsIfOccupied();
12404
12405 const lhs_mcv = try self.resolveInst(bin_op.lhs);
12406 const lhs_locks: [2]?RegisterLock = switch (lhs_mcv) {
12407 .register => |lhs_reg| .{ self.register_manager.lockRegAssumeUnused(lhs_reg), null },
12408 .register_pair => |lhs_regs| locks: {
12409 const locks = self.register_manager.lockRegsAssumeUnused(2, lhs_regs);
12410 break :locks .{ locks[0], locks[1] };
12411 },
12412 .register_offset => |lhs_ro| .{
12413 self.register_manager.lockRegAssumeUnused(lhs_ro.reg),
12414 null,
12415 },
12416 else => .{null} ** 2,
12417 };
12418 defer for (lhs_locks) |lhs_lock| if (lhs_lock) |lock| self.register_manager.unlockReg(lock);
12419
12420 const rhs_mcv = try self.resolveInst(bin_op.rhs);
12421 const rhs_locks: [2]?RegisterLock = switch (rhs_mcv) {
12422 .register => |rhs_reg| .{ self.register_manager.lockReg(rhs_reg), null },
12423 .register_pair => |rhs_regs| self.register_manager.lockRegs(2, rhs_regs),
12424 .register_offset => |rhs_ro| .{ self.register_manager.lockReg(rhs_ro.reg), null },
12425 else => .{null} ** 2,
12426 };
12427 defer for (rhs_locks) |rhs_lock| if (rhs_lock) |lock| self.register_manager.unlockReg(lock);
12428
1240212429 switch (ty.zigTypeTag(mod)) {
1240312430 .Float => {
1240412431 const float_bits = ty.floatBits(self.target.*);
......@@ -12435,34 +12462,66 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
1243512462 };
1243612463 }
1243712464 },
12438 else => {},
12439 }
12465 .Optional => if (!ty.optionalReprIsPayload(mod)) {
12466 const opt_ty = ty;
12467 const opt_abi_size: u31 = @intCast(opt_ty.abiSize(mod));
12468 ty = opt_ty.optionalChild(mod);
12469 const payload_abi_size: u31 = @intCast(ty.abiSize(mod));
1244012470
12441 try self.spillEflagsIfOccupied();
12471 const temp_lhs_reg = try self.register_manager.allocReg(null, abi.RegisterClass.gp);
12472 const temp_lhs_lock = self.register_manager.lockRegAssumeUnused(temp_lhs_reg);
12473 defer self.register_manager.unlockReg(temp_lhs_lock);
1244212474
12443 const lhs_mcv = try self.resolveInst(bin_op.lhs);
12444 const lhs_locks: [2]?RegisterLock = switch (lhs_mcv) {
12445 .register => |lhs_reg| .{ self.register_manager.lockRegAssumeUnused(lhs_reg), null },
12446 .register_pair => |lhs_regs| locks: {
12447 const locks = self.register_manager.lockRegsAssumeUnused(2, lhs_regs);
12448 break :locks .{ locks[0], locks[1] };
12449 },
12450 .register_offset => |lhs_ro| .{
12451 self.register_manager.lockRegAssumeUnused(lhs_ro.reg),
12452 null,
12453 },
12454 else => .{null} ** 2,
12455 };
12456 defer for (lhs_locks) |lhs_lock| if (lhs_lock) |lock| self.register_manager.unlockReg(lock);
12475 if (lhs_mcv.isMemory()) try self.asmRegisterMemory(
12476 .{ ._, .mov },
12477 temp_lhs_reg.to8(),
12478 try lhs_mcv.address().offset(payload_abi_size).deref().mem(self, .byte),
12479 ) else {
12480 try self.genSetReg(temp_lhs_reg, opt_ty, lhs_mcv, .{});
12481 try self.asmRegisterImmediate(
12482 .{ ._r, .sh },
12483 registerAlias(temp_lhs_reg, opt_abi_size),
12484 Immediate.u(payload_abi_size * 8),
12485 );
12486 }
1245712487
12458 const rhs_mcv = try self.resolveInst(bin_op.rhs);
12459 const rhs_locks: [2]?RegisterLock = switch (rhs_mcv) {
12460 .register => |rhs_reg| .{ self.register_manager.lockReg(rhs_reg), null },
12461 .register_pair => |rhs_regs| self.register_manager.lockRegs(2, rhs_regs),
12462 .register_offset => |rhs_ro| .{ self.register_manager.lockReg(rhs_ro.reg), null },
12463 else => .{null} ** 2,
12464 };
12465 defer for (rhs_locks) |rhs_lock| if (rhs_lock) |lock| self.register_manager.unlockReg(lock);
12488 const payload_compare = payload_compare: {
12489 if (rhs_mcv.isMemory()) {
12490 const rhs_mem =
12491 try rhs_mcv.address().offset(payload_abi_size).deref().mem(self, .byte);
12492 try self.asmMemoryRegister(.{ ._, .@"test" }, rhs_mem, temp_lhs_reg.to8());
12493 const payload_compare = try self.asmJccReloc(.nz, undefined);
12494 try self.asmRegisterMemory(.{ ._, .cmp }, temp_lhs_reg.to8(), rhs_mem);
12495 break :payload_compare payload_compare;
12496 }
12497
12498 const temp_rhs_reg = try self.copyToTmpRegister(opt_ty, rhs_mcv);
12499 const temp_rhs_lock = self.register_manager.lockRegAssumeUnused(temp_rhs_reg);
12500 defer self.register_manager.unlockReg(temp_rhs_lock);
12501
12502 try self.asmRegisterImmediate(
12503 .{ ._r, .sh },
12504 registerAlias(temp_rhs_reg, opt_abi_size),
12505 Immediate.u(payload_abi_size * 8),
12506 );
12507 try self.asmRegisterRegister(
12508 .{ ._, .@"test" },
12509 temp_lhs_reg.to8(),
12510 temp_rhs_reg.to8(),
12511 );
12512 const payload_compare = try self.asmJccReloc(.nz, undefined);
12513 try self.asmRegisterRegister(
12514 .{ ._, .cmp },
12515 temp_lhs_reg.to8(),
12516 temp_rhs_reg.to8(),
12517 );
12518 break :payload_compare payload_compare;
12519 };
12520 null_compare = try self.asmJmpReloc(undefined);
12521 self.performReloc(payload_compare);
12522 },
12523 else => {},
12524 }
1246612525
1246712526 switch (ty.zigTypeTag(mod)) {
1246812527 else => {
......@@ -12775,6 +12834,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
1277512834 }
1277612835 };
1277712836
12837 if (null_compare) |reloc| self.performReloc(reloc);
1277812838 self.eflags_inst = inst;
1277912839 return self.finishAir(inst, .{ .eflags = result }, .{ bin_op.lhs, bin_op.rhs, .none });
1278012840}
src/codegen/c.zig+23-38
......@@ -4140,9 +4140,7 @@ fn airCmpOp(
41404140 if (need_cast) try writer.writeAll("(void*)");
41414141 try f.writeCValue(writer, lhs, .Other);
41424142 try v.elem(f, writer);
4143 try writer.writeByte(' ');
41444143 try writer.writeAll(compareOperatorC(operator));
4145 try writer.writeByte(' ');
41464144 if (need_cast) try writer.writeAll("(void*)");
41474145 try f.writeCValue(writer, rhs, .Other);
41484146 try v.elem(f, writer);
......@@ -4181,41 +4179,28 @@ fn airEquality(
41814179 const writer = f.object.writer();
41824180 const inst_ty = f.typeOfIndex(inst);
41834181 const local = try f.allocLocal(inst, inst_ty);
4182 const a = try Assignment.start(f, writer, inst_ty);
41844183 try f.writeCValue(writer, local, .Other);
4185 try writer.writeAll(" = ");
4184 try a.assign(f, writer);
41864185
41874186 if (operand_ty.zigTypeTag(mod) == .Optional and !operand_ty.optionalReprIsPayload(mod)) {
4188 // (A && B) || (C && (A == B))
4189 // A = lhs.is_null ; B = rhs.is_null ; C = rhs.payload == lhs.payload
4190
4191 switch (operator) {
4192 .eq => {},
4193 .neq => try writer.writeByte('!'),
4194 else => unreachable,
4195 }
4196 try writer.writeAll("((");
4197 try f.writeCValue(writer, lhs, .Other);
4198 try writer.writeAll(".is_null && ");
4199 try f.writeCValue(writer, rhs, .Other);
4200 try writer.writeAll(".is_null) || (");
4201 try f.writeCValue(writer, lhs, .Other);
4202 try writer.writeAll(".payload == ");
4203 try f.writeCValue(writer, rhs, .Other);
4204 try writer.writeAll(".payload && ");
4187 try f.writeCValueMember(writer, lhs, .{ .identifier = "is_null" });
4188 try writer.writeAll(" || ");
4189 try f.writeCValueMember(writer, rhs, .{ .identifier = "is_null" });
4190 try writer.writeAll(" ? ");
4191 try f.writeCValueMember(writer, lhs, .{ .identifier = "is_null" });
4192 try writer.writeAll(compareOperatorC(operator));
4193 try f.writeCValueMember(writer, rhs, .{ .identifier = "is_null" });
4194 try writer.writeAll(" : ");
4195 try f.writeCValueMember(writer, lhs, .{ .identifier = "payload" });
4196 try writer.writeAll(compareOperatorC(operator));
4197 try f.writeCValueMember(writer, rhs, .{ .identifier = "payload" });
4198 } else {
42054199 try f.writeCValue(writer, lhs, .Other);
4206 try writer.writeAll(".is_null == ");
4200 try writer.writeAll(compareOperatorC(operator));
42074201 try f.writeCValue(writer, rhs, .Other);
4208 try writer.writeAll(".is_null));\n");
4209
4210 return local;
42114202 }
4212
4213 try f.writeCValue(writer, lhs, .Other);
4214 try writer.writeByte(' ');
4215 try writer.writeAll(compareOperatorC(operator));
4216 try writer.writeByte(' ');
4217 try f.writeCValue(writer, rhs, .Other);
4218 try writer.writeAll(";\n");
4203 try a.end(f, writer);
42194204
42204205 return local;
42214206}
......@@ -6322,7 +6307,7 @@ fn airCmpBuiltinCall(
63226307 try v.elem(f, writer);
63236308 try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info);
63246309 try writer.writeByte(')');
6325 if (!ref_ret) try writer.print(" {s} {}", .{
6310 if (!ref_ret) try writer.print("{s}{}", .{
63266311 compareOperatorC(operator),
63276312 try f.fmtIntLiteral(Type.i32, try mod.intValue(Type.i32, 0)),
63286313 });
......@@ -7668,12 +7653,12 @@ fn compareOperatorAbbrev(operator: std.math.CompareOperator) []const u8 {
76687653
76697654fn compareOperatorC(operator: std.math.CompareOperator) []const u8 {
76707655 return switch (operator) {
7671 .lt => "<",
7672 .lte => "<=",
7673 .eq => "==",
7674 .gte => ">=",
7675 .gt => ">",
7676 .neq => "!=",
7656 .lt => " < ",
7657 .lte => " <= ",
7658 .eq => " == ",
7659 .gte => " >= ",
7660 .gt => " > ",
7661 .neq => " != ",
76777662 };
76787663}
76797664
test/behavior/optional.zig+77-32
......@@ -110,44 +110,89 @@ test "nested optional field in struct" {
110110 try expect(s.x.?.y == 127);
111111}
112112
113test "equality compare optional with non-optional" {
113test "equality compare optionals and non-optionals" {
114114 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
115115 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
116116 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
117117
118 try test_cmp_optional_non_optional();
119 try comptime test_cmp_optional_non_optional();
118 const S = struct {
119 fn doTheTest() !void {
120 var five: isize = 5;
121 var ten: isize = 10;
122 var opt_null: ?isize = null;
123 var opt_ten: ?isize = 10;
124 _ = .{ &five, &ten, &opt_null, &opt_ten };
125 try expect(opt_null != five);
126 try expect(opt_null != ten);
127 try expect(opt_ten != five);
128 try expect(opt_ten == ten);
129
130 var opt_int: ?isize = null;
131 try expect(opt_int != five);
132 try expect(opt_int != ten);
133 try expect(opt_int == opt_null);
134 try expect(opt_int != opt_ten);
135
136 opt_int = 10;
137 try expect(opt_int != five);
138 try expect(opt_int == ten);
139 try expect(opt_int != opt_null);
140 try expect(opt_int == opt_ten);
141
142 opt_int = five;
143 try expect(opt_int == five);
144 try expect(opt_int != ten);
145 try expect(opt_int != opt_null);
146 try expect(opt_int != opt_ten);
147
148 // test evaluation is always lexical
149 // ensure that the optional isn't always computed before the non-optional
150 var mutable_state: i32 = 0;
151 _ = blk1: {
152 mutable_state += 1;
153 break :blk1 @as(?f64, 10.0);
154 } != blk2: {
155 try expect(mutable_state == 1);
156 break :blk2 @as(f64, 5.0);
157 };
158 _ = blk1: {
159 mutable_state += 1;
160 break :blk1 @as(f64, 10.0);
161 } != blk2: {
162 try expect(mutable_state == 2);
163 break :blk2 @as(?f64, 5.0);
164 };
165 }
166 };
167
168 try S.doTheTest();
169 try comptime S.doTheTest();
120170}
121171
122fn test_cmp_optional_non_optional() !void {
123 var ten: i32 = 10;
124 var opt_ten: ?i32 = 10;
125 var five: i32 = 5;
126 var int_n: ?i32 = null;
127
128 _ = .{ &ten, &opt_ten, &five, &int_n };
129
130 try expect(int_n != ten);
131 try expect(opt_ten == ten);
132 try expect(opt_ten != five);
133
134 // test evaluation is always lexical
135 // ensure that the optional isn't always computed before the non-optional
136 var mutable_state: i32 = 0;
137 _ = blk1: {
138 mutable_state += 1;
139 break :blk1 @as(?f64, 10.0);
140 } != blk2: {
141 try expect(mutable_state == 1);
142 break :blk2 @as(f64, 5.0);
143 };
144 _ = blk1: {
145 mutable_state += 1;
146 break :blk1 @as(f64, 10.0);
147 } != blk2: {
148 try expect(mutable_state == 2);
149 break :blk2 @as(?f64, 5.0);
150 };
172test "compare optionals with modified payloads" {
173 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
174
175 var lhs: ?bool = false;
176 const lhs_payload = &lhs.?;
177 var rhs: ?bool = true;
178 const rhs_payload = &rhs.?;
179 try expect(lhs != rhs and !(lhs == rhs));
180
181 lhs = null;
182 lhs_payload.* = false;
183 rhs = false;
184 try expect(lhs != rhs and !(lhs == rhs));
185
186 lhs = true;
187 rhs = null;
188 rhs_payload.* = true;
189 try expect(lhs != rhs and !(lhs == rhs));
190
191 lhs = null;
192 lhs_payload.* = false;
193 rhs = null;
194 rhs_payload.* = true;
195 try expect(lhs == rhs and !(lhs != rhs));
151196}
152197
153198test "unwrap function call with optional pointer return value" {