| ... | ... | @@ -81,6 +81,19 @@ pub const Feature = enum { |
| 81 | 81 | /// Legalize reduce of a one element vector to a bitcast |
| 82 | 82 | reduce_one_elem_to_bitcast, |
| 83 | 83 | |
| 84 | /// Replace `intcast_safe` with an explicit safety check which `call`s the panic function on failure. |
| 85 | /// Not compatible with `scalarize_intcast_safe`. |
| 86 | expand_intcast_safe, |
| 87 | /// Replace `add_safe` with an explicit safety check which `call`s the panic function on failure. |
| 88 | /// Not compatible with `scalarize_add_safe`. |
| 89 | expand_add_safe, |
| 90 | /// Replace `sub_safe` with an explicit safety check which `call`s the panic function on failure. |
| 91 | /// Not compatible with `scalarize_sub_safe`. |
| 92 | expand_sub_safe, |
| 93 | /// Replace `mul_safe` with an explicit safety check which `call`s the panic function on failure. |
| 94 | /// Not compatible with `scalarize_mul_safe`. |
| 95 | expand_mul_safe, |
| 96 | |
| 84 | 97 | fn scalarize(tag: Air.Inst.Tag) Feature { |
| 85 | 98 | return switch (tag) { |
| 86 | 99 | else => unreachable, |
| ... | ... | @@ -205,17 +218,14 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 205 | 218 | .arg, |
| 206 | 219 | => {}, |
| 207 | 220 | inline .add, |
| 208 | | .add_safe, |
| 209 | 221 | .add_optimized, |
| 210 | 222 | .add_wrap, |
| 211 | 223 | .add_sat, |
| 212 | 224 | .sub, |
| 213 | | .sub_safe, |
| 214 | 225 | .sub_optimized, |
| 215 | 226 | .sub_wrap, |
| 216 | 227 | .sub_sat, |
| 217 | 228 | .mul, |
| 218 | | .mul_safe, |
| 219 | 229 | .mul_optimized, |
| 220 | 230 | .mul_wrap, |
| 221 | 231 | .mul_sat, |
| ... | ... | @@ -240,6 +250,27 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 240 | 250 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 241 | 251 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); |
| 242 | 252 | }, |
| 253 | .add_safe => if (l.features.contains(.expand_add_safe)) { |
| 254 | assert(!l.features.contains(.scalarize_add_safe)); // it doesn't make sense to do both |
| 255 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .add_with_overflow)); |
| 256 | } else if (l.features.contains(.scalarize_add_safe)) { |
| 257 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 258 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); |
| 259 | }, |
| 260 | .sub_safe => if (l.features.contains(.expand_sub_safe)) { |
| 261 | assert(!l.features.contains(.scalarize_sub_safe)); // it doesn't make sense to do both |
| 262 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .sub_with_overflow)); |
| 263 | } else if (l.features.contains(.scalarize_sub_safe)) { |
| 264 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 265 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); |
| 266 | }, |
| 267 | .mul_safe => if (l.features.contains(.expand_mul_safe)) { |
| 268 | assert(!l.features.contains(.scalarize_mul_safe)); // it doesn't make sense to do both |
| 269 | continue :inst l.replaceInst(inst, .block, try l.safeArithmeticBlockPayload(inst, .mul_with_overflow)); |
| 270 | } else if (l.features.contains(.scalarize_mul_safe)) { |
| 271 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op; |
| 272 | if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op); |
| 273 | }, |
| 243 | 274 | .ptr_add, |
| 244 | 275 | .ptr_sub, |
| 245 | 276 | .add_with_overflow, |
| ... | ... | @@ -295,7 +326,6 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 295 | 326 | .fptrunc, |
| 296 | 327 | .fpext, |
| 297 | 328 | .intcast, |
| 298 | | .intcast_safe, |
| 299 | 329 | .trunc, |
| 300 | 330 | .int_from_float, |
| 301 | 331 | .int_from_float_optimized, |
| ... | ... | @@ -312,6 +342,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { |
| 312 | 342 | if (to_ty.isVector(zcu) and from_ty.isVector(zcu) and to_ty.vectorLen(zcu) == from_ty.vectorLen(zcu)) |
| 313 | 343 | continue :inst try l.scalarize(inst, .ty_op); |
| 314 | 344 | }, |
| 345 | .intcast_safe => if (l.features.contains(.expand_intcast_safe)) { |
| 346 | assert(!l.features.contains(.scalarize_intcast_safe)); // it doesn't make sense to do both |
| 347 | continue :inst l.replaceInst(inst, .block, try l.safeIntcastBlockPayload(inst)); |
| 348 | } else if (l.features.contains(.scalarize_intcast_safe)) { |
| 349 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(inst)].ty_op; |
| 350 | if (ty_op.ty.toType().isVector(zcu)) continue :inst try l.scalarize(inst, .ty_op); |
| 351 | }, |
| 315 | 352 | .block, |
| 316 | 353 | .loop, |
| 317 | 354 | => { |
| ... | ... | @@ -550,81 +587,83 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_ |
| 550 | 587 | const expected_instructions_len = l.air_instructions.len + (6 + arity + 8); |
| 551 | 588 | try l.air_instructions.ensureTotalCapacity(gpa, expected_instructions_len); |
| 552 | 589 | |
| 553 | | var res_block: Block(4) = .empty; |
| 590 | var res_block_buf: [4]Air.Inst.Index = undefined; |
| 591 | var res_block: Block = .init(&res_block_buf); |
| 554 | 592 | { |
| 555 | | const res_alloc_inst = res_block.add(l.addInstAssumeCapacity(.{ |
| 593 | const res_alloc_inst = res_block.add(l, .{ |
| 556 | 594 | .tag = .alloc, |
| 557 | 595 | .data = .{ .ty = try pt.singleMutPtrType(res_ty) }, |
| 558 | | })); |
| 559 | | const index_alloc_inst = res_block.add(l.addInstAssumeCapacity(.{ |
| 596 | }); |
| 597 | const index_alloc_inst = res_block.add(l, .{ |
| 560 | 598 | .tag = .alloc, |
| 561 | 599 | .data = .{ .ty = .ptr_usize }, |
| 562 | | })); |
| 563 | | _ = res_block.add(l.addInstAssumeCapacity(.{ |
| 600 | }); |
| 601 | _ = res_block.add(l, .{ |
| 564 | 602 | .tag = .store, |
| 565 | 603 | .data = .{ .bin_op = .{ |
| 566 | 604 | .lhs = index_alloc_inst.toRef(), |
| 567 | 605 | .rhs = .zero_usize, |
| 568 | 606 | } }, |
| 569 | | })); |
| 607 | }); |
| 570 | 608 | |
| 571 | 609 | const loop_inst: Air.Inst.Index = @enumFromInt(l.air_instructions.len + (3 + arity + 7)); |
| 572 | | var loop_block: Block(3 + arity + 2) = .empty; |
| 610 | var loop_block_buf: [3 + arity + 2]Air.Inst.Index = undefined; |
| 611 | var loop_block: Block = .init(&loop_block_buf); |
| 573 | 612 | { |
| 574 | | const cur_index_inst = loop_block.add(l.addInstAssumeCapacity(.{ |
| 613 | const cur_index_inst = loop_block.add(l, .{ |
| 575 | 614 | .tag = .load, |
| 576 | 615 | .data = .{ .ty_op = .{ |
| 577 | 616 | .ty = .usize_type, |
| 578 | 617 | .operand = index_alloc_inst.toRef(), |
| 579 | 618 | } }, |
| 580 | | })); |
| 581 | | _ = loop_block.add(l.addInstAssumeCapacity(.{ |
| 619 | }); |
| 620 | _ = loop_block.add(l, .{ |
| 582 | 621 | .tag = .vector_store_elem, |
| 583 | 622 | .data = .{ .vector_store_elem = .{ |
| 584 | 623 | .vector_ptr = res_alloc_inst.toRef(), |
| 585 | 624 | .payload = try l.addExtra(Air.Bin, .{ |
| 586 | 625 | .lhs = cur_index_inst.toRef(), |
| 587 | | .rhs = loop_block.add(l.addInstAssumeCapacity(res_elem: switch (data_tag) { |
| 626 | .rhs = loop_block.add(l, res_elem: switch (data_tag) { |
| 588 | 627 | .un_op => .{ |
| 589 | 628 | .tag = orig.tag, |
| 590 | | .data = .{ .un_op = loop_block.add(l.addInstAssumeCapacity(.{ |
| 629 | .data = .{ .un_op = loop_block.add(l, .{ |
| 591 | 630 | .tag = .array_elem_val, |
| 592 | 631 | .data = .{ .bin_op = .{ |
| 593 | 632 | .lhs = orig.data.un_op, |
| 594 | 633 | .rhs = cur_index_inst.toRef(), |
| 595 | 634 | } }, |
| 596 | | })).toRef() }, |
| 635 | }).toRef() }, |
| 597 | 636 | }, |
| 598 | 637 | .ty_op => .{ |
| 599 | 638 | .tag = orig.tag, |
| 600 | 639 | .data = .{ .ty_op = .{ |
| 601 | 640 | .ty = Air.internedToRef(orig.data.ty_op.ty.toType().scalarType(zcu).toIntern()), |
| 602 | | .operand = loop_block.add(l.addInstAssumeCapacity(.{ |
| 641 | .operand = loop_block.add(l, .{ |
| 603 | 642 | .tag = .array_elem_val, |
| 604 | 643 | .data = .{ .bin_op = .{ |
| 605 | 644 | .lhs = orig.data.ty_op.operand, |
| 606 | 645 | .rhs = cur_index_inst.toRef(), |
| 607 | 646 | } }, |
| 608 | | })).toRef(), |
| 647 | }).toRef(), |
| 609 | 648 | } }, |
| 610 | 649 | }, |
| 611 | 650 | .bin_op => .{ |
| 612 | 651 | .tag = orig.tag, |
| 613 | 652 | .data = .{ .bin_op = .{ |
| 614 | | .lhs = loop_block.add(l.addInstAssumeCapacity(.{ |
| 653 | .lhs = loop_block.add(l, .{ |
| 615 | 654 | .tag = .array_elem_val, |
| 616 | 655 | .data = .{ .bin_op = .{ |
| 617 | 656 | .lhs = orig.data.bin_op.lhs, |
| 618 | 657 | .rhs = cur_index_inst.toRef(), |
| 619 | 658 | } }, |
| 620 | | })).toRef(), |
| 621 | | .rhs = loop_block.add(l.addInstAssumeCapacity(.{ |
| 659 | }).toRef(), |
| 660 | .rhs = loop_block.add(l, .{ |
| 622 | 661 | .tag = .array_elem_val, |
| 623 | 662 | .data = .{ .bin_op = .{ |
| 624 | 663 | .lhs = orig.data.bin_op.rhs, |
| 625 | 664 | .rhs = cur_index_inst.toRef(), |
| 626 | 665 | } }, |
| 627 | | })).toRef(), |
| 666 | }).toRef(), |
| 628 | 667 | } }, |
| 629 | 668 | }, |
| 630 | 669 | .ty_pl_vector_cmp => { |
| ... | ... | @@ -650,20 +689,20 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_ |
| 650 | 689 | }, |
| 651 | 690 | }, |
| 652 | 691 | .data = .{ .bin_op = .{ |
| 653 | | .lhs = loop_block.add(l.addInstAssumeCapacity(.{ |
| 692 | .lhs = loop_block.add(l, .{ |
| 654 | 693 | .tag = .array_elem_val, |
| 655 | 694 | .data = .{ .bin_op = .{ |
| 656 | 695 | .lhs = extra.lhs, |
| 657 | 696 | .rhs = cur_index_inst.toRef(), |
| 658 | 697 | } }, |
| 659 | | })).toRef(), |
| 660 | | .rhs = loop_block.add(l.addInstAssumeCapacity(.{ |
| 698 | }).toRef(), |
| 699 | .rhs = loop_block.add(l, .{ |
| 661 | 700 | .tag = .array_elem_val, |
| 662 | 701 | .data = .{ .bin_op = .{ |
| 663 | 702 | .lhs = extra.rhs, |
| 664 | 703 | .rhs = cur_index_inst.toRef(), |
| 665 | 704 | } }, |
| 666 | | })).toRef(), |
| 705 | }).toRef(), |
| 667 | 706 | } }, |
| 668 | 707 | }; |
| 669 | 708 | }, |
| ... | ... | @@ -673,94 +712,96 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_ |
| 673 | 712 | .tag = orig.tag, |
| 674 | 713 | .data = .{ .pl_op = .{ |
| 675 | 714 | .payload = try l.addExtra(Air.Bin, .{ |
| 676 | | .lhs = loop_block.add(l.addInstAssumeCapacity(.{ |
| 715 | .lhs = loop_block.add(l, .{ |
| 677 | 716 | .tag = .array_elem_val, |
| 678 | 717 | .data = .{ .bin_op = .{ |
| 679 | 718 | .lhs = extra.lhs, |
| 680 | 719 | .rhs = cur_index_inst.toRef(), |
| 681 | 720 | } }, |
| 682 | | })).toRef(), |
| 683 | | .rhs = loop_block.add(l.addInstAssumeCapacity(.{ |
| 721 | }).toRef(), |
| 722 | .rhs = loop_block.add(l, .{ |
| 684 | 723 | .tag = .array_elem_val, |
| 685 | 724 | .data = .{ .bin_op = .{ |
| 686 | 725 | .lhs = extra.rhs, |
| 687 | 726 | .rhs = cur_index_inst.toRef(), |
| 688 | 727 | } }, |
| 689 | | })).toRef(), |
| 728 | }).toRef(), |
| 690 | 729 | }), |
| 691 | | .operand = loop_block.add(l.addInstAssumeCapacity(.{ |
| 730 | .operand = loop_block.add(l, .{ |
| 692 | 731 | .tag = .array_elem_val, |
| 693 | 732 | .data = .{ .bin_op = .{ |
| 694 | 733 | .lhs = orig.data.pl_op.operand, |
| 695 | 734 | .rhs = cur_index_inst.toRef(), |
| 696 | 735 | } }, |
| 697 | | })).toRef(), |
| 736 | }).toRef(), |
| 698 | 737 | } }, |
| 699 | 738 | }; |
| 700 | 739 | }, |
| 701 | | })).toRef(), |
| 740 | }).toRef(), |
| 702 | 741 | }), |
| 703 | 742 | } }, |
| 704 | | })); |
| 705 | | const not_done_inst = loop_block.add(l.addInstAssumeCapacity(.{ |
| 743 | }); |
| 744 | const not_done_inst = loop_block.add(l, .{ |
| 706 | 745 | .tag = .cmp_lt, |
| 707 | 746 | .data = .{ .bin_op = .{ |
| 708 | 747 | .lhs = cur_index_inst.toRef(), |
| 709 | 748 | .rhs = try pt.intRef(.usize, res_ty.vectorLen(zcu) - 1), |
| 710 | 749 | } }, |
| 711 | | })); |
| 750 | }); |
| 712 | 751 | |
| 713 | | var not_done_block: Block(3) = .empty; |
| 752 | var not_done_block_buf: [3]Air.Inst.Index = undefined; |
| 753 | var not_done_block: Block = .init(&not_done_block_buf); |
| 714 | 754 | { |
| 715 | | _ = not_done_block.add(l.addInstAssumeCapacity(.{ |
| 755 | _ = not_done_block.add(l, .{ |
| 716 | 756 | .tag = .store, |
| 717 | 757 | .data = .{ .bin_op = .{ |
| 718 | 758 | .lhs = index_alloc_inst.toRef(), |
| 719 | | .rhs = not_done_block.add(l.addInstAssumeCapacity(.{ |
| 759 | .rhs = not_done_block.add(l, .{ |
| 720 | 760 | .tag = .add, |
| 721 | 761 | .data = .{ .bin_op = .{ |
| 722 | 762 | .lhs = cur_index_inst.toRef(), |
| 723 | 763 | .rhs = .one_usize, |
| 724 | 764 | } }, |
| 725 | | })).toRef(), |
| 765 | }).toRef(), |
| 726 | 766 | } }, |
| 727 | | })); |
| 728 | | _ = not_done_block.add(l.addInstAssumeCapacity(.{ |
| 767 | }); |
| 768 | _ = not_done_block.add(l, .{ |
| 729 | 769 | .tag = .repeat, |
| 730 | 770 | .data = .{ .repeat = .{ .loop_inst = loop_inst } }, |
| 731 | | })); |
| 771 | }); |
| 732 | 772 | } |
| 733 | | var done_block: Block(2) = .empty; |
| 773 | var done_block_buf: [2]Air.Inst.Index = undefined; |
| 774 | var done_block: Block = .init(&done_block_buf); |
| 734 | 775 | { |
| 735 | | _ = done_block.add(l.addInstAssumeCapacity(.{ |
| 776 | _ = done_block.add(l, .{ |
| 736 | 777 | .tag = .br, |
| 737 | 778 | .data = .{ .br = .{ |
| 738 | 779 | .block_inst = orig_inst, |
| 739 | | .operand = done_block.add(l.addInstAssumeCapacity(.{ |
| 780 | .operand = done_block.add(l, .{ |
| 740 | 781 | .tag = .load, |
| 741 | 782 | .data = .{ .ty_op = .{ |
| 742 | 783 | .ty = Air.internedToRef(res_ty.toIntern()), |
| 743 | 784 | .operand = res_alloc_inst.toRef(), |
| 744 | 785 | } }, |
| 745 | | })).toRef(), |
| 786 | }).toRef(), |
| 746 | 787 | } }, |
| 747 | | })); |
| 788 | }); |
| 748 | 789 | } |
| 749 | | _ = loop_block.add(l.addInstAssumeCapacity(.{ |
| 790 | _ = loop_block.add(l, .{ |
| 750 | 791 | .tag = .cond_br, |
| 751 | 792 | .data = .{ .pl_op = .{ |
| 752 | 793 | .operand = not_done_inst.toRef(), |
| 753 | 794 | .payload = try l.addCondBrBodies(not_done_block.body(), done_block.body()), |
| 754 | 795 | } }, |
| 755 | | })); |
| 796 | }); |
| 756 | 797 | } |
| 757 | | assert(loop_inst == res_block.add(l.addInstAssumeCapacity(.{ |
| 798 | assert(loop_inst == res_block.add(l, .{ |
| 758 | 799 | .tag = .loop, |
| 759 | 800 | .data = .{ .ty_pl = .{ |
| 760 | 801 | .ty = .noreturn_type, |
| 761 | 802 | .payload = try l.addBlockBody(loop_block.body()), |
| 762 | 803 | } }, |
| 763 | | }))); |
| 804 | })); |
| 764 | 805 | } |
| 765 | 806 | assert(l.air_instructions.len == expected_instructions_len); |
| 766 | 807 | return .{ .ty_pl = .{ |
| ... | ... | @@ -768,29 +809,423 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_ |
| 768 | 809 | .payload = try l.addBlockBody(res_block.body()), |
| 769 | 810 | } }; |
| 770 | 811 | } |
| 812 | fn safeIntcastBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data { |
| 813 | const pt = l.pt; |
| 814 | const zcu = pt.zcu; |
| 815 | const gpa = zcu.gpa; |
| 816 | const ty_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].ty_op; |
| 817 | |
| 818 | const operand_ref = ty_op.operand; |
| 819 | const operand_ty = l.typeOf(operand_ref); |
| 820 | const dest_ty = ty_op.ty.toType(); |
| 821 | |
| 822 | const is_vector = operand_ty.zigTypeTag(zcu) == .vector; |
| 823 | const operand_scalar_ty = operand_ty.scalarType(zcu); |
| 824 | const dest_scalar_ty = dest_ty.scalarType(zcu); |
| 825 | |
| 826 | assert(operand_scalar_ty.zigTypeTag(zcu) == .int); |
| 827 | const dest_is_enum = switch (dest_scalar_ty.zigTypeTag(zcu)) { |
| 828 | .int => false, |
| 829 | .@"enum" => true, |
| 830 | else => unreachable, |
| 831 | }; |
| 832 | |
| 833 | const operand_info = operand_scalar_ty.intInfo(zcu); |
| 834 | const dest_info = dest_scalar_ty.intInfo(zcu); |
| 835 | |
| 836 | const have_min_check, const have_max_check = c: { |
| 837 | const dest_pos_bits = dest_info.bits - @intFromBool(dest_info.signedness == .signed); |
| 838 | const operand_pos_bits = operand_info.bits - @intFromBool(operand_info.signedness == .signed); |
| 839 | const dest_allows_neg = dest_info.signedness == .signed and dest_info.bits > 0; |
| 840 | const operand_allows_neg = operand_info.signedness == .signed and operand_info.bits > 0; |
| 841 | break :c .{ |
| 842 | operand_allows_neg and (!dest_allows_neg or dest_info.bits < operand_info.bits), |
| 843 | dest_pos_bits < operand_pos_bits, |
| 844 | }; |
| 845 | }; |
| 846 | |
| 847 | // The worst-case scenario in terms of total instructions and total condbrs is the case where |
| 848 | // the result type is an exhaustive enum whose tag type is smaller than the operand type: |
| 849 | // |
| 850 | // %x = block({ |
| 851 | // %1 = cmp_lt(%y, @min_allowed_int) |
| 852 | // %2 = cmp_gt(%y, @max_allowed_int) |
| 853 | // %3 = bool_or(%1, %2) |
| 854 | // %4 = cond_br(%3, { |
| 855 | // %5 = call(@panic.invalidEnumValue, []) |
| 856 | // %6 = unreach() |
| 857 | // }, { |
| 858 | // %7 = intcast(@res_ty, %y) |
| 859 | // %8 = is_named_enum_value(%7) |
| 860 | // %9 = cond_br(%8, { |
| 861 | // %10 = br(%x, %7) |
| 862 | // }, { |
| 863 | // %11 = call(@panic.invalidEnumValue, []) |
| 864 | // %12 = unreach() |
| 865 | // }) |
| 866 | // }) |
| 867 | // }) |
| 868 | // |
| 869 | // Note that vectors of enums don't exist -- the worst case for vectors is this: |
| 870 | // |
| 871 | // %x = block({ |
| 872 | // %1 = cmp_lt(%y, @min_allowed_int) |
| 873 | // %2 = cmp_gt(%y, @max_allowed_int) |
| 874 | // %3 = bool_or(%1, %2) |
| 875 | // %4 = reduce(%3, .@"or") |
| 876 | // %5 = cond_br(%4, { |
| 877 | // %6 = call(@panic.invalidEnumValue, []) |
| 878 | // %7 = unreach() |
| 879 | // }, { |
| 880 | // %8 = intcast(@res_ty, %y) |
| 881 | // %9 = br(%x, %8) |
| 882 | // }) |
| 883 | // }) |
| 884 | |
| 885 | try l.air_instructions.ensureUnusedCapacity(gpa, 12); |
| 886 | var body_inst_buf: [12]Air.Inst.Index = undefined; |
| 887 | var condbr_buf: [2]CondBr = undefined; |
| 888 | var condbr_idx: usize = 0; |
| 889 | |
| 890 | var main_block: Block = .init(&body_inst_buf); |
| 891 | var cur_block: *Block = &main_block; |
| 892 | |
| 893 | const panic_id: Zcu.SimplePanicId = if (dest_is_enum) .invalid_enum_value else .cast_truncated_data; |
| 894 | |
| 895 | if (have_min_check or have_max_check) { |
| 896 | const dest_int_ty = if (dest_is_enum) dest_ty.intTagType(zcu) else dest_ty; |
| 897 | const condbr = &condbr_buf[condbr_idx]; |
| 898 | condbr_idx += 1; |
| 899 | const below_min_inst: Air.Inst.Index = if (have_min_check) inst: { |
| 900 | const min_val_ref = Air.internedToRef((try dest_int_ty.minInt(pt, operand_ty)).toIntern()); |
| 901 | break :inst try cur_block.addCmp(l, is_vector, .lt, operand_ref, min_val_ref); |
| 902 | } else undefined; |
| 903 | const above_max_inst: Air.Inst.Index = if (have_max_check) inst: { |
| 904 | const max_val_ref = Air.internedToRef((try dest_int_ty.maxInt(pt, operand_ty)).toIntern()); |
| 905 | break :inst try cur_block.addCmp(l, is_vector, .gt, operand_ref, max_val_ref); |
| 906 | } else undefined; |
| 907 | const out_of_range_inst: Air.Inst.Index = inst: { |
| 908 | if (have_min_check and have_max_check) break :inst cur_block.add(l, .{ |
| 909 | .tag = .bool_or, |
| 910 | .data = .{ .bin_op = .{ |
| 911 | .lhs = below_min_inst.toRef(), |
| 912 | .rhs = above_max_inst.toRef(), |
| 913 | } }, |
| 914 | }); |
| 915 | if (have_min_check) break :inst below_min_inst; |
| 916 | if (have_max_check) break :inst above_max_inst; |
| 917 | unreachable; |
| 918 | }; |
| 919 | const scalar_out_of_range_inst: Air.Inst.Index = if (is_vector) cur_block.add(l, .{ |
| 920 | .tag = .reduce, |
| 921 | .data = .{ .reduce = .{ |
| 922 | .operand = out_of_range_inst.toRef(), |
| 923 | .operation = .Or, |
| 924 | } }, |
| 925 | }) else out_of_range_inst; |
| 926 | condbr.* = .init(l, scalar_out_of_range_inst.toRef(), cur_block, .{ |
| 927 | .true = .cold, |
| 928 | .false = .none, |
| 929 | }); |
| 930 | condbr.then_block = .init(cur_block.stealRemainingCapacity()); |
| 931 | try condbr.then_block.addPanic(l, panic_id); |
| 932 | condbr.else_block = .init(condbr.then_block.stealRemainingCapacity()); |
| 933 | cur_block = &condbr.else_block; |
| 934 | } |
| 935 | |
| 936 | // Now we know we're in-range, we can intcast: |
| 937 | const cast_inst = cur_block.add(l, .{ |
| 938 | .tag = .intcast, |
| 939 | .data = .{ .ty_op = .{ |
| 940 | .ty = Air.internedToRef(dest_ty.toIntern()), |
| 941 | .operand = operand_ref, |
| 942 | } }, |
| 943 | }); |
| 944 | // For ints we're already done, but for exhaustive enums we must check this is a valid tag. |
| 945 | if (dest_is_enum and !dest_ty.isNonexhaustiveEnum(zcu) and zcu.backendSupportsFeature(.is_named_enum_value)) { |
| 946 | assert(!is_vector); // vectors of enums don't exist |
| 947 | // We are building this: |
| 948 | // %1 = is_named_enum_value(%cast_inst) |
| 949 | // %2 = cond_br(%1, { |
| 950 | // <new cursor> |
| 951 | // }, { |
| 952 | // <panic> |
| 953 | // }) |
| 954 | const is_named_inst = cur_block.add(l, .{ |
| 955 | .tag = .is_named_enum_value, |
| 956 | .data = .{ .un_op = cast_inst.toRef() }, |
| 957 | }); |
| 958 | const condbr = &condbr_buf[condbr_idx]; |
| 959 | condbr_idx += 1; |
| 960 | condbr.* = .init(l, is_named_inst.toRef(), cur_block, .{ |
| 961 | .true = .none, |
| 962 | .false = .cold, |
| 963 | }); |
| 964 | condbr.else_block = .init(cur_block.stealRemainingCapacity()); |
| 965 | try condbr.else_block.addPanic(l, panic_id); |
| 966 | condbr.then_block = .init(condbr.else_block.stealRemainingCapacity()); |
| 967 | cur_block = &condbr.then_block; |
| 968 | } |
| 969 | // Finally, just `br` to our outer `block`. |
| 970 | _ = cur_block.add(l, .{ |
| 971 | .tag = .br, |
| 972 | .data = .{ .br = .{ |
| 973 | .block_inst = orig_inst, |
| 974 | .operand = cast_inst.toRef(), |
| 975 | } }, |
| 976 | }); |
| 977 | // We might not have used all of the instructions; that's intentional. |
| 978 | _ = cur_block.stealRemainingCapacity(); |
| 979 | |
| 980 | for (condbr_buf[0..condbr_idx]) |*condbr| try condbr.finish(l); |
| 981 | return .{ .ty_pl = .{ |
| 982 | .ty = Air.internedToRef(dest_ty.toIntern()), |
| 983 | .payload = try l.addBlockBody(main_block.body()), |
| 984 | } }; |
| 985 | } |
| 986 | fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_op_tag: Air.Inst.Tag) Error!Air.Inst.Data { |
| 987 | const pt = l.pt; |
| 988 | const zcu = pt.zcu; |
| 989 | const gpa = zcu.gpa; |
| 990 | const bin_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].bin_op; |
| 991 | |
| 992 | const operand_ty = l.typeOf(bin_op.lhs); |
| 993 | assert(l.typeOf(bin_op.rhs).toIntern() == operand_ty.toIntern()); |
| 994 | const is_vector = operand_ty.zigTypeTag(zcu) == .vector; |
| 995 | |
| 996 | const overflow_tuple_ty = try pt.overflowArithmeticTupleType(operand_ty); |
| 997 | const overflow_bits_ty = overflow_tuple_ty.fieldType(1, zcu); |
| 998 | |
| 999 | // The worst-case scenario is a vector operand: |
| 1000 | // |
| 1001 | // %1 = add_with_overflow(%x, %y) |
| 1002 | // %2 = struct_field_val(%1, .@"1") |
| 1003 | // %3 = reduce(%2, .@"or") |
| 1004 | // %4 = bitcast(%3, @bool_type) |
| 1005 | // %5 = cond_br(%4, { |
| 1006 | // %6 = call(@panic.integerOverflow, []) |
| 1007 | // %7 = unreach() |
| 1008 | // }, { |
| 1009 | // %8 = struct_field_val(%1, .@"0") |
| 1010 | // %9 = br(%z, %8) |
| 1011 | // }) |
| 1012 | try l.air_instructions.ensureUnusedCapacity(gpa, 9); |
| 1013 | var body_inst_buf: [9]Air.Inst.Index = undefined; |
| 1014 | |
| 1015 | var main_block: Block = .init(&body_inst_buf); |
| 1016 | |
| 1017 | const overflow_op_inst = main_block.add(l, .{ |
| 1018 | .tag = overflow_op_tag, |
| 1019 | .data = .{ .ty_pl = .{ |
| 1020 | .ty = Air.internedToRef(overflow_tuple_ty.toIntern()), |
| 1021 | .payload = try l.addExtra(Air.Bin, .{ |
| 1022 | .lhs = bin_op.lhs, |
| 1023 | .rhs = bin_op.rhs, |
| 1024 | }), |
| 1025 | } }, |
| 1026 | }); |
| 1027 | const overflow_bits_inst = main_block.add(l, .{ |
| 1028 | .tag = .struct_field_val, |
| 1029 | .data = .{ .ty_pl = .{ |
| 1030 | .ty = Air.internedToRef(overflow_bits_ty.toIntern()), |
| 1031 | .payload = try l.addExtra(Air.StructField, .{ |
| 1032 | .struct_operand = overflow_op_inst.toRef(), |
| 1033 | .field_index = 1, |
| 1034 | }), |
| 1035 | } }, |
| 1036 | }); |
| 1037 | const any_overflow_bit_inst = if (is_vector) main_block.add(l, .{ |
| 1038 | .tag = .reduce, |
| 1039 | .data = .{ .reduce = .{ |
| 1040 | .operand = overflow_bits_inst.toRef(), |
| 1041 | .operation = .Or, |
| 1042 | } }, |
| 1043 | }) else overflow_bits_inst; |
| 1044 | const any_overflow_inst = try main_block.addCmp(l, false, .eq, any_overflow_bit_inst.toRef(), .one_u1); |
| 1045 | |
| 1046 | var condbr: CondBr = .init(l, any_overflow_inst.toRef(), &main_block, .{ |
| 1047 | .true = .cold, |
| 1048 | .false = .none, |
| 1049 | }); |
| 1050 | condbr.then_block = .init(main_block.stealRemainingCapacity()); |
| 1051 | try condbr.then_block.addPanic(l, .integer_overflow); |
| 1052 | condbr.else_block = .init(condbr.then_block.stealRemainingCapacity()); |
| 1053 | |
| 1054 | const result_inst = condbr.else_block.add(l, .{ |
| 1055 | .tag = .struct_field_val, |
| 1056 | .data = .{ .ty_pl = .{ |
| 1057 | .ty = Air.internedToRef(operand_ty.toIntern()), |
| 1058 | .payload = try l.addExtra(Air.StructField, .{ |
| 1059 | .struct_operand = overflow_op_inst.toRef(), |
| 1060 | .field_index = 0, |
| 1061 | }), |
| 1062 | } }, |
| 1063 | }); |
| 1064 | _ = condbr.else_block.add(l, .{ |
| 1065 | .tag = .br, |
| 1066 | .data = .{ .br = .{ |
| 1067 | .block_inst = orig_inst, |
| 1068 | .operand = result_inst.toRef(), |
| 1069 | } }, |
| 1070 | }); |
| 1071 | // We might not have used all of the instructions; that's intentional. |
| 1072 | _ = condbr.else_block.stealRemainingCapacity(); |
| 1073 | |
| 1074 | try condbr.finish(l); |
| 1075 | return .{ .ty_pl = .{ |
| 1076 | .ty = Air.internedToRef(operand_ty.toIntern()), |
| 1077 | .payload = try l.addBlockBody(main_block.body()), |
| 1078 | } }; |
| 1079 | } |
| 771 | 1080 | |
| 772 | | fn Block(comptime capacity: usize) type { |
| 773 | | return struct { |
| 774 | | instructions: [capacity]Air.Inst.Index, |
| 775 | | len: usize, |
| 1081 | const Block = struct { |
| 1082 | instructions: []Air.Inst.Index, |
| 1083 | len: usize, |
| 776 | 1084 | |
| 777 | | const empty: @This() = .{ |
| 778 | | .instructions = undefined, |
| 1085 | /// There are two common usages of the API: |
| 1086 | /// * `buf.len` is exactly the number of instructions which will be in this block |
| 1087 | /// * `buf.len` is no smaller than necessary, and `b.stealRemainingCapacity` will be used |
| 1088 | fn init(buf: []Air.Inst.Index) Block { |
| 1089 | return .{ |
| 1090 | .instructions = buf, |
| 779 | 1091 | .len = 0, |
| 780 | 1092 | }; |
| 1093 | } |
| 781 | 1094 | |
| 782 | | fn add(b: *@This(), inst: Air.Inst.Index) Air.Inst.Index { |
| 783 | | b.instructions[b.len] = inst; |
| 784 | | b.len += 1; |
| 785 | | return inst; |
| 1095 | /// Like `Legalize.addInstAssumeCapacity`, but also appends the instruction to `b`. |
| 1096 | fn add(b: *Block, l: *Legalize, inst_data: Air.Inst) Air.Inst.Index { |
| 1097 | const inst = l.addInstAssumeCapacity(inst_data); |
| 1098 | b.instructions[b.len] = inst; |
| 1099 | b.len += 1; |
| 1100 | return inst; |
| 1101 | } |
| 1102 | |
| 1103 | /// Adds the code to call the panic handler `panic_id`. This is usually `.call` then `.unreach`, |
| 1104 | /// but if `Zcu.Feature.panic_fn` is unsupported, we lower to `.trap` instead. |
| 1105 | fn addPanic(b: *Block, l: *Legalize, panic_id: Zcu.SimplePanicId) Error!void { |
| 1106 | const zcu = l.pt.zcu; |
| 1107 | if (!zcu.backendSupportsFeature(.panic_fn)) { |
| 1108 | _ = b.add(l, .{ |
| 1109 | .tag = .trap, |
| 1110 | .data = .{ .no_op = {} }, |
| 1111 | }); |
| 1112 | return; |
| 786 | 1113 | } |
| 1114 | const panic_fn_val = zcu.builtin_decl_values.get(panic_id.toBuiltin()); |
| 1115 | _ = b.add(l, .{ |
| 1116 | .tag = .call, |
| 1117 | .data = .{ .pl_op = .{ |
| 1118 | .operand = Air.internedToRef(panic_fn_val), |
| 1119 | .payload = try l.addExtra(Air.Call, .{ .args_len = 0 }), |
| 1120 | } }, |
| 1121 | }); |
| 1122 | _ = b.add(l, .{ |
| 1123 | .tag = .unreach, |
| 1124 | .data = .{ .no_op = {} }, |
| 1125 | }); |
| 1126 | } |
| 787 | 1127 | |
| 788 | | fn body(b: *const @This()) []const Air.Inst.Index { |
| 789 | | assert(b.len == b.instructions.len); |
| 790 | | return &b.instructions; |
| 1128 | /// Adds a `cmp_*` instruction (including maybe `cmp_vector`) to `b`. This is a fairly thin wrapper |
| 1129 | /// around `add`, although it does compute the result type if `is_vector` (`@Vector(n, bool)`). |
| 1130 | fn addCmp( |
| 1131 | b: *Block, |
| 1132 | l: *Legalize, |
| 1133 | is_vector: bool, |
| 1134 | op: std.math.CompareOperator, |
| 1135 | lhs: Air.Inst.Ref, |
| 1136 | rhs: Air.Inst.Ref, |
| 1137 | ) Error!Air.Inst.Index { |
| 1138 | const pt = l.pt; |
| 1139 | if (is_vector) { |
| 1140 | const bool_vec_ty = try pt.vectorType(.{ |
| 1141 | .child = .bool_type, |
| 1142 | .len = l.typeOf(lhs).vectorLen(pt.zcu), |
| 1143 | }); |
| 1144 | return b.add(l, .{ |
| 1145 | .tag = .cmp_vector, |
| 1146 | .data = .{ .ty_pl = .{ |
| 1147 | .ty = Air.internedToRef(bool_vec_ty.toIntern()), |
| 1148 | .payload = try l.addExtra(Air.VectorCmp, .{ |
| 1149 | .lhs = lhs, |
| 1150 | .rhs = rhs, |
| 1151 | .op = Air.VectorCmp.encodeOp(op), |
| 1152 | }), |
| 1153 | } }, |
| 1154 | }); |
| 791 | 1155 | } |
| 1156 | return b.add(l, .{ |
| 1157 | .tag = switch (op) { |
| 1158 | .lt => .cmp_lt, |
| 1159 | .lte => .cmp_lte, |
| 1160 | .eq => .cmp_eq, |
| 1161 | .gte => .cmp_gte, |
| 1162 | .gt => .cmp_gt, |
| 1163 | .neq => .cmp_neq, |
| 1164 | }, |
| 1165 | .data = .{ .bin_op = .{ |
| 1166 | .lhs = lhs, |
| 1167 | .rhs = rhs, |
| 1168 | } }, |
| 1169 | }); |
| 1170 | } |
| 1171 | |
| 1172 | /// Returns the unused capacity of `b.instructions`, and shrinks `b.instructions` down to `b.len`. |
| 1173 | /// This is useful when you've provided a buffer big enough for all your instructions, but you are |
| 1174 | /// now starting a new block and some of them need to live there instead. |
| 1175 | fn stealRemainingCapacity(b: *Block) []Air.Inst.Index { |
| 1176 | const remaining = b.instructions[b.len..]; |
| 1177 | b.instructions = b.instructions[0..b.len]; |
| 1178 | return remaining; |
| 1179 | } |
| 1180 | |
| 1181 | fn body(b: *const Block) []const Air.Inst.Index { |
| 1182 | assert(b.len == b.instructions.len); |
| 1183 | return b.instructions; |
| 1184 | } |
| 1185 | }; |
| 1186 | |
| 1187 | const CondBr = struct { |
| 1188 | inst: Air.Inst.Index, |
| 1189 | hints: BranchHints, |
| 1190 | then_block: Block, |
| 1191 | else_block: Block, |
| 1192 | |
| 1193 | const BranchHints = struct { |
| 1194 | true: std.builtin.BranchHint, |
| 1195 | false: std.builtin.BranchHint, |
| 792 | 1196 | }; |
| 793 | | } |
| 1197 | |
| 1198 | /// The return value has `then_block` and `else_block` initialized to `undefined`; it is the |
| 1199 | /// caller's reponsibility to initialize them. |
| 1200 | fn init(l: *Legalize, operand: Air.Inst.Ref, parent_block: *Block, hints: BranchHints) CondBr { |
| 1201 | return .{ |
| 1202 | .inst = parent_block.add(l, .{ |
| 1203 | .tag = .cond_br, |
| 1204 | .data = .{ .pl_op = .{ |
| 1205 | .operand = operand, |
| 1206 | .payload = undefined, |
| 1207 | } }, |
| 1208 | }), |
| 1209 | .hints = hints, |
| 1210 | .then_block = undefined, |
| 1211 | .else_block = undefined, |
| 1212 | }; |
| 1213 | } |
| 1214 | |
| 1215 | fn finish(cond_br: CondBr, l: *Legalize) Error!void { |
| 1216 | const data = &l.air_instructions.items(.data)[@intFromEnum(cond_br.inst)]; |
| 1217 | data.pl_op.payload = try l.addCondBrBodiesHints( |
| 1218 | cond_br.then_block.body(), |
| 1219 | cond_br.else_block.body(), |
| 1220 | .{ |
| 1221 | .true = cond_br.hints.true, |
| 1222 | .false = cond_br.hints.false, |
| 1223 | .then_cov = .none, |
| 1224 | .else_cov = .none, |
| 1225 | }, |
| 1226 | ); |
| 1227 | } |
| 1228 | }; |
| 794 | 1229 | |
| 795 | 1230 | fn addInstAssumeCapacity(l: *Legalize, inst: Air.Inst) Air.Inst.Index { |
| 796 | 1231 | defer l.air_instructions.appendAssumeCapacity(inst); |
| ... | ... | @@ -818,17 +1253,20 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 { |
| 818 | 1253 | } |
| 819 | 1254 | |
| 820 | 1255 | fn addCondBrBodies(l: *Legalize, then_body: []const Air.Inst.Index, else_body: []const Air.Inst.Index) Error!u32 { |
| 1256 | return l.addCondBrBodiesHints(then_body, else_body, .{ |
| 1257 | .true = .none, |
| 1258 | .false = .none, |
| 1259 | .then_cov = .none, |
| 1260 | .else_cov = .none, |
| 1261 | }); |
| 1262 | } |
| 1263 | fn addCondBrBodiesHints(l: *Legalize, then_body: []const Air.Inst.Index, else_body: []const Air.Inst.Index, hints: Air.CondBr.BranchHints) Error!u32 { |
| 821 | 1264 | try l.air_extra.ensureUnusedCapacity(l.pt.zcu.gpa, 3 + then_body.len + else_body.len); |
| 822 | 1265 | defer { |
| 823 | 1266 | l.air_extra.appendSliceAssumeCapacity(&.{ |
| 824 | 1267 | @intCast(then_body.len), |
| 825 | 1268 | @intCast(else_body.len), |
| 826 | | @bitCast(Air.CondBr.BranchHints{ |
| 827 | | .true = .none, |
| 828 | | .false = .none, |
| 829 | | .then_cov = .none, |
| 830 | | .else_cov = .none, |
| 831 | | }), |
| 1269 | @bitCast(hints), |
| 832 | 1270 | }); |
| 833 | 1271 | l.air_extra.appendSliceAssumeCapacity(@ptrCast(then_body)); |
| 834 | 1272 | l.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body)); |