| author | |
| committer | |
| log | e5c439a16ddb34e3b59e3e6998aa7dc61f652d88 |
| tree | 3c2c0f83afe51857c41f4e12ed82877c9728450d |
| parent | a76d8ca29b98c4898d1db7db85ee1e6a781b1c0d |
Closes #189593 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 | 12396 | fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { |
| 12397 | 12397 | const mod = self.bin_file.comp.module.?; |
| 12398 | 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; | |
| 12400 | 12401 | |
| 12401 | 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 | 12429 | switch (ty.zigTypeTag(mod)) { |
| 12403 | 12430 | .Float => { |
| 12404 | 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 | 12462 | }; |
| 12436 | 12463 | } |
| 12437 | 12464 | }, |
| 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)); | |
| 12440 | 12470 | |
| 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); | |
| 12442 | 12474 | |
| 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 | } | |
| 12457 | 12487 | |
| 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 | } | |
| 12466 | 12525 | |
| 12467 | 12526 | switch (ty.zigTypeTag(mod)) { |
| 12468 | 12527 | else => { |
| ... | ... | @@ -12775,6 +12834,7 @@ fn airCmp(self: *Self, inst: Air.Inst.Index, op: math.CompareOperator) !void { |
| 12775 | 12834 | } |
| 12776 | 12835 | }; |
| 12777 | 12836 | |
| 12837 | if (null_compare) |reloc| self.performReloc(reloc); | |
| 12778 | 12838 | self.eflags_inst = inst; |
| 12779 | 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 | 4140 | if (need_cast) try writer.writeAll("(void*)"); |
| 4141 | 4141 | try f.writeCValue(writer, lhs, .Other); |
| 4142 | 4142 | try v.elem(f, writer); |
| 4143 | try writer.writeByte(' '); | |
| 4144 | 4143 | try writer.writeAll(compareOperatorC(operator)); |
| 4145 | try writer.writeByte(' '); | |
| 4146 | 4144 | if (need_cast) try writer.writeAll("(void*)"); |
| 4147 | 4145 | try f.writeCValue(writer, rhs, .Other); |
| 4148 | 4146 | try v.elem(f, writer); |
| ... | ... | @@ -4181,41 +4179,28 @@ fn airEquality( |
| 4181 | 4179 | const writer = f.object.writer(); |
| 4182 | 4180 | const inst_ty = f.typeOfIndex(inst); |
| 4183 | 4181 | const local = try f.allocLocal(inst, inst_ty); |
| 4182 | const a = try Assignment.start(f, writer, inst_ty); | |
| 4184 | 4183 | try f.writeCValue(writer, local, .Other); |
| 4185 | try writer.writeAll(" = "); | |
| 4184 | try a.assign(f, writer); | |
| 4186 | 4185 | |
| 4187 | 4186 | 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 { | |
| 4205 | 4199 | try f.writeCValue(writer, lhs, .Other); |
| 4206 | try writer.writeAll(".is_null == "); | |
| 4200 | try writer.writeAll(compareOperatorC(operator)); | |
| 4207 | 4201 | try f.writeCValue(writer, rhs, .Other); |
| 4208 | try writer.writeAll(".is_null));\n"); | |
| 4209 | ||
| 4210 | return local; | |
| 4211 | 4202 | } |
| 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); | |
| 4219 | 4204 | |
| 4220 | 4205 | return local; |
| 4221 | 4206 | } |
| ... | ... | @@ -6322,7 +6307,7 @@ fn airCmpBuiltinCall( |
| 6322 | 6307 | try v.elem(f, writer); |
| 6323 | 6308 | try f.object.dg.renderBuiltinInfo(writer, scalar_ty, info); |
| 6324 | 6309 | try writer.writeByte(')'); |
| 6325 | if (!ref_ret) try writer.print(" {s} {}", .{ | |
| 6310 | if (!ref_ret) try writer.print("{s}{}", .{ | |
| 6326 | 6311 | compareOperatorC(operator), |
| 6327 | 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 | 7653 | |
| 7669 | 7654 | fn compareOperatorC(operator: std.math.CompareOperator) []const u8 { |
| 7670 | 7655 | 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 => " != ", | |
| 7677 | 7662 | }; |
| 7678 | 7663 | } |
| 7679 | 7664 |
test/behavior/optional.zig+77-32| ... | ... | @@ -110,44 +110,89 @@ test "nested optional field in struct" { |
| 110 | 110 | try expect(s.x.?.y == 127); |
| 111 | 111 | } |
| 112 | 112 | |
| 113 | test "equality compare optional with non-optional" { | |
| 113 | test "equality compare optionals and non-optionals" { | |
| 114 | 114 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; |
| 115 | 115 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 116 | 116 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 117 | 117 | |
| 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(); | |
| 120 | 170 | } |
| 121 | 171 | |
| 122 | fn 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 | }; | |
| 172 | test "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)); | |
| 151 | 196 | } |
| 152 | 197 | |
| 153 | 198 | test "unwrap function call with optional pointer return value" { |