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 {...@@ -12396,9 +12396,36 @@ fn airRetLoad(self: *Self, inst: Air.Inst.Index) !void {
12396fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {12396fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
12397 const mod = self.bin_file.comp.module.?;12397 const mod = self.bin_file.comp.module.?;
12398 const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;12398 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
12401 const result: Condition = result: {12402 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
12402 switch (ty.zigTypeTag(mod)) {12429 switch (ty.zigTypeTag(mod)) {
12403 .Float => {12430 .Float => {
12404 const float_bits = ty.floatBits(self.target.*);12431 const float_bits = ty.floatBits(self.target.*);
...@@ -12435,34 +12462,66 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {...@@ -12435,34 +12462,66 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
12435 };12462 };
12436 }12463 }
12437 },12464 },
12438 else => {},12465 .Optional => if (!ty.optionalReprIsPayload(mod)) {
12439 }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);12475 if (lhs_mcv.isMemory()) try self.asmRegisterMemory(
12444 const lhs_locks: [2]?RegisterLock = switch (lhs_mcv) {12476 .{ ._, .mov },
12445 .register => |lhs_reg| .{ self.register_manager.lockRegAssumeUnused(lhs_reg), null },12477 temp_lhs_reg.to8(),
12446 .register_pair => |lhs_regs| locks: {12478 try lhs_mcv.address().offset(payload_abi_size).deref().mem(self, .byte),
12447 const locks = self.register_manager.lockRegsAssumeUnused(2, lhs_regs);12479 ) else {
12448 break :locks .{ locks[0], locks[1] };12480 try self.genSetReg(temp_lhs_reg, opt_ty, lhs_mcv, .{});
12449 },12481 try self.asmRegisterImmediate(
12450 .register_offset => |lhs_ro| .{12482 .{ ._r, .sh },
12451 self.register_manager.lockRegAssumeUnused(lhs_ro.reg),12483 registerAlias(temp_lhs_reg, opt_abi_size),
12452 null,12484 Immediate.u(payload_abi_size * 8),
12453 },12485 );
12454 else => .{null} ** 2,12486 }
12455 };
12456 defer for (lhs_locks) |lhs_lock| if (lhs_lock) |lock| self.register_manager.unlockReg(lock);
1245712487
12458 const rhs_mcv = try self.resolveInst(bin_op.rhs);12488 const payload_compare = payload_compare: {
12459 const rhs_locks: [2]?RegisterLock = switch (rhs_mcv) {12489 if (rhs_mcv.isMemory()) {
12460 .register => |rhs_reg| .{ self.register_manager.lockReg(rhs_reg), null },12490 const rhs_mem =
12461 .register_pair => |rhs_regs| self.register_manager.lockRegs(2, rhs_regs),12491 try rhs_mcv.address().offset(payload_abi_size).deref().mem(self, .byte);
12462 .register_offset => |rhs_ro| .{ self.register_manager.lockReg(rhs_ro.reg), null },12492 try self.asmMemoryRegister(.{ ._, .@"test" }, rhs_mem, temp_lhs_reg.to8());
12463 else => .{null} ** 2,12493 const payload_compare = try self.asmJccReloc(.nz, undefined);
12464 };12494 try self.asmRegisterMemory(.{ ._, .cmp }, temp_lhs_reg.to8(), rhs_mem);
12465 defer for (rhs_locks) |rhs_lock| if (rhs_lock) |lock| self.register_manager.unlockReg(lock);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
12467 switch (ty.zigTypeTag(mod)) {12526 switch (ty.zigTypeTag(mod)) {
12468 else => {12527 else => {
...@@ -12775,6 +12834,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {...@@ -12775,6 +12834,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void {
12775 }12834 }
12776 };12835 };
1277712836
12837 if (null_compare) |reloc| self.performReloc(reloc);
12778 self.eflags_inst = inst;12838 self.eflags_inst = inst;
12779 return self.finishAir(inst, .{ .eflags = result }, .{ bin_op.lhs, bin_op.rhs, .none });12839 return self.finishAir(inst, .{ .eflags = result }, .{ bin_op.lhs, bin_op.rhs, .none });
12780}12840}
src/codegen/c.zig+23-38
...@@ -4140,9 +4140,7 @@ fn airCmpOp(...@@ -4140,9 +4140,7 @@ fn airCmpOp(
4140 if (need_cast) try writer.writeAll("(void*)");4140 if (need_cast) try writer.writeAll("(void*)");
4141 try f.writeCValue(writer, lhs, .Other);4141 try f.writeCValue(writer, lhs, .Other);
4142 try v.elem(f, writer);4142 try v.elem(f, writer);
4143 try writer.writeByte(' ');
4144 try writer.writeAll(compareOperatorC(operator));4143 try writer.writeAll(compareOperatorC(operator));
4145 try writer.writeByte(' ');
4146 if (need_cast) try writer.writeAll("(void*)");4144 if (need_cast) try writer.writeAll("(void*)");
4147 try f.writeCValue(writer, rhs, .Other);4145 try f.writeCValue(writer, rhs, .Other);
4148 try v.elem(f, writer);4146 try v.elem(f, writer);
...@@ -4181,41 +4179,28 @@ fn airEquality(...@@ -4181,41 +4179,28 @@ fn airEquality(
4181 const writer = f.object.writer();4179 const writer = f.object.writer();
4182 const inst_ty = f.typeOfIndex(inst);4180 const inst_ty = f.typeOfIndex(inst);
4183 const local = try f.allocLocal(inst, inst_ty);4181 const local = try f.allocLocal(inst, inst_ty);
4182 const a = try Assignment.start(f, writer, inst_ty);
4184 try f.writeCValue(writer, local, .Other);4183 try f.writeCValue(writer, local, .Other);
4185 try writer.writeAll(" = ");4184 try a.assign(f, writer);
41864185
4187 if (operand_ty.zigTypeTag(mod) == .Optional and !operand_ty.optionalReprIsPayload(mod)) {4186 if (operand_ty.zigTypeTag(mod) == .Optional and !operand_ty.optionalReprIsPayload(mod)) {
4188 // (A && B) || (C && (A == B))4187 try f.writeCValueMember(writer, lhs, .{ .identifier = "is_null" });
4189 // A = lhs.is_null ; B = rhs.is_null ; C = rhs.payload == lhs.payload4188 try writer.writeAll(" || ");
41904189 try f.writeCValueMember(writer, rhs, .{ .identifier = "is_null" });
4191 switch (operator) {4190 try writer.writeAll(" ? ");
4192 .eq => {},4191 try f.writeCValueMember(writer, lhs, .{ .identifier = "is_null" });
4193 .neq => try writer.writeByte('!'),4192 try writer.writeAll(compareOperatorC(operator));
4194 else => unreachable,4193 try f.writeCValueMember(writer, rhs, .{ .identifier = "is_null" });
4195 }4194 try writer.writeAll(" : ");
4196 try writer.writeAll("((");4195 try f.writeCValueMember(writer, lhs, .{ .identifier = "payload" });
4197 try f.writeCValue(writer, lhs, .Other);4196 try writer.writeAll(compareOperatorC(operator));
4198 try writer.writeAll(".is_null && ");4197 try f.writeCValueMember(writer, rhs, .{ .identifier = "payload" });
4199 try f.writeCValue(writer, rhs, .Other);4198 } else {
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 && ");
4205 try f.writeCValue(writer, lhs, .Other);4199 try f.writeCValue(writer, lhs, .Other);
4206 try writer.writeAll(".is_null == ");4200 try writer.writeAll(compareOperatorC(operator));
4207 try f.writeCValue(writer, rhs, .Other);4201 try f.writeCValue(writer, rhs, .Other);
4208 try writer.writeAll(".is_null));\n");
4209
4210 return local;
4211 }4202 }
42124203 try a.end(f, writer);
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");
42194204
4220 return local;4205 return local;
4221}4206}
...@@ -6322,7 +6307,7 @@ fn airCmpBuiltinCall(...@@ -6322,7 +6307,7 @@ fn airCmpBuiltinCall(
6322 try v.elem(f, writer);6307 try v.elem(f, writer);
6323 try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info);6308 try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info);
6324 try writer.writeByte(')');6309 try writer.writeByte(')');
6325 if (!ref_ret) try writer.print(" {s} {}", .{6310 if (!ref_ret) try writer.print("{s}{}", .{
6326 compareOperatorC(operator),6311 compareOperatorC(operator),
6327 try f.fmtIntLiteral(Type.i32, try mod.intValue(Type.i32, 0)),6312 try f.fmtIntLiteral(Type.i32, try mod.intValue(Type.i32, 0)),
6328 });6313 });
...@@ -7668,12 +7653,12 @@ fn compareOperatorAbbrev(operator: std.math.CompareOperator) []const u8 {...@@ -7668,12 +7653,12 @@ fn compareOperatorAbbrev(operator: std.math.CompareOperator) []const u8 {
76687653
7669fn compareOperatorC(operator: std.math.CompareOperator) []const u8 {7654fn compareOperatorC(operator: std.math.CompareOperator) []const u8 {
7670 return switch (operator) {7655 return switch (operator) {
7671 .lt => "<",7656 .lt => " < ",
7672 .lte => "<=",7657 .lte => " <= ",
7673 .eq => "==",7658 .eq => " == ",
7674 .gte => ">=",7659 .gte => " >= ",
7675 .gt => ">",7660 .gt => " > ",
7676 .neq => "!=",7661 .neq => " != ",
7677 };7662 };
7678}7663}
76797664
test/behavior/optional.zig+77-32
...@@ -110,44 +110,89 @@ test "nested optional field in struct" {...@@ -110,44 +110,89 @@ test "nested optional field in struct" {
110 try expect(s.x.?.y == 127);110 try expect(s.x.?.y == 127);
111}111}
112112
113test "equality compare optional with non-optional" {113test "equality compare optionals and non-optionals" {
114 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;114 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
115 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO115 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
116 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO116 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
117117
118 try test_cmp_optional_non_optional();118 const S = struct {
119 try comptime test_cmp_optional_non_optional();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();
120}170}
121171
122fn test_cmp_optional_non_optional() !void {172test "compare optionals with modified payloads" {
123 var ten: i32 = 10;173 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
124 var opt_ten: ?i32 = 10;174
125 var five: i32 = 5;175 var lhs: ?bool = false;
126 var int_n: ?i32 = null;176 const lhs_payload = &lhs.?;
127177 var rhs: ?bool = true;
128 _ = .{ &ten, &opt_ten, &five, &int_n };178 const rhs_payload = &rhs.?;
129179 try expect(lhs != rhs and !(lhs == rhs));
130 try expect(int_n != ten);180
131 try expect(opt_ten == ten);181 lhs = null;
132 try expect(opt_ten != five);182 lhs_payload.* = false;
133183 rhs = false;
134 // test evaluation is always lexical184 try expect(lhs != rhs and !(lhs == rhs));
135 // ensure that the optional isn't always computed before the non-optional185
136 var mutable_state: i32 = 0;186 lhs = true;
137 _ = blk1: {187 rhs = null;
138 mutable_state += 1;188 rhs_payload.* = true;
139 break :blk1 @as(?f64, 10.0);189 try expect(lhs != rhs and !(lhs == rhs));
140 } != blk2: {190
141 try expect(mutable_state == 1);191 lhs = null;
142 break :blk2 @as(f64, 5.0);192 lhs_payload.* = false;
143 };193 rhs = null;
144 _ = blk1: {194 rhs_payload.* = true;
145 mutable_state += 1;195 try expect(lhs == rhs and !(lhs != rhs));
146 break :blk1 @as(f64, 10.0);
147 } != blk2: {
148 try expect(mutable_state == 2);
149 break :blk2 @as(?f64, 5.0);
150 };
151}196}
152197
153test "unwrap function call with optional pointer return value" {198test "unwrap function call with optional pointer return value" {