authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-12-18 04:42:13+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2021-12-21 01:41:51+01:00
logf3d635b6683ba4a53f82ae8087b1cf78552abac5
treebd038a7404df4e79170915e07528694768f44f7c
parent28bcd7dbdda7fb2c2fe80dbdb5981479a04e973a

stage2: @addWithOverflow


15 files changed, 311 insertions(+), 82 deletions(-)

lib/std/math/big/int.zig+23-13
......@@ -443,12 +443,12 @@ pub const Mutable = struct {
443443 }
444444 }
445445
446 /// r = a + b with 2s-complement wrapping semantics.
446 /// r = a + b with 2s-complement wrapping semantics. Returns whether overflow occurred.
447447 /// r, a and b may be aliases
448448 ///
449449 /// Asserts the result fits in `r`. An upper bound on the number of limbs needed by
450450 /// r is `calcTwosCompLimbCount(bit_count)`.
451 pub fn addWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) void {
451 pub fn addWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) bool {
452452 const req_limbs = calcTwosCompLimbCount(bit_count);
453453
454454 // Slice of the upper bits if they exist, these will be ignored and allows us to use addCarry to determine
......@@ -463,6 +463,7 @@ pub const Mutable = struct {
463463 .limbs = b.limbs[0..math.min(req_limbs, b.limbs.len)],
464464 };
465465
466 var carry_truncated = false;
466467 if (r.addCarry(x, y)) {
467468 // There are two possibilities here:
468469 // - We overflowed req_limbs. In this case, the carry is ignored, as it would be removed by
......@@ -473,10 +474,17 @@ pub const Mutable = struct {
473474 if (msl < req_limbs) {
474475 r.limbs[msl] = 1;
475476 r.len = req_limbs;
477 } else {
478 carry_truncated = true;
476479 }
477480 }
478481
479 r.truncate(r.toConst(), signedness, bit_count);
482 if (!r.toConst().fitsInTwosComp(signedness, bit_count)) {
483 r.truncate(r.toConst(), signedness, bit_count);
484 return true;
485 }
486
487 return carry_truncated;
480488 }
481489
482490 /// r = a + b with 2s-complement saturating semantics.
......@@ -581,13 +589,13 @@ pub const Mutable = struct {
581589 r.add(a, b.negate());
582590 }
583591
584 /// r = a - b with 2s-complement wrapping semantics.
592 /// r = a - b with 2s-complement wrapping semantics. Returns whether any overflow occured.
585593 ///
586594 /// r, a and b may be aliases
587595 /// Asserts the result fits in `r`. An upper bound on the number of limbs needed by
588596 /// r is `calcTwosCompLimbCount(bit_count)`.
589 pub fn subWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) void {
590 r.addWrap(a, b.negate(), signedness, bit_count);
597 pub fn subWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) bool {
598 return r.addWrap(a, b.negate(), signedness, bit_count);
591599 }
592600
593601 /// r = a - b with 2s-complement saturating semantics.
......@@ -1039,7 +1047,7 @@ pub const Mutable = struct {
10391047 pub fn bitNotWrap(r: *Mutable, a: Const, signedness: Signedness, bit_count: usize) void {
10401048 r.copy(a.negate());
10411049 const negative_one = Const{ .limbs = &.{1}, .positive = false };
1042 r.addWrap(r.toConst(), negative_one, signedness, bit_count);
1050 _ = r.addWrap(r.toConst(), negative_one, signedness, bit_count);
10431051 }
10441052
10451053 /// r = a | b under 2s complement semantics.
......@@ -2443,17 +2451,18 @@ pub const Managed = struct {
24432451 r.setMetadata(m.positive, m.len);
24442452 }
24452453
2446 /// r = a + b with 2s-complement wrapping semantics.
2454 /// r = a + b with 2s-complement wrapping semantics. Returns whether any overflow occured.
24472455 ///
24482456 /// r, a and b may be aliases. If r aliases a or b, then caller must call
24492457 /// `r.ensureTwosCompCapacity` prior to calling `add`.
24502458 ///
24512459 /// Returns an error if memory could not be allocated.
2452 pub fn addWrap(r: *Managed, a: Const, b: Const, signedness: Signedness, bit_count: usize) Allocator.Error!void {
2460 pub fn addWrap(r: *Managed, a: Const, b: Const, signedness: Signedness, bit_count: usize) Allocator.Error!bool {
24532461 try r.ensureTwosCompCapacity(bit_count);
24542462 var m = r.toMutable();
2455 m.addWrap(a, b, signedness, bit_count);
2463 const wrapped = m.addWrap(a, b, signedness, bit_count);
24562464 r.setMetadata(m.positive, m.len);
2465 return wrapped;
24572466 }
24582467
24592468 /// r = a + b with 2s-complement saturating semantics.
......@@ -2481,17 +2490,18 @@ pub const Managed = struct {
24812490 r.setMetadata(m.positive, m.len);
24822491 }
24832492
2484 /// r = a - b with 2s-complement wrapping semantics.
2493 /// r = a - b with 2s-complement wrapping semantics. Returns whether any overflow occured.
24852494 ///
24862495 /// r, a and b may be aliases. If r aliases a or b, then caller must call
24872496 /// `r.ensureTwosCompCapacity` prior to calling `add`.
24882497 ///
24892498 /// Returns an error if memory could not be allocated.
2490 pub fn subWrap(r: *Managed, a: Const, b: Const, signedness: Signedness, bit_count: usize) Allocator.Error!void {
2499 pub fn subWrap(r: *Managed, a: Const, b: Const, signedness: Signedness, bit_count: usize) Allocator.Error!bool {
24912500 try r.ensureTwosCompCapacity(bit_count);
24922501 var m = r.toMutable();
2493 m.subWrap(a, b, signedness, bit_count);
2502 const wrapped = m.subWrap(a, b, signedness, bit_count);
24942503 r.setMetadata(m.positive, m.len);
2504 return wrapped;
24952505 }
24962506
24972507 /// r = a - b with 2s-complement saturating semantics.
lib/std/math/big/int_test.zig+16-8
......@@ -590,8 +590,9 @@ test "big.int addWrap single-single, unsigned" {
590590 var b = try Managed.initSet(testing.allocator, 10);
591591 defer b.deinit();
592592
593 try a.addWrap(a.toConst(), b.toConst(), .unsigned, 17);
593 const wrapped = try a.addWrap(a.toConst(), b.toConst(), .unsigned, 17);
594594
595 try testing.expect(wrapped);
595596 try testing.expect((try a.to(u17)) == 9);
596597}
597598
......@@ -602,8 +603,9 @@ test "big.int subWrap single-single, unsigned" {
602603 var b = try Managed.initSet(testing.allocator, maxInt(u17));
603604 defer b.deinit();
604605
605 try a.subWrap(a.toConst(), b.toConst(), .unsigned, 17);
606 const wrapped = try a.subWrap(a.toConst(), b.toConst(), .unsigned, 17);
606607
608 try testing.expect(wrapped);
607609 try testing.expect((try a.to(u17)) == 1);
608610}
609611
......@@ -614,8 +616,9 @@ test "big.int addWrap multi-multi, unsigned, limb aligned" {
614616 var b = try Managed.initSet(testing.allocator, maxInt(DoubleLimb));
615617 defer b.deinit();
616618
617 try a.addWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb));
619 const wrapped = try a.addWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb));
618620
621 try testing.expect(wrapped);
619622 try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 1);
620623}
621624
......@@ -626,8 +629,9 @@ test "big.int subWrap single-multi, unsigned, limb aligned" {
626629 var b = try Managed.initSet(testing.allocator, maxInt(DoubleLimb) + 100);
627630 defer b.deinit();
628631
629 try a.subWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb));
632 const wrapped = try a.subWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb));
630633
634 try testing.expect(wrapped);
631635 try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 88);
632636}
633637
......@@ -638,8 +642,9 @@ test "big.int addWrap single-single, signed" {
638642 var b = try Managed.initSet(testing.allocator, 1 + 1 + maxInt(u21));
639643 defer b.deinit();
640644
641 try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21));
645 const wrapped = try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21));
642646
647 try testing.expect(wrapped);
643648 try testing.expect((try a.to(i21)) == minInt(i21));
644649}
645650
......@@ -650,8 +655,9 @@ test "big.int subWrap single-single, signed" {
650655 var b = try Managed.initSet(testing.allocator, 1);
651656 defer b.deinit();
652657
653 try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21));
658 const wrapped = try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21));
654659
660 try testing.expect(wrapped);
655661 try testing.expect((try a.to(i21)) == maxInt(i21));
656662}
657663
......@@ -662,8 +668,9 @@ test "big.int addWrap multi-multi, signed, limb aligned" {
662668 var b = try Managed.initSet(testing.allocator, maxInt(SignedDoubleLimb));
663669 defer b.deinit();
664670
665 try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb));
671 const wrapped = try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb));
666672
673 try testing.expect(wrapped);
667674 try testing.expect((try a.to(SignedDoubleLimb)) == -2);
668675}
669676
......@@ -674,8 +681,9 @@ test "big.int subWrap single-multi, signed, limb aligned" {
674681 var b = try Managed.initSet(testing.allocator, 1);
675682 defer b.deinit();
676683
677 try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb));
684 const wrapped = try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb));
678685
686 try testing.expect(wrapped);
679687 try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb));
680688}
681689
src/Air.zig+8
......@@ -135,6 +135,12 @@ pub const Inst = struct {
135135 /// is the same as both operands.
136136 /// Uses the `bin_op` field.
137137 min,
138 /// Integer addition with overflow. Both operands are guaranteed to be the same type,
139 /// and the result is bool. The wrapped value is written to the pointer given by the in
140 /// operand of the `pl_op` field. Payload is `Bin` with `lhs` and `rhs` the relevant types
141 /// of the operation.
142 /// Uses the `pl_op` field with payload `Bin`.
143 add_with_overflow,
138144 /// Allocates stack local memory.
139145 /// Uses the `ty` field.
140146 alloc,
......@@ -804,6 +810,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
804810 const ptr_ty = air.typeOf(datas[inst].pl_op.operand);
805811 return ptr_ty.elemType();
806812 },
813
814 .add_with_overflow => return Type.initTag(.bool),
807815 }
808816}
809817
src/Liveness.zig+1-1
......@@ -381,7 +381,7 @@ fn analyzeInst(
381381 const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data;
382382 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none });
383383 },
384 .memset, .memcpy => {
384 .memset, .memcpy, .add_with_overflow => {
385385 const pl_op = inst_datas[inst].pl_op;
386386 const extra = a.air.extraData(Air.Bin, pl_op.payload).data;
387387 return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs });
src/Sema.zig+94-5
......@@ -1051,10 +1051,10 @@ fn zirExtended(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
10511051 .@"asm" => return sema.zirAsm( block, extended, inst),
10521052 .typeof_peer => return sema.zirTypeofPeer( block, extended),
10531053 .compile_log => return sema.zirCompileLog( block, extended),
1054 .add_with_overflow => return sema.zirOverflowArithmetic(block, extended),
1055 .sub_with_overflow => return sema.zirOverflowArithmetic(block, extended),
1056 .mul_with_overflow => return sema.zirOverflowArithmetic(block, extended),
1057 .shl_with_overflow => return sema.zirOverflowArithmetic(block, extended),
1054 .add_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode),
1055 .sub_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode),
1056 .mul_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode),
1057 .shl_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode),
10581058 .c_undef => return sema.zirCUndef( block, extended),
10591059 .c_include => return sema.zirCInclude( block, extended),
10601060 .c_define => return sema.zirCDefine( block, extended),
......@@ -7310,6 +7310,7 @@ fn zirOverflowArithmetic(
73107310 sema: *Sema,
73117311 block: *Block,
73127312 extended: Zir.Inst.Extended.InstData,
7313 zir_tag: Zir.Inst.Extended,
73137314) CompileError!Air.Inst.Ref {
73147315 const tracy = trace(@src());
73157316 defer tracy.end();
......@@ -7317,7 +7318,95 @@ fn zirOverflowArithmetic(
73177318 const extra = sema.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data;
73187319 const src: LazySrcLoc = .{ .node_offset = extra.node };
73197320
7320 return sema.fail(block, src, "TODO implement Sema.zirOverflowArithmetic", .{});
7321 const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
7322 const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node };
7323 const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = extra.node };
7324
7325 const lhs = sema.resolveInst(extra.lhs);
7326 const rhs = sema.resolveInst(extra.rhs);
7327 const ptr = sema.resolveInst(extra.ptr);
7328
7329 const lhs_ty = sema.typeOf(lhs);
7330
7331 // Note, the types of lhs/rhs (also for shifting)/ptr are already correct as ensured by astgen.
7332 const dest_ty = lhs_ty;
7333 if (dest_ty.zigTypeTag() != .Int) {
7334 return sema.fail(block, src, "expected integer type, found '{}'", .{dest_ty});
7335 }
7336
7337 const target = sema.mod.getTarget();
7338
7339 const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs);
7340 const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs);
7341
7342 const result: struct {
7343 overflowed: enum { yes, no, undef },
7344 wrapped: Air.Inst.Ref,
7345 } = result: {
7346 const air_tag: Air.Inst.Tag = switch (zir_tag) {
7347 .add_with_overflow => blk: {
7348 // If either of the arguments is zero, `false` is returned and the other is stored
7349 // to the result, even if it is undefined..
7350 // Otherwise, if either of the argument is undefined, undefined is returned.
7351 if (maybe_lhs_val) |lhs_val| {
7352 if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) {
7353 break :result .{ .overflowed = .no, .wrapped = rhs };
7354 }
7355 }
7356 if (maybe_rhs_val) |rhs_val| {
7357 if (!rhs_val.isUndef() and rhs_val.compareWithZero(.eq)) {
7358 break :result .{ .overflowed = .no, .wrapped = lhs };
7359 }
7360 }
7361 if (maybe_lhs_val) |lhs_val| {
7362 if (maybe_rhs_val) |rhs_val| {
7363 if (lhs_val.isUndef() or rhs_val.isUndef()) {
7364 break :result .{ .overflowed = .undef, .wrapped = try sema.addConstUndef(dest_ty) };
7365 }
7366
7367 const result = try lhs_val.intAddWithOverflow(rhs_val, dest_ty, sema.arena, target);
7368 const inst = try sema.addConstant(
7369 dest_ty,
7370 result.wrapped_result,
7371 );
7372
7373 if (result.overflowed) {
7374 break :result .{ .overflowed = .yes, .wrapped = inst };
7375 } else {
7376 break :result .{ .overflowed = .no, .wrapped = inst };
7377 }
7378 }
7379 }
7380
7381 break :blk .add_with_overflow;
7382 },
7383 .sub_with_overflow,
7384 .mul_with_overflow,
7385 .shl_with_overflow,
7386 => return sema.fail(block, src, "TODO implement Sema.zirOverflowArithmetic for {}", .{zir_tag}),
7387 else => unreachable,
7388 };
7389
7390 try sema.requireRuntimeBlock(block, src);
7391 return block.addInst(.{
7392 .tag = air_tag,
7393 .data = .{ .pl_op = .{
7394 .operand = ptr,
7395 .payload = try sema.addExtra(Air.Bin{
7396 .lhs = lhs,
7397 .rhs = rhs,
7398 }),
7399 } },
7400 });
7401 };
7402
7403 try sema.storePtr2(block, src, ptr, ptr_src, result.wrapped, src, .store);
7404
7405 return switch (result.overflowed) {
7406 .yes => Air.Inst.Ref.bool_true,
7407 .no => Air.Inst.Ref.bool_false,
7408 .undef => try sema.addConstUndef(Type.initTag(.bool)),
7409 };
73217410}
73227411
73237412fn analyzeArithmetic(
src/arch/aarch64/CodeGen.zig+7
......@@ -521,6 +521,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
521521 .max => try self.airMax(inst),
522522 .slice => try self.airSlice(inst),
523523
524 .add_with_overflow => try self.airAddWithOverflow(inst),
525
524526 .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
525527
526528 .cmp_lt => try self.airCmp(inst, .lt),
......@@ -968,6 +970,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
968970 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
969971}
970972
973fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
974 _ = inst;
975 return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch});
976}
977
971978fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
972979 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
973980 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
src/arch/arm/CodeGen.zig+7
......@@ -519,6 +519,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
519519 .max => try self.airMax(inst),
520520 .slice => try self.airSlice(inst),
521521
522 .add_with_overflow => try self.airAddWithOverflow(inst),
523
522524 .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
523525
524526 .cmp_lt => try self.airCmp(inst, .lt),
......@@ -998,6 +1000,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
9981000 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
9991001}
10001002
1003fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
1004 _ = inst;
1005 return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch});
1006}
1007
10011008fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
10021009 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
10031010 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
src/arch/riscv64/CodeGen.zig+7
......@@ -500,6 +500,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
500500 .max => try self.airMax(inst),
501501 .slice => try self.airSlice(inst),
502502
503 .add_with_overflow => try self.airAddWithOverflow(inst),
504
503505 .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
504506
505507 .cmp_lt => try self.airCmp(inst, .lt),
......@@ -913,6 +915,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
913915 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
914916}
915917
918fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
919 _ = inst;
920 return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch});
921}
922
916923fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
917924 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
918925 const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch});
src/arch/x86_64/CodeGen.zig+7
......@@ -553,6 +553,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
553553 .max => try self.airMax(inst),
554554 .slice => try self.airSlice(inst),
555555
556 .add_with_overflow => try self.airAddWithOverflow(inst),
557
556558 .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
557559
558560 .cmp_lt => try self.airCmp(inst, .lt),
......@@ -1027,6 +1029,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void {
10271029 return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none });
10281030}
10291031
1032fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void {
1033 _ = inst;
1034 return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch});
1035}
1036
10301037fn airDiv(self: *Self, inst: Air.Inst.Index) !void {
10311038 const bin_op = self.air.instructions.items(.data)[inst].bin_op;
10321039 const result: MCValue = if (self.liveness.isUnused(inst))
src/codegen/c.zig+8
......@@ -1155,6 +1155,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
11551155 .mul_sat => try airSatOp(f, inst, "muls_"),
11561156 .shl_sat => try airSatOp(f, inst, "shls_"),
11571157
1158 .add_with_overflow => try airAddWithOverflow(f, inst),
1159
11581160 .min => try airMinMax(f, inst, "<"),
11591161 .max => try airMinMax(f, inst, ">"),
11601162
......@@ -1864,6 +1866,12 @@ fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue {
18641866 return ret;
18651867}
18661868
1869fn airAddWithOverflow(f: *Function, inst: Air.Inst.Index) !CValue {
1870 _ = f;
1871 _ = inst;
1872 return f.fail("TODO add with overflow", .{});
1873}
1874
18671875fn airNot(f: *Function, inst: Air.Inst.Index) !CValue {
18681876 if (f.liveness.isUnused(inst))
18691877 return CValue.none;
src/codegen/llvm.zig+37-6
......@@ -1714,6 +1714,8 @@ pub const FuncGen = struct {
17141714 .max => try self.airMax(inst),
17151715 .slice => try self.airSlice(inst),
17161716
1717 .add_with_overflow => try self.airAddWithOverflow(inst),
1718
17171719 .bit_and, .bool_and => try self.airAnd(inst),
17181720 .bit_or, .bool_or => try self.airOr(inst),
17191721 .xor => try self.airXor(inst),
......@@ -3133,6 +3135,38 @@ pub const FuncGen = struct {
31333135 }
31343136 }
31353137
3138 fn airAddWithOverflow(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
3139 if (self.liveness.isUnused(inst))
3140 return null;
3141
3142 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
3143 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
3144
3145 const ptr = try self.resolveInst(pl_op.operand);
3146 const lhs = try self.resolveInst(extra.lhs);
3147 const rhs = try self.resolveInst(extra.rhs);
3148
3149 const ptr_ty = self.air.typeOf(pl_op.operand);
3150 const lhs_ty = self.air.typeOf(extra.lhs);
3151
3152 const intrinsic_name: []const u8 = if (lhs_ty.isSignedInt())
3153 "llvm.sadd.with.overflow"
3154 else
3155 "llvm.uadd.with.overflow";
3156
3157 const llvm_lhs_ty = try self.dg.llvmType(lhs_ty);
3158
3159 const llvm_fn = self.getIntrinsic(intrinsic_name, &.{llvm_lhs_ty});
3160 const result_struct = self.builder.buildCall(llvm_fn, &[_]*const llvm.Value{ lhs, rhs }, 2, .Fast, .Auto, "");
3161
3162 const result = self.builder.buildExtractValue(result_struct, 0, "");
3163 const overflow_bit = self.builder.buildExtractValue(result_struct, 1, "");
3164
3165 self.store(ptr, ptr_ty, result, .NotAtomic);
3166
3167 return overflow_bit;
3168 }
3169
31363170 fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
31373171 if (self.liveness.isUnused(inst))
31383172 return null;
......@@ -3511,7 +3545,7 @@ pub const FuncGen = struct {
35113545
35123546 fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
35133547 _ = inst;
3514 const llvm_fn = self.getIntrinsic("llvm.debugtrap");
3548 const llvm_fn = self.getIntrinsic("llvm.debugtrap", &.{});
35153549 _ = self.builder.buildCall(llvm_fn, undefined, 0, .C, .Auto, "");
35163550 return null;
35173551 }
......@@ -3946,13 +3980,10 @@ pub const FuncGen = struct {
39463980 return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
39473981 }
39483982
3949 fn getIntrinsic(self: *FuncGen, name: []const u8) *const llvm.Value {
3983 fn getIntrinsic(self: *FuncGen, name: []const u8, types: []*const llvm.Type) *const llvm.Value {
39503984 const id = llvm.lookupIntrinsicID(name.ptr, name.len);
39513985 assert(id != 0);
3952 // TODO: add support for overload intrinsics by passing the prefix of the intrinsic
3953 // to `lookupIntrinsicID` and then passing the correct types to
3954 // `getIntrinsicDeclaration`
3955 return self.llvmModule().getIntrinsicDeclaration(id, null, 0);
3986 return self.llvmModule().getIntrinsicDeclaration(id, types.ptr, types.len);
39563987 }
39573988
39583989 fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) ?*const llvm.Value {
src/print_air.zig+12
......@@ -228,6 +228,7 @@ const Writer = struct {
228228 .atomic_rmw => try w.writeAtomicRmw(s, inst),
229229 .memcpy => try w.writeMemcpy(s, inst),
230230 .memset => try w.writeMemset(s, inst),
231 .add_with_overflow => try w.writeAddWithOverflow(s, inst),
231232 }
232233 }
233234
......@@ -348,6 +349,17 @@ const Writer = struct {
348349 try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) });
349350 }
350351
352 fn writeAddWithOverflow(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
353 const pl_op = w.air.instructions.items(.data)[inst].pl_op;
354 const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
355
356 try w.writeOperand(s, inst, 0, pl_op.operand);
357 try s.writeAll(", ");
358 try w.writeOperand(s, inst, 1, extra.lhs);
359 try s.writeAll(", ");
360 try w.writeOperand(s, inst, 2, extra.rhs);
361 }
362
351363 fn writeMemset(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
352364 const pl_op = w.air.instructions.items(.data)[inst].pl_op;
353365 const extra = w.air.extraData(Air.Bin, pl_op.payload).data;
src/value.zig+57-22
......@@ -1969,20 +1969,18 @@ pub const Value = extern union {
19691969 return @divFloor(@floatToInt(std.math.big.Limb, std.math.log2(w_value)), @typeInfo(std.math.big.Limb).Int.bits) + 1;
19701970 }
19711971
1972 /// Supports both floats and ints; handles undefined.
1973 pub fn numberAddWrap(
1972 pub const OverflowArithmeticResult = struct {
1973 overflowed: bool,
1974 wrapped_result: Value,
1975 };
1976
1977 pub fn intAddWithOverflow(
19741978 lhs: Value,
19751979 rhs: Value,
19761980 ty: Type,
19771981 arena: Allocator,
19781982 target: Target,
1979 ) !Value {
1980 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
1981
1982 if (ty.isAnyFloat()) {
1983 return floatAdd(lhs, rhs, ty, arena);
1984 }
1985
1983 ) !OverflowArithmeticResult {
19861984 const info = ty.intInfo(target);
19871985
19881986 var lhs_space: Value.BigIntSpace = undefined;
......@@ -1994,8 +1992,30 @@ pub const Value = extern union {
19941992 std.math.big.int.calcTwosCompLimbCount(info.bits),
19951993 );
19961994 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
1997 result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
1998 return fromBigInt(arena, result_bigint.toConst());
1995 const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
1996 const result = try fromBigInt(arena, result_bigint.toConst());
1997 return OverflowArithmeticResult{
1998 .overflowed = overflowed,
1999 .wrapped_result = result,
2000 };
2001 }
2002
2003 /// Supports both floats and ints; handles undefined.
2004 pub fn numberAddWrap(
2005 lhs: Value,
2006 rhs: Value,
2007 ty: Type,
2008 arena: Allocator,
2009 target: Target,
2010 ) !Value {
2011 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
2012
2013 if (ty.isAnyFloat()) {
2014 return floatAdd(lhs, rhs, ty, arena);
2015 }
2016
2017 const overflow_result = try intAddWithOverflow(lhs, rhs, ty, arena, target);
2018 return overflow_result.wrapped_result;
19992019 }
20002020
20012021 fn fromBigInt(arena: Allocator, big_int: BigIntConst) !Value {
......@@ -2040,20 +2060,13 @@ pub const Value = extern union {
20402060 return fromBigInt(arena, result_bigint.toConst());
20412061 }
20422062
2043 /// Supports both floats and ints; handles undefined.
2044 pub fn numberSubWrap(
2063 pub fn intSubWithOverflow(
20452064 lhs: Value,
20462065 rhs: Value,
20472066 ty: Type,
20482067 arena: Allocator,
20492068 target: Target,
2050 ) !Value {
2051 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
2052
2053 if (ty.isAnyFloat()) {
2054 return floatSub(lhs, rhs, ty, arena);
2055 }
2056
2069 ) !OverflowArithmeticResult {
20572070 const info = ty.intInfo(target);
20582071
20592072 var lhs_space: Value.BigIntSpace = undefined;
......@@ -2065,8 +2078,30 @@ pub const Value = extern union {
20652078 std.math.big.int.calcTwosCompLimbCount(info.bits),
20662079 );
20672080 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
2068 result_bigint.subWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
2069 return fromBigInt(arena, result_bigint.toConst());
2081 const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits);
2082 const wrapped_result = try fromBigInt(arena, result_bigint.toConst());
2083 return OverflowArithmeticResult{
2084 .overflowed = overflowed,
2085 .wrapped_result = wrapped_result,
2086 };
2087 }
2088
2089 /// Supports both floats and ints; handles undefined.
2090 pub fn numberSubWrap(
2091 lhs: Value,
2092 rhs: Value,
2093 ty: Type,
2094 arena: Allocator,
2095 target: Target,
2096 ) !Value {
2097 if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef);
2098
2099 if (ty.isAnyFloat()) {
2100 return floatSub(lhs, rhs, ty, arena);
2101 }
2102
2103 const overflow_result = try intSubWithOverflow(lhs, rhs, ty, arena, target);
2104 return overflow_result.wrapped_result;
20702105 }
20712106
20722107 /// Supports integers only; asserts neither operand is undefined.
test/behavior/math.zig+27
......@@ -444,3 +444,30 @@ test "128-bit multiplication" {
444444 var c = a * b;
445445 try expect(c == 6);
446446}
447
448test "@addWithOverflow" {
449 var result: u8 = undefined;
450 try expect(@addWithOverflow(u8, 250, 100, &result));
451 try expect(result == 94);
452 try expect(!@addWithOverflow(u8, 100, 150, &result));
453 try expect(result == 250);
454}
455
456test "small int addition" {
457 var x: u2 = 0;
458 try expect(x == 0);
459
460 x += 1;
461 try expect(x == 1);
462
463 x += 1;
464 try expect(x == 2);
465
466 x += 1;
467 try expect(x == 3);
468
469 var result: @TypeOf(x) = 3;
470 try expect(@addWithOverflow(@TypeOf(x), x, 1, &result));
471
472 try expect(result == 0);
473}
test/behavior/math_stage1.zig-27
......@@ -6,14 +6,6 @@ const maxInt = std.math.maxInt;
66const minInt = std.math.minInt;
77const mem = std.mem;
88
9test "@addWithOverflow" {
10 var result: u8 = undefined;
11 try expect(@addWithOverflow(u8, 250, 100, &result));
12 try expect(result == 94);
13 try expect(!@addWithOverflow(u8, 100, 150, &result));
14 try expect(result == 250);
15}
16
179test "@mulWithOverflow" {
1810 var result: u8 = undefined;
1911 try expect(@mulWithOverflow(u8, 86, 3, &result));
......@@ -90,25 +82,6 @@ fn testCtzVectors() !void {
9082 try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
9183}
9284
93test "small int addition" {
94 var x: u2 = 0;
95 try expect(x == 0);
96
97 x += 1;
98 try expect(x == 1);
99
100 x += 1;
101 try expect(x == 2);
102
103 x += 1;
104 try expect(x == 3);
105
106 var result: @TypeOf(x) = 3;
107 try expect(@addWithOverflow(@TypeOf(x), x, 1, &result));
108
109 try expect(result == 0);
110}
111
11285test "allow signed integer division/remainder when values are comptime known and positive or exact" {
11386 try expect(5 / 3 == 1);
11487 try expect(-5 / -3 == 1);