| author | |
| committer | |
| log | f3d635b6683ba4a53f82ae8087b1cf78552abac5 |
| tree | bd038a7404df4e79170915e07528694768f44f7c |
| parent | 28bcd7dbdda7fb2c2fe80dbdb5981479a04e973a |
15 files changed, 311 insertions(+), 82 deletions(-)
lib/std/math/big/int.zig+23-13| ... | @@ -443,12 +443,12 @@ pub const Mutable = struct { | ... | @@ -443,12 +443,12 @@ pub const Mutable = struct { |
| 443 | } | 443 | } |
| 444 | } | 444 | } |
| 445 | 445 | ||
| 446 | /// r = a + b with 2s-complement wrapping semantics. | 446 | /// r = a + b with 2s-complement wrapping semantics. Returns whether overflow occurred. |
| 447 | /// r, a and b may be aliases | 447 | /// r, a and b may be aliases |
| 448 | /// | 448 | /// |
| 449 | /// Asserts the result fits in `r`. An upper bound on the number of limbs needed by | 449 | /// Asserts the result fits in `r`. An upper bound on the number of limbs needed by |
| 450 | /// r is `calcTwosCompLimbCount(bit_count)`. | 450 | /// 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 { |
| 452 | const req_limbs = calcTwosCompLimbCount(bit_count); | 452 | const req_limbs = calcTwosCompLimbCount(bit_count); |
| 453 | 453 | ||
| 454 | // Slice of the upper bits if they exist, these will be ignored and allows us to use addCarry to determine | 454 | // 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 { | ... | @@ -463,6 +463,7 @@ pub const Mutable = struct { |
| 463 | .limbs = b.limbs[0..math.min(req_limbs, b.limbs.len)], | 463 | .limbs = b.limbs[0..math.min(req_limbs, b.limbs.len)], |
| 464 | }; | 464 | }; |
| 465 | 465 | ||
| 466 | var carry_truncated = false; | ||
| 466 | if (r.addCarry(x, y)) { | 467 | if (r.addCarry(x, y)) { |
| 467 | // There are two possibilities here: | 468 | // There are two possibilities here: |
| 468 | // - We overflowed req_limbs. In this case, the carry is ignored, as it would be removed by | 469 | // - 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 { | ... | @@ -473,10 +474,17 @@ pub const Mutable = struct { |
| 473 | if (msl < req_limbs) { | 474 | if (msl < req_limbs) { |
| 474 | r.limbs[msl] = 1; | 475 | r.limbs[msl] = 1; |
| 475 | r.len = req_limbs; | 476 | r.len = req_limbs; |
| 477 | } else { | ||
| 478 | carry_truncated = true; | ||
| 476 | } | 479 | } |
| 477 | } | 480 | } |
| 478 | 481 | ||
| 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; | ||
| 480 | } | 488 | } |
| 481 | 489 | ||
| 482 | /// r = a + b with 2s-complement saturating semantics. | 490 | /// r = a + b with 2s-complement saturating semantics. |
| ... | @@ -581,13 +589,13 @@ pub const Mutable = struct { | ... | @@ -581,13 +589,13 @@ pub const Mutable = struct { |
| 581 | r.add(a, b.negate()); | 589 | r.add(a, b.negate()); |
| 582 | } | 590 | } |
| 583 | 591 | ||
| 584 | /// r = a - b with 2s-complement wrapping semantics. | 592 | /// r = a - b with 2s-complement wrapping semantics. Returns whether any overflow occured. |
| 585 | /// | 593 | /// |
| 586 | /// r, a and b may be aliases | 594 | /// r, a and b may be aliases |
| 587 | /// Asserts the result fits in `r`. An upper bound on the number of limbs needed by | 595 | /// Asserts the result fits in `r`. An upper bound on the number of limbs needed by |
| 588 | /// r is `calcTwosCompLimbCount(bit_count)`. | 596 | /// r is `calcTwosCompLimbCount(bit_count)`. |
| 589 | pub fn subWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) void { | 597 | pub fn subWrap(r: *Mutable, a: Const, b: Const, signedness: Signedness, bit_count: usize) bool { |
| 590 | r.addWrap(a, b.negate(), signedness, bit_count); | 598 | return r.addWrap(a, b.negate(), signedness, bit_count); |
| 591 | } | 599 | } |
| 592 | 600 | ||
| 593 | /// r = a - b with 2s-complement saturating semantics. | 601 | /// r = a - b with 2s-complement saturating semantics. |
| ... | @@ -1039,7 +1047,7 @@ pub const Mutable = struct { | ... | @@ -1039,7 +1047,7 @@ pub const Mutable = struct { |
| 1039 | pub fn bitNotWrap(r: *Mutable, a: Const, signedness: Signedness, bit_count: usize) void { | 1047 | pub fn bitNotWrap(r: *Mutable, a: Const, signedness: Signedness, bit_count: usize) void { |
| 1040 | r.copy(a.negate()); | 1048 | r.copy(a.negate()); |
| 1041 | const negative_one = Const{ .limbs = &.{1}, .positive = false }; | 1049 | 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); |
| 1043 | } | 1051 | } |
| 1044 | 1052 | ||
| 1045 | /// r = a | b under 2s complement semantics. | 1053 | /// r = a | b under 2s complement semantics. |
| ... | @@ -2443,17 +2451,18 @@ pub const Managed = struct { | ... | @@ -2443,17 +2451,18 @@ pub const Managed = struct { |
| 2443 | r.setMetadata(m.positive, m.len); | 2451 | r.setMetadata(m.positive, m.len); |
| 2444 | } | 2452 | } |
| 2445 | 2453 | ||
| 2446 | /// r = a + b with 2s-complement wrapping semantics. | 2454 | /// r = a + b with 2s-complement wrapping semantics. Returns whether any overflow occured. |
| 2447 | /// | 2455 | /// |
| 2448 | /// r, a and b may be aliases. If r aliases a or b, then caller must call | 2456 | /// r, a and b may be aliases. If r aliases a or b, then caller must call |
| 2449 | /// `r.ensureTwosCompCapacity` prior to calling `add`. | 2457 | /// `r.ensureTwosCompCapacity` prior to calling `add`. |
| 2450 | /// | 2458 | /// |
| 2451 | /// Returns an error if memory could not be allocated. | 2459 | /// 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 { |
| 2453 | try r.ensureTwosCompCapacity(bit_count); | 2461 | try r.ensureTwosCompCapacity(bit_count); |
| 2454 | var m = r.toMutable(); | 2462 | var m = r.toMutable(); |
| 2455 | m.addWrap(a, b, signedness, bit_count); | 2463 | const wrapped = m.addWrap(a, b, signedness, bit_count); |
| 2456 | r.setMetadata(m.positive, m.len); | 2464 | r.setMetadata(m.positive, m.len); |
| 2465 | return wrapped; | ||
| 2457 | } | 2466 | } |
| 2458 | 2467 | ||
| 2459 | /// r = a + b with 2s-complement saturating semantics. | 2468 | /// r = a + b with 2s-complement saturating semantics. |
| ... | @@ -2481,17 +2490,18 @@ pub const Managed = struct { | ... | @@ -2481,17 +2490,18 @@ pub const Managed = struct { |
| 2481 | r.setMetadata(m.positive, m.len); | 2490 | r.setMetadata(m.positive, m.len); |
| 2482 | } | 2491 | } |
| 2483 | 2492 | ||
| 2484 | /// r = a - b with 2s-complement wrapping semantics. | 2493 | /// r = a - b with 2s-complement wrapping semantics. Returns whether any overflow occured. |
| 2485 | /// | 2494 | /// |
| 2486 | /// r, a and b may be aliases. If r aliases a or b, then caller must call | 2495 | /// r, a and b may be aliases. If r aliases a or b, then caller must call |
| 2487 | /// `r.ensureTwosCompCapacity` prior to calling `add`. | 2496 | /// `r.ensureTwosCompCapacity` prior to calling `add`. |
| 2488 | /// | 2497 | /// |
| 2489 | /// Returns an error if memory could not be allocated. | 2498 | /// 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 { |
| 2491 | try r.ensureTwosCompCapacity(bit_count); | 2500 | try r.ensureTwosCompCapacity(bit_count); |
| 2492 | var m = r.toMutable(); | 2501 | var m = r.toMutable(); |
| 2493 | m.subWrap(a, b, signedness, bit_count); | 2502 | const wrapped = m.subWrap(a, b, signedness, bit_count); |
| 2494 | r.setMetadata(m.positive, m.len); | 2503 | r.setMetadata(m.positive, m.len); |
| 2504 | return wrapped; | ||
| 2495 | } | 2505 | } |
| 2496 | 2506 | ||
| 2497 | /// r = a - b with 2s-complement saturating semantics. | 2507 | /// 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" { | ... | @@ -590,8 +590,9 @@ test "big.int addWrap single-single, unsigned" { |
| 590 | var b = try Managed.initSet(testing.allocator, 10); | 590 | var b = try Managed.initSet(testing.allocator, 10); |
| 591 | defer b.deinit(); | 591 | defer b.deinit(); |
| 592 | 592 | ||
| 593 | try a.addWrap(a.toConst(), b.toConst(), .unsigned, 17); | 593 | const wrapped = try a.addWrap(a.toConst(), b.toConst(), .unsigned, 17); |
| 594 | 594 | ||
| 595 | try testing.expect(wrapped); | ||
| 595 | try testing.expect((try a.to(u17)) == 9); | 596 | try testing.expect((try a.to(u17)) == 9); |
| 596 | } | 597 | } |
| 597 | 598 | ||
| ... | @@ -602,8 +603,9 @@ test "big.int subWrap single-single, unsigned" { | ... | @@ -602,8 +603,9 @@ test "big.int subWrap single-single, unsigned" { |
| 602 | var b = try Managed.initSet(testing.allocator, maxInt(u17)); | 603 | var b = try Managed.initSet(testing.allocator, maxInt(u17)); |
| 603 | defer b.deinit(); | 604 | defer b.deinit(); |
| 604 | 605 | ||
| 605 | try a.subWrap(a.toConst(), b.toConst(), .unsigned, 17); | 606 | const wrapped = try a.subWrap(a.toConst(), b.toConst(), .unsigned, 17); |
| 606 | 607 | ||
| 608 | try testing.expect(wrapped); | ||
| 607 | try testing.expect((try a.to(u17)) == 1); | 609 | try testing.expect((try a.to(u17)) == 1); |
| 608 | } | 610 | } |
| 609 | 611 | ||
| ... | @@ -614,8 +616,9 @@ test "big.int addWrap multi-multi, unsigned, limb aligned" { | ... | @@ -614,8 +616,9 @@ test "big.int addWrap multi-multi, unsigned, limb aligned" { |
| 614 | var b = try Managed.initSet(testing.allocator, maxInt(DoubleLimb)); | 616 | var b = try Managed.initSet(testing.allocator, maxInt(DoubleLimb)); |
| 615 | defer b.deinit(); | 617 | defer b.deinit(); |
| 616 | 618 | ||
| 617 | try a.addWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb)); | 619 | const wrapped = try a.addWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb)); |
| 618 | 620 | ||
| 621 | try testing.expect(wrapped); | ||
| 619 | try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 1); | 622 | try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 1); |
| 620 | } | 623 | } |
| 621 | 624 | ||
| ... | @@ -626,8 +629,9 @@ test "big.int subWrap single-multi, unsigned, limb aligned" { | ... | @@ -626,8 +629,9 @@ test "big.int subWrap single-multi, unsigned, limb aligned" { |
| 626 | var b = try Managed.initSet(testing.allocator, maxInt(DoubleLimb) + 100); | 629 | var b = try Managed.initSet(testing.allocator, maxInt(DoubleLimb) + 100); |
| 627 | defer b.deinit(); | 630 | defer b.deinit(); |
| 628 | 631 | ||
| 629 | try a.subWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb)); | 632 | const wrapped = try a.subWrap(a.toConst(), b.toConst(), .unsigned, @bitSizeOf(DoubleLimb)); |
| 630 | 633 | ||
| 634 | try testing.expect(wrapped); | ||
| 631 | try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 88); | 635 | try testing.expect((try a.to(DoubleLimb)) == maxInt(DoubleLimb) - 88); |
| 632 | } | 636 | } |
| 633 | 637 | ||
| ... | @@ -638,8 +642,9 @@ test "big.int addWrap single-single, signed" { | ... | @@ -638,8 +642,9 @@ test "big.int addWrap single-single, signed" { |
| 638 | var b = try Managed.initSet(testing.allocator, 1 + 1 + maxInt(u21)); | 642 | var b = try Managed.initSet(testing.allocator, 1 + 1 + maxInt(u21)); |
| 639 | defer b.deinit(); | 643 | defer b.deinit(); |
| 640 | 644 | ||
| 641 | try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21)); | 645 | const wrapped = try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21)); |
| 642 | 646 | ||
| 647 | try testing.expect(wrapped); | ||
| 643 | try testing.expect((try a.to(i21)) == minInt(i21)); | 648 | try testing.expect((try a.to(i21)) == minInt(i21)); |
| 644 | } | 649 | } |
| 645 | 650 | ||
| ... | @@ -650,8 +655,9 @@ test "big.int subWrap single-single, signed" { | ... | @@ -650,8 +655,9 @@ test "big.int subWrap single-single, signed" { |
| 650 | var b = try Managed.initSet(testing.allocator, 1); | 655 | var b = try Managed.initSet(testing.allocator, 1); |
| 651 | defer b.deinit(); | 656 | defer b.deinit(); |
| 652 | 657 | ||
| 653 | try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21)); | 658 | const wrapped = try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(i21)); |
| 654 | 659 | ||
| 660 | try testing.expect(wrapped); | ||
| 655 | try testing.expect((try a.to(i21)) == maxInt(i21)); | 661 | try testing.expect((try a.to(i21)) == maxInt(i21)); |
| 656 | } | 662 | } |
| 657 | 663 | ||
| ... | @@ -662,8 +668,9 @@ test "big.int addWrap multi-multi, signed, limb aligned" { | ... | @@ -662,8 +668,9 @@ test "big.int addWrap multi-multi, signed, limb aligned" { |
| 662 | var b = try Managed.initSet(testing.allocator, maxInt(SignedDoubleLimb)); | 668 | var b = try Managed.initSet(testing.allocator, maxInt(SignedDoubleLimb)); |
| 663 | defer b.deinit(); | 669 | defer b.deinit(); |
| 664 | 670 | ||
| 665 | try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb)); | 671 | const wrapped = try a.addWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb)); |
| 666 | 672 | ||
| 673 | try testing.expect(wrapped); | ||
| 667 | try testing.expect((try a.to(SignedDoubleLimb)) == -2); | 674 | try testing.expect((try a.to(SignedDoubleLimb)) == -2); |
| 668 | } | 675 | } |
| 669 | 676 | ||
| ... | @@ -674,8 +681,9 @@ test "big.int subWrap single-multi, signed, limb aligned" { | ... | @@ -674,8 +681,9 @@ test "big.int subWrap single-multi, signed, limb aligned" { |
| 674 | var b = try Managed.initSet(testing.allocator, 1); | 681 | var b = try Managed.initSet(testing.allocator, 1); |
| 675 | defer b.deinit(); | 682 | defer b.deinit(); |
| 676 | 683 | ||
| 677 | try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb)); | 684 | const wrapped = try a.subWrap(a.toConst(), b.toConst(), .signed, @bitSizeOf(SignedDoubleLimb)); |
| 678 | 685 | ||
| 686 | try testing.expect(wrapped); | ||
| 679 | try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb)); | 687 | try testing.expect((try a.to(SignedDoubleLimb)) == maxInt(SignedDoubleLimb)); |
| 680 | } | 688 | } |
| 681 | 689 |
src/Air.zig+8| ... | @@ -135,6 +135,12 @@ pub const Inst = struct { | ... | @@ -135,6 +135,12 @@ pub const Inst = struct { |
| 135 | /// is the same as both operands. | 135 | /// is the same as both operands. |
| 136 | /// Uses the `bin_op` field. | 136 | /// Uses the `bin_op` field. |
| 137 | min, | 137 | 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, | ||
| 138 | /// Allocates stack local memory. | 144 | /// Allocates stack local memory. |
| 139 | /// Uses the `ty` field. | 145 | /// Uses the `ty` field. |
| 140 | alloc, | 146 | alloc, |
| ... | @@ -804,6 +810,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { | ... | @@ -804,6 +810,8 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { |
| 804 | const ptr_ty = air.typeOf(datas[inst].pl_op.operand); | 810 | const ptr_ty = air.typeOf(datas[inst].pl_op.operand); |
| 805 | return ptr_ty.elemType(); | 811 | return ptr_ty.elemType(); |
| 806 | }, | 812 | }, |
| 813 | |||
| 814 | .add_with_overflow => return Type.initTag(.bool), | ||
| 807 | } | 815 | } |
| 808 | } | 816 | } |
| 809 | 817 |
src/Liveness.zig+1-1| ... | @@ -381,7 +381,7 @@ fn analyzeInst( | ... | @@ -381,7 +381,7 @@ fn analyzeInst( |
| 381 | const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data; | 381 | const extra = a.air.extraData(Air.AtomicRmw, pl_op.payload).data; |
| 382 | return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none }); | 382 | return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.operand, .none }); |
| 383 | }, | 383 | }, |
| 384 | .memset, .memcpy => { | 384 | .memset, .memcpy, .add_with_overflow => { |
| 385 | const pl_op = inst_datas[inst].pl_op; | 385 | const pl_op = inst_datas[inst].pl_op; |
| 386 | const extra = a.air.extraData(Air.Bin, pl_op.payload).data; | 386 | const extra = a.air.extraData(Air.Bin, pl_op.payload).data; |
| 387 | return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, extra.lhs, extra.rhs }); | 387 | 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 | ... | @@ -1051,10 +1051,10 @@ fn zirExtended(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai |
| 1051 | .@"asm" => return sema.zirAsm( block, extended, inst), | 1051 | .@"asm" => return sema.zirAsm( block, extended, inst), |
| 1052 | .typeof_peer => return sema.zirTypeofPeer( block, extended), | 1052 | .typeof_peer => return sema.zirTypeofPeer( block, extended), |
| 1053 | .compile_log => return sema.zirCompileLog( block, extended), | 1053 | .compile_log => return sema.zirCompileLog( block, extended), |
| 1054 | .add_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), | 1055 | .sub_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode), |
| 1056 | .mul_with_overflow => return sema.zirOverflowArithmetic(block, extended), | 1056 | .mul_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode), |
| 1057 | .shl_with_overflow => return sema.zirOverflowArithmetic(block, extended), | 1057 | .shl_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode), |
| 1058 | .c_undef => return sema.zirCUndef( block, extended), | 1058 | .c_undef => return sema.zirCUndef( block, extended), |
| 1059 | .c_include => return sema.zirCInclude( block, extended), | 1059 | .c_include => return sema.zirCInclude( block, extended), |
| 1060 | .c_define => return sema.zirCDefine( block, extended), | 1060 | .c_define => return sema.zirCDefine( block, extended), |
| ... | @@ -7310,6 +7310,7 @@ fn zirOverflowArithmetic( | ... | @@ -7310,6 +7310,7 @@ fn zirOverflowArithmetic( |
| 7310 | sema: *Sema, | 7310 | sema: *Sema, |
| 7311 | block: *Block, | 7311 | block: *Block, |
| 7312 | extended: Zir.Inst.Extended.InstData, | 7312 | extended: Zir.Inst.Extended.InstData, |
| 7313 | zir_tag: Zir.Inst.Extended, | ||
| 7313 | ) CompileError!Air.Inst.Ref { | 7314 | ) CompileError!Air.Inst.Ref { |
| 7314 | const tracy = trace(@src()); | 7315 | const tracy = trace(@src()); |
| 7315 | defer tracy.end(); | 7316 | defer tracy.end(); |
| ... | @@ -7317,7 +7318,95 @@ fn zirOverflowArithmetic( | ... | @@ -7317,7 +7318,95 @@ fn zirOverflowArithmetic( |
| 7317 | const extra = sema.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data; | 7318 | const extra = sema.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data; |
| 7318 | const src: LazySrcLoc = .{ .node_offset = extra.node }; | 7319 | const src: LazySrcLoc = .{ .node_offset = extra.node }; |
| 7319 | 7320 | ||
| 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 | }; | ||
| 7321 | } | 7410 | } |
| 7322 | 7411 | ||
| 7323 | fn analyzeArithmetic( | 7412 | fn analyzeArithmetic( |
src/arch/aarch64/CodeGen.zig+7| ... | @@ -521,6 +521,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { | ... | @@ -521,6 +521,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 521 | .max => try self.airMax(inst), | 521 | .max => try self.airMax(inst), |
| 522 | .slice => try self.airSlice(inst), | 522 | .slice => try self.airSlice(inst), |
| 523 | 523 | ||
| 524 | .add_with_overflow => try self.airAddWithOverflow(inst), | ||
| 525 | |||
| 524 | .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), | 526 | .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), |
| 525 | 527 | ||
| 526 | .cmp_lt => try self.airCmp(inst, .lt), | 528 | .cmp_lt => try self.airCmp(inst, .lt), |
| ... | @@ -968,6 +970,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { | ... | @@ -968,6 +970,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { |
| 968 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); | 970 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 969 | } | 971 | } |
| 970 | 972 | ||
| 973 | fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void { | ||
| 974 | _ = inst; | ||
| 975 | return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch}); | ||
| 976 | } | ||
| 977 | |||
| 971 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { | 978 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { |
| 972 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 979 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 973 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch}); | 980 | 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 { | ... | @@ -519,6 +519,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 519 | .max => try self.airMax(inst), | 519 | .max => try self.airMax(inst), |
| 520 | .slice => try self.airSlice(inst), | 520 | .slice => try self.airSlice(inst), |
| 521 | 521 | ||
| 522 | .add_with_overflow => try self.airAddWithOverflow(inst), | ||
| 523 | |||
| 522 | .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), | 524 | .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), |
| 523 | 525 | ||
| 524 | .cmp_lt => try self.airCmp(inst, .lt), | 526 | .cmp_lt => try self.airCmp(inst, .lt), |
| ... | @@ -998,6 +1000,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { | ... | @@ -998,6 +1000,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { |
| 998 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); | 1000 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 999 | } | 1001 | } |
| 1000 | 1002 | ||
| 1003 | fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void { | ||
| 1004 | _ = inst; | ||
| 1005 | return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch}); | ||
| 1006 | } | ||
| 1007 | |||
| 1001 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { | 1008 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { |
| 1002 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 1009 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 1003 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch}); | 1010 | 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 { | ... | @@ -500,6 +500,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 500 | .max => try self.airMax(inst), | 500 | .max => try self.airMax(inst), |
| 501 | .slice => try self.airSlice(inst), | 501 | .slice => try self.airSlice(inst), |
| 502 | 502 | ||
| 503 | .add_with_overflow => try self.airAddWithOverflow(inst), | ||
| 504 | |||
| 503 | .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), | 505 | .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), |
| 504 | 506 | ||
| 505 | .cmp_lt => try self.airCmp(inst, .lt), | 507 | .cmp_lt => try self.airCmp(inst, .lt), |
| ... | @@ -913,6 +915,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { | ... | @@ -913,6 +915,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { |
| 913 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); | 915 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 914 | } | 916 | } |
| 915 | 917 | ||
| 918 | fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void { | ||
| 919 | _ = inst; | ||
| 920 | return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch}); | ||
| 921 | } | ||
| 922 | |||
| 916 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { | 923 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { |
| 917 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 924 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 918 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement div for {}", .{self.target.cpu.arch}); | 925 | 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 { | ... | @@ -553,6 +553,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { |
| 553 | .max => try self.airMax(inst), | 553 | .max => try self.airMax(inst), |
| 554 | .slice => try self.airSlice(inst), | 554 | .slice => try self.airSlice(inst), |
| 555 | 555 | ||
| 556 | .add_with_overflow => try self.airAddWithOverflow(inst), | ||
| 557 | |||
| 556 | .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), | 558 | .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst), |
| 557 | 559 | ||
| 558 | .cmp_lt => try self.airCmp(inst, .lt), | 560 | .cmp_lt => try self.airCmp(inst, .lt), |
| ... | @@ -1027,6 +1029,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { | ... | @@ -1027,6 +1029,11 @@ fn airMulSat(self: *Self, inst: Air.Inst.Index) !void { |
| 1027 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); | 1029 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 1028 | } | 1030 | } |
| 1029 | 1031 | ||
| 1032 | fn airAddWithOverflow(self: *Self, inst: Air.Inst.Index) !void { | ||
| 1033 | _ = inst; | ||
| 1034 | return self.fail("TODO implement airAddResultWithOverflow for {}", .{self.target.cpu.arch}); | ||
| 1035 | } | ||
| 1036 | |||
| 1030 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { | 1037 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { |
| 1031 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; | 1038 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 1032 | const result: MCValue = if (self.liveness.isUnused(inst)) | 1039 | 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 | ... | @@ -1155,6 +1155,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO |
| 1155 | .mul_sat => try airSatOp(f, inst, "muls_"), | 1155 | .mul_sat => try airSatOp(f, inst, "muls_"), |
| 1156 | .shl_sat => try airSatOp(f, inst, "shls_"), | 1156 | .shl_sat => try airSatOp(f, inst, "shls_"), |
| 1157 | 1157 | ||
| 1158 | .add_with_overflow => try airAddWithOverflow(f, inst), | ||
| 1159 | |||
| 1158 | .min => try airMinMax(f, inst, "<"), | 1160 | .min => try airMinMax(f, inst, "<"), |
| 1159 | .max => try airMinMax(f, inst, ">"), | 1161 | .max => try airMinMax(f, inst, ">"), |
| 1160 | 1162 | ||
| ... | @@ -1864,6 +1866,12 @@ fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue { | ... | @@ -1864,6 +1866,12 @@ fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue { |
| 1864 | return ret; | 1866 | return ret; |
| 1865 | } | 1867 | } |
| 1866 | 1868 | ||
| 1869 | fn airAddWithOverflow(f: *Function, inst: Air.Inst.Index) !CValue { | ||
| 1870 | _ = f; | ||
| 1871 | _ = inst; | ||
| 1872 | return f.fail("TODO add with overflow", .{}); | ||
| 1873 | } | ||
| 1874 | |||
| 1867 | fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { | 1875 | fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { |
| 1868 | if (f.liveness.isUnused(inst)) | 1876 | if (f.liveness.isUnused(inst)) |
| 1869 | return CValue.none; | 1877 | return CValue.none; |
src/codegen/llvm.zig+37-6| ... | @@ -1714,6 +1714,8 @@ pub const FuncGen = struct { | ... | @@ -1714,6 +1714,8 @@ pub const FuncGen = struct { |
| 1714 | .max => try self.airMax(inst), | 1714 | .max => try self.airMax(inst), |
| 1715 | .slice => try self.airSlice(inst), | 1715 | .slice => try self.airSlice(inst), |
| 1716 | 1716 | ||
| 1717 | .add_with_overflow => try self.airAddWithOverflow(inst), | ||
| 1718 | |||
| 1717 | .bit_and, .bool_and => try self.airAnd(inst), | 1719 | .bit_and, .bool_and => try self.airAnd(inst), |
| 1718 | .bit_or, .bool_or => try self.airOr(inst), | 1720 | .bit_or, .bool_or => try self.airOr(inst), |
| 1719 | .xor => try self.airXor(inst), | 1721 | .xor => try self.airXor(inst), |
| ... | @@ -3133,6 +3135,38 @@ pub const FuncGen = struct { | ... | @@ -3133,6 +3135,38 @@ pub const FuncGen = struct { |
| 3133 | } | 3135 | } |
| 3134 | } | 3136 | } |
| 3135 | 3137 | ||
| 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 | |||
| 3136 | fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { | 3170 | fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { |
| 3137 | if (self.liveness.isUnused(inst)) | 3171 | if (self.liveness.isUnused(inst)) |
| 3138 | return null; | 3172 | return null; |
| ... | @@ -3511,7 +3545,7 @@ pub const FuncGen = struct { | ... | @@ -3511,7 +3545,7 @@ pub const FuncGen = struct { |
| 3511 | 3545 | ||
| 3512 | fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { | 3546 | fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { |
| 3513 | _ = inst; | 3547 | _ = inst; |
| 3514 | const llvm_fn = self.getIntrinsic("llvm.debugtrap"); | 3548 | const llvm_fn = self.getIntrinsic("llvm.debugtrap", &.{}); |
| 3515 | _ = self.builder.buildCall(llvm_fn, undefined, 0, .C, .Auto, ""); | 3549 | _ = self.builder.buildCall(llvm_fn, undefined, 0, .C, .Auto, ""); |
| 3516 | return null; | 3550 | return null; |
| 3517 | } | 3551 | } |
| ... | @@ -3946,13 +3980,10 @@ pub const FuncGen = struct { | ... | @@ -3946,13 +3980,10 @@ pub const FuncGen = struct { |
| 3946 | return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); | 3980 | return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, ""); |
| 3947 | } | 3981 | } |
| 3948 | 3982 | ||
| 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 { |
| 3950 | const id = llvm.lookupIntrinsicID(name.ptr, name.len); | 3984 | const id = llvm.lookupIntrinsicID(name.ptr, name.len); |
| 3951 | assert(id != 0); | 3985 | assert(id != 0); |
| 3952 | // TODO: add support for overload intrinsics by passing the prefix of the intrinsic | 3986 | return self.llvmModule().getIntrinsicDeclaration(id, types.ptr, types.len); |
| 3953 | // to `lookupIntrinsicID` and then passing the correct types to | ||
| 3954 | // `getIntrinsicDeclaration` | ||
| 3955 | return self.llvmModule().getIntrinsicDeclaration(id, null, 0); | ||
| 3956 | } | 3987 | } |
| 3957 | 3988 | ||
| 3958 | fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) ?*const llvm.Value { | 3989 | 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 { | ... | @@ -228,6 +228,7 @@ const Writer = struct { |
| 228 | .atomic_rmw => try w.writeAtomicRmw(s, inst), | 228 | .atomic_rmw => try w.writeAtomicRmw(s, inst), |
| 229 | .memcpy => try w.writeMemcpy(s, inst), | 229 | .memcpy => try w.writeMemcpy(s, inst), |
| 230 | .memset => try w.writeMemset(s, inst), | 230 | .memset => try w.writeMemset(s, inst), |
| 231 | .add_with_overflow => try w.writeAddWithOverflow(s, inst), | ||
| 231 | } | 232 | } |
| 232 | } | 233 | } |
| 233 | 234 | ||
| ... | @@ -348,6 +349,17 @@ const Writer = struct { | ... | @@ -348,6 +349,17 @@ const Writer = struct { |
| 348 | try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) }); | 349 | try s.print(", {s}, {s}", .{ @tagName(extra.op()), @tagName(extra.ordering()) }); |
| 349 | } | 350 | } |
| 350 | 351 | ||
| 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 | |||
| 351 | fn writeMemset(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { | 363 | fn writeMemset(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { |
| 352 | const pl_op = w.air.instructions.items(.data)[inst].pl_op; | 364 | const pl_op = w.air.instructions.items(.data)[inst].pl_op; |
| 353 | const extra = w.air.extraData(Air.Bin, pl_op.payload).data; | 365 | 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 { | ... | @@ -1969,20 +1969,18 @@ pub const Value = extern union { |
| 1969 | return @divFloor(@floatToInt(std.math.big.Limb, std.math.log2(w_value)), @typeInfo(std.math.big.Limb).Int.bits) + 1; | 1969 | return @divFloor(@floatToInt(std.math.big.Limb, std.math.log2(w_value)), @typeInfo(std.math.big.Limb).Int.bits) + 1; |
| 1970 | } | 1970 | } |
| 1971 | 1971 | ||
| 1972 | /// Supports both floats and ints; handles undefined. | 1972 | pub const OverflowArithmeticResult = struct { |
| 1973 | pub fn numberAddWrap( | 1973 | overflowed: bool, |
| 1974 | wrapped_result: Value, | ||
| 1975 | }; | ||
| 1976 | |||
| 1977 | pub fn intAddWithOverflow( | ||
| 1974 | lhs: Value, | 1978 | lhs: Value, |
| 1975 | rhs: Value, | 1979 | rhs: Value, |
| 1976 | ty: Type, | 1980 | ty: Type, |
| 1977 | arena: Allocator, | 1981 | arena: Allocator, |
| 1978 | target: Target, | 1982 | target: Target, |
| 1979 | ) !Value { | 1983 | ) !OverflowArithmeticResult { |
| 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 | |||
| 1986 | const info = ty.intInfo(target); | 1984 | const info = ty.intInfo(target); |
| 1987 | 1985 | ||
| 1988 | var lhs_space: Value.BigIntSpace = undefined; | 1986 | var lhs_space: Value.BigIntSpace = undefined; |
| ... | @@ -1994,8 +1992,30 @@ pub const Value = extern union { | ... | @@ -1994,8 +1992,30 @@ pub const Value = extern union { |
| 1994 | std.math.big.int.calcTwosCompLimbCount(info.bits), | 1992 | std.math.big.int.calcTwosCompLimbCount(info.bits), |
| 1995 | ); | 1993 | ); |
| 1996 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | 1994 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; |
| 1997 | result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits); | 1995 | const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits); |
| 1998 | return fromBigInt(arena, result_bigint.toConst()); | 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; | ||
| 1999 | } | 2019 | } |
| 2000 | 2020 | ||
| 2001 | fn fromBigInt(arena: Allocator, big_int: BigIntConst) !Value { | 2021 | fn fromBigInt(arena: Allocator, big_int: BigIntConst) !Value { |
| ... | @@ -2040,20 +2060,13 @@ pub const Value = extern union { | ... | @@ -2040,20 +2060,13 @@ pub const Value = extern union { |
| 2040 | return fromBigInt(arena, result_bigint.toConst()); | 2060 | return fromBigInt(arena, result_bigint.toConst()); |
| 2041 | } | 2061 | } |
| 2042 | 2062 | ||
| 2043 | /// Supports both floats and ints; handles undefined. | 2063 | pub fn intSubWithOverflow( |
| 2044 | pub fn numberSubWrap( | ||
| 2045 | lhs: Value, | 2064 | lhs: Value, |
| 2046 | rhs: Value, | 2065 | rhs: Value, |
| 2047 | ty: Type, | 2066 | ty: Type, |
| 2048 | arena: Allocator, | 2067 | arena: Allocator, |
| 2049 | target: Target, | 2068 | target: Target, |
| 2050 | ) !Value { | 2069 | ) !OverflowArithmeticResult { |
| 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 | |||
| 2057 | const info = ty.intInfo(target); | 2070 | const info = ty.intInfo(target); |
| 2058 | 2071 | ||
| 2059 | var lhs_space: Value.BigIntSpace = undefined; | 2072 | var lhs_space: Value.BigIntSpace = undefined; |
| ... | @@ -2065,8 +2078,30 @@ pub const Value = extern union { | ... | @@ -2065,8 +2078,30 @@ pub const Value = extern union { |
| 2065 | std.math.big.int.calcTwosCompLimbCount(info.bits), | 2078 | std.math.big.int.calcTwosCompLimbCount(info.bits), |
| 2066 | ); | 2079 | ); |
| 2067 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; | 2080 | var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; |
| 2068 | result_bigint.subWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits); | 2081 | const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits); |
| 2069 | return fromBigInt(arena, result_bigint.toConst()); | 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; | ||
| 2070 | } | 2105 | } |
| 2071 | 2106 | ||
| 2072 | /// Supports integers only; asserts neither operand is undefined. | 2107 | /// Supports integers only; asserts neither operand is undefined. |
test/behavior/math.zig+27| ... | @@ -444,3 +444,30 @@ test "128-bit multiplication" { | ... | @@ -444,3 +444,30 @@ test "128-bit multiplication" { |
| 444 | var c = a * b; | 444 | var c = a * b; |
| 445 | try expect(c == 6); | 445 | try expect(c == 6); |
| 446 | } | 446 | } |
| 447 | |||
| 448 | test "@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 | |||
| 456 | test "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; | ... | @@ -6,14 +6,6 @@ const maxInt = std.math.maxInt; |
| 6 | const minInt = std.math.minInt; | 6 | const minInt = std.math.minInt; |
| 7 | const mem = std.mem; | 7 | const mem = std.mem; |
| 8 | 8 | ||
| 9 | test "@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 | |||
| 17 | test "@mulWithOverflow" { | 9 | test "@mulWithOverflow" { |
| 18 | var result: u8 = undefined; | 10 | var result: u8 = undefined; |
| 19 | try expect(@mulWithOverflow(u8, 86, 3, &result)); | 11 | try expect(@mulWithOverflow(u8, 86, 3, &result)); |
| ... | @@ -90,25 +82,6 @@ fn testCtzVectors() !void { | ... | @@ -90,25 +82,6 @@ fn testCtzVectors() !void { |
| 90 | try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16))); | 82 | try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16))); |
| 91 | } | 83 | } |
| 92 | 84 | ||
| 93 | test "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 | |||
| 112 | test "allow signed integer division/remainder when values are comptime known and positive or exact" { | 85 | test "allow signed integer division/remainder when values are comptime known and positive or exact" { |
| 113 | try expect(5 / 3 == 1); | 86 | try expect(5 / 3 == 1); |
| 114 | try expect(-5 / -3 == 1); | 87 | try expect(-5 / -3 == 1); |