authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-05-30 17:12:11+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-01 08:24:00+01:00
log4c4dacf81a5da85a1f7d1550ed45f5cb20fd1524
treedc8ed2db66482c0f8706b5e730ebcddd4f3155b7
parent77e6513030a6258a893c2f11ad9708c9612b7715
signaturelock-open Commit is signed but in an unrecognized format.

Legalize: replace `safety_checked_instructions`

This adds 4 `Legalize.Feature`s: * `expand_intcast_safe` * `expand_add_safe` * `expand_sub_safe` * `expand_mul_safe` These do pretty much what they say on the tin. This logic was previously in Sema, used when `Zcu.Feature.safety_checked_instructions` was not supported by the backend. That `Zcu.Feature` has been removed in favour of this legalization.

10 files changed, 558 insertions(+), 244 deletions(-)

src/Air.zig-6
......@@ -50,8 +50,6 @@ pub const Inst = struct {
5050 /// is the same as both operands.
5151 /// The panic handler function must be populated before lowering AIR
5252 /// that contains this instruction.
53 /// This instruction will only be emitted if the backend has the
54 /// feature `safety_checked_instructions`.
5553 /// Uses the `bin_op` field.
5654 add_safe,
5755 /// Float addition. The instruction is allowed to have equal or more
......@@ -79,8 +77,6 @@ pub const Inst = struct {
7977 /// is the same as both operands.
8078 /// The panic handler function must be populated before lowering AIR
8179 /// that contains this instruction.
82 /// This instruction will only be emitted if the backend has the
83 /// feature `safety_checked_instructions`.
8480 /// Uses the `bin_op` field.
8581 sub_safe,
8682 /// Float subtraction. The instruction is allowed to have equal or more
......@@ -108,8 +104,6 @@ pub const Inst = struct {
108104 /// is the same as both operands.
109105 /// The panic handler function must be populated before lowering AIR
110106 /// that contains this instruction.
111 /// This instruction will only be emitted if the backend has the
112 /// feature `safety_checked_instructions`.
113107 /// Uses the `bin_op` field.
114108 mul_safe,
115109 /// Float multiplication. The instruction is allowed to have equal or more
src/Air/Legalize.zig+512-74
......@@ -81,6 +81,19 @@ pub const Feature = enum {
8181 /// Legalize reduce of a one element vector to a bitcast
8282 reduce_one_elem_to_bitcast,
8383
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
8497 fn scalarize(tag: Air.Inst.Tag) Feature {
8598 return switch (tag) {
8699 else => unreachable,
......@@ -205,17 +218,14 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
205218 .arg,
206219 => {},
207220 inline .add,
208 .add_safe,
209221 .add_optimized,
210222 .add_wrap,
211223 .add_sat,
212224 .sub,
213 .sub_safe,
214225 .sub_optimized,
215226 .sub_wrap,
216227 .sub_sat,
217228 .mul,
218 .mul_safe,
219229 .mul_optimized,
220230 .mul_wrap,
221231 .mul_sat,
......@@ -240,6 +250,27 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
240250 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
241251 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);
242252 },
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 },
243274 .ptr_add,
244275 .ptr_sub,
245276 .add_with_overflow,
......@@ -295,7 +326,6 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
295326 .fptrunc,
296327 .fpext,
297328 .intcast,
298 .intcast_safe,
299329 .trunc,
300330 .int_from_float,
301331 .int_from_float_optimized,
......@@ -312,6 +342,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
312342 if (to_ty.isVector(zcu) and from_ty.isVector(zcu) and to_ty.vectorLen(zcu) == from_ty.vectorLen(zcu))
313343 continue :inst try l.scalarize(inst, .ty_op);
314344 },
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 },
315352 .block,
316353 .loop,
317354 => {
......@@ -550,81 +587,83 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_
550587 const expected_instructions_len = l.air_instructions.len + (6 + arity + 8);
551588 try l.air_instructions.ensureTotalCapacity(gpa, expected_instructions_len);
552589
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);
554592 {
555 const res_alloc_inst = res_block.add(l.addInstAssumeCapacity(.{
593 const res_alloc_inst = res_block.add(l, .{
556594 .tag = .alloc,
557595 .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, .{
560598 .tag = .alloc,
561599 .data = .{ .ty = .ptr_usize },
562 }));
563 _ = res_block.add(l.addInstAssumeCapacity(.{
600 });
601 _ = res_block.add(l, .{
564602 .tag = .store,
565603 .data = .{ .bin_op = .{
566604 .lhs = index_alloc_inst.toRef(),
567605 .rhs = .zero_usize,
568606 } },
569 }));
607 });
570608
571609 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);
573612 {
574 const cur_index_inst = loop_block.add(l.addInstAssumeCapacity(.{
613 const cur_index_inst = loop_block.add(l, .{
575614 .tag = .load,
576615 .data = .{ .ty_op = .{
577616 .ty = .usize_type,
578617 .operand = index_alloc_inst.toRef(),
579618 } },
580 }));
581 _ = loop_block.add(l.addInstAssumeCapacity(.{
619 });
620 _ = loop_block.add(l, .{
582621 .tag = .vector_store_elem,
583622 .data = .{ .vector_store_elem = .{
584623 .vector_ptr = res_alloc_inst.toRef(),
585624 .payload = try l.addExtra(Air.Bin, .{
586625 .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) {
588627 .un_op => .{
589628 .tag = orig.tag,
590 .data = .{ .un_op = loop_block.add(l.addInstAssumeCapacity(.{
629 .data = .{ .un_op = loop_block.add(l, .{
591630 .tag = .array_elem_val,
592631 .data = .{ .bin_op = .{
593632 .lhs = orig.data.un_op,
594633 .rhs = cur_index_inst.toRef(),
595634 } },
596 })).toRef() },
635 }).toRef() },
597636 },
598637 .ty_op => .{
599638 .tag = orig.tag,
600639 .data = .{ .ty_op = .{
601640 .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, .{
603642 .tag = .array_elem_val,
604643 .data = .{ .bin_op = .{
605644 .lhs = orig.data.ty_op.operand,
606645 .rhs = cur_index_inst.toRef(),
607646 } },
608 })).toRef(),
647 }).toRef(),
609648 } },
610649 },
611650 .bin_op => .{
612651 .tag = orig.tag,
613652 .data = .{ .bin_op = .{
614 .lhs = loop_block.add(l.addInstAssumeCapacity(.{
653 .lhs = loop_block.add(l, .{
615654 .tag = .array_elem_val,
616655 .data = .{ .bin_op = .{
617656 .lhs = orig.data.bin_op.lhs,
618657 .rhs = cur_index_inst.toRef(),
619658 } },
620 })).toRef(),
621 .rhs = loop_block.add(l.addInstAssumeCapacity(.{
659 }).toRef(),
660 .rhs = loop_block.add(l, .{
622661 .tag = .array_elem_val,
623662 .data = .{ .bin_op = .{
624663 .lhs = orig.data.bin_op.rhs,
625664 .rhs = cur_index_inst.toRef(),
626665 } },
627 })).toRef(),
666 }).toRef(),
628667 } },
629668 },
630669 .ty_pl_vector_cmp => {
......@@ -650,20 +689,20 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_
650689 },
651690 },
652691 .data = .{ .bin_op = .{
653 .lhs = loop_block.add(l.addInstAssumeCapacity(.{
692 .lhs = loop_block.add(l, .{
654693 .tag = .array_elem_val,
655694 .data = .{ .bin_op = .{
656695 .lhs = extra.lhs,
657696 .rhs = cur_index_inst.toRef(),
658697 } },
659 })).toRef(),
660 .rhs = loop_block.add(l.addInstAssumeCapacity(.{
698 }).toRef(),
699 .rhs = loop_block.add(l, .{
661700 .tag = .array_elem_val,
662701 .data = .{ .bin_op = .{
663702 .lhs = extra.rhs,
664703 .rhs = cur_index_inst.toRef(),
665704 } },
666 })).toRef(),
705 }).toRef(),
667706 } },
668707 };
669708 },
......@@ -673,94 +712,96 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_
673712 .tag = orig.tag,
674713 .data = .{ .pl_op = .{
675714 .payload = try l.addExtra(Air.Bin, .{
676 .lhs = loop_block.add(l.addInstAssumeCapacity(.{
715 .lhs = loop_block.add(l, .{
677716 .tag = .array_elem_val,
678717 .data = .{ .bin_op = .{
679718 .lhs = extra.lhs,
680719 .rhs = cur_index_inst.toRef(),
681720 } },
682 })).toRef(),
683 .rhs = loop_block.add(l.addInstAssumeCapacity(.{
721 }).toRef(),
722 .rhs = loop_block.add(l, .{
684723 .tag = .array_elem_val,
685724 .data = .{ .bin_op = .{
686725 .lhs = extra.rhs,
687726 .rhs = cur_index_inst.toRef(),
688727 } },
689 })).toRef(),
728 }).toRef(),
690729 }),
691 .operand = loop_block.add(l.addInstAssumeCapacity(.{
730 .operand = loop_block.add(l, .{
692731 .tag = .array_elem_val,
693732 .data = .{ .bin_op = .{
694733 .lhs = orig.data.pl_op.operand,
695734 .rhs = cur_index_inst.toRef(),
696735 } },
697 })).toRef(),
736 }).toRef(),
698737 } },
699738 };
700739 },
701 })).toRef(),
740 }).toRef(),
702741 }),
703742 } },
704 }));
705 const not_done_inst = loop_block.add(l.addInstAssumeCapacity(.{
743 });
744 const not_done_inst = loop_block.add(l, .{
706745 .tag = .cmp_lt,
707746 .data = .{ .bin_op = .{
708747 .lhs = cur_index_inst.toRef(),
709748 .rhs = try pt.intRef(.usize, res_ty.vectorLen(zcu) - 1),
710749 } },
711 }));
750 });
712751
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);
714754 {
715 _ = not_done_block.add(l.addInstAssumeCapacity(.{
755 _ = not_done_block.add(l, .{
716756 .tag = .store,
717757 .data = .{ .bin_op = .{
718758 .lhs = index_alloc_inst.toRef(),
719 .rhs = not_done_block.add(l.addInstAssumeCapacity(.{
759 .rhs = not_done_block.add(l, .{
720760 .tag = .add,
721761 .data = .{ .bin_op = .{
722762 .lhs = cur_index_inst.toRef(),
723763 .rhs = .one_usize,
724764 } },
725 })).toRef(),
765 }).toRef(),
726766 } },
727 }));
728 _ = not_done_block.add(l.addInstAssumeCapacity(.{
767 });
768 _ = not_done_block.add(l, .{
729769 .tag = .repeat,
730770 .data = .{ .repeat = .{ .loop_inst = loop_inst } },
731 }));
771 });
732772 }
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);
734775 {
735 _ = done_block.add(l.addInstAssumeCapacity(.{
776 _ = done_block.add(l, .{
736777 .tag = .br,
737778 .data = .{ .br = .{
738779 .block_inst = orig_inst,
739 .operand = done_block.add(l.addInstAssumeCapacity(.{
780 .operand = done_block.add(l, .{
740781 .tag = .load,
741782 .data = .{ .ty_op = .{
742783 .ty = Air.internedToRef(res_ty.toIntern()),
743784 .operand = res_alloc_inst.toRef(),
744785 } },
745 })).toRef(),
786 }).toRef(),
746787 } },
747 }));
788 });
748789 }
749 _ = loop_block.add(l.addInstAssumeCapacity(.{
790 _ = loop_block.add(l, .{
750791 .tag = .cond_br,
751792 .data = .{ .pl_op = .{
752793 .operand = not_done_inst.toRef(),
753794 .payload = try l.addCondBrBodies(not_done_block.body(), done_block.body()),
754795 } },
755 }));
796 });
756797 }
757 assert(loop_inst == res_block.add(l.addInstAssumeCapacity(.{
798 assert(loop_inst == res_block.add(l, .{
758799 .tag = .loop,
759800 .data = .{ .ty_pl = .{
760801 .ty = .noreturn_type,
761802 .payload = try l.addBlockBody(loop_block.body()),
762803 } },
763 })));
804 }));
764805 }
765806 assert(l.air_instructions.len == expected_instructions_len);
766807 return .{ .ty_pl = .{
......@@ -768,29 +809,423 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_
768809 .payload = try l.addBlockBody(res_block.body()),
769810 } };
770811}
812fn 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}
986fn 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}
7711080
772fn Block(comptime capacity: usize) type {
773 return struct {
774 instructions: [capacity]Air.Inst.Index,
775 len: usize,
1081const Block = struct {
1082 instructions: []Air.Inst.Index,
1083 len: usize,
7761084
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,
7791091 .len = 0,
7801092 };
1093 }
7811094
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;
7861113 }
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 }
7871127
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 });
7911155 }
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
1187const 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,
7921196 };
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};
7941229
7951230fn addInstAssumeCapacity(l: *Legalize, inst: Air.Inst) Air.Inst.Index {
7961231 defer l.air_instructions.appendAssumeCapacity(inst);
......@@ -818,17 +1253,20 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 {
8181253}
8191254
8201255fn 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}
1263fn addCondBrBodiesHints(l: *Legalize, then_body: []const Air.Inst.Index, else_body: []const Air.Inst.Index, hints: Air.CondBr.BranchHints) Error!u32 {
8211264 try l.air_extra.ensureUnusedCapacity(l.pt.zcu.gpa, 3 + then_body.len + else_body.len);
8221265 defer {
8231266 l.air_extra.appendSliceAssumeCapacity(&.{
8241267 @intCast(then_body.len),
8251268 @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),
8321270 });
8331271 l.air_extra.appendSliceAssumeCapacity(@ptrCast(then_body));
8341272 l.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));
src/Sema.zig+9-148
......@@ -8912,21 +8912,10 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
89128912
89138913 try sema.requireRuntimeBlock(block, src, operand_src);
89148914 if (block.wantSafety()) {
8915 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
8915 if (zcu.backendSupportsFeature(.panic_fn)) {
89168916 _ = try sema.preparePanicId(src, .invalid_enum_value);
8917 return block.addTyOp(.intcast_safe, dest_ty, operand);
8918 } else {
8919 // Slightly silly fallback case...
8920 const int_tag_ty = dest_ty.intTagType(zcu);
8921 // Use `intCast`, since it'll set up the Sema-emitted safety checks for us!
8922 const int_val = try sema.intCast(block, src, int_tag_ty, src, operand, src, true, true);
8923 const result = try block.addBitCast(dest_ty, int_val);
8924 if (!dest_ty.isNonexhaustiveEnum(zcu) and zcu.backendSupportsFeature(.is_named_enum_value)) {
8925 const ok = try block.addUnOp(.is_named_enum_value, result);
8926 try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);
8927 }
8928 return result;
89298917 }
8918 return block.addTyOp(.intcast_safe, dest_ty, operand);
89308919 }
89318920 return block.addTyOp(.intcast, dest_ty, operand);
89328921}
......@@ -10331,90 +10320,11 @@ fn intCast(
1033110320
1033210321 try sema.requireRuntimeBlock(block, src, operand_src);
1033310322 if (runtime_safety and block.wantSafety()) {
10334 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
10323 if (zcu.backendSupportsFeature(.panic_fn)) {
1033510324 _ = try sema.preparePanicId(src, .negative_to_unsigned);
1033610325 _ = try sema.preparePanicId(src, .cast_truncated_data);
10337 return block.addTyOp(.intcast_safe, dest_ty, operand);
10338 }
10339 const actual_info = operand_scalar_ty.intInfo(zcu);
10340 const wanted_info = dest_scalar_ty.intInfo(zcu);
10341 const actual_bits = actual_info.bits;
10342 const wanted_bits = wanted_info.bits;
10343 const actual_value_bits = actual_bits - @intFromBool(actual_info.signedness == .signed);
10344 const wanted_value_bits = wanted_bits - @intFromBool(wanted_info.signedness == .signed);
10345
10346 // range shrinkage
10347 // requirement: int value fits into target type
10348 if (wanted_value_bits < actual_value_bits) {
10349 const dest_max_val_scalar = try dest_scalar_ty.maxIntScalar(pt, operand_scalar_ty);
10350 const dest_max_val = try sema.splat(operand_ty, dest_max_val_scalar);
10351 const dest_max = Air.internedToRef(dest_max_val.toIntern());
10352
10353 if (actual_info.signedness == .signed) {
10354 const diff = try block.addBinOp(.sub_wrap, dest_max, operand);
10355
10356 // Reinterpret the sign-bit as part of the value. This will make
10357 // negative differences (`operand` > `dest_max`) appear too big.
10358 const unsigned_scalar_operand_ty = try pt.intType(.unsigned, actual_bits);
10359 const unsigned_operand_ty = if (is_vector) try pt.vectorType(.{
10360 .len = dest_ty.vectorLen(zcu),
10361 .child = unsigned_scalar_operand_ty.toIntern(),
10362 }) else unsigned_scalar_operand_ty;
10363 const diff_unsigned = try block.addBitCast(unsigned_operand_ty, diff);
10364
10365 // If the destination type is signed, then we need to double its
10366 // range to account for negative values.
10367 const dest_range_val = if (wanted_info.signedness == .signed) range_val: {
10368 const one_scalar = try pt.intValue(unsigned_scalar_operand_ty, 1);
10369 const one = if (is_vector) Value.fromInterned(try pt.intern(.{ .aggregate = .{
10370 .ty = unsigned_operand_ty.toIntern(),
10371 .storage = .{ .repeated_elem = one_scalar.toIntern() },
10372 } })) else one_scalar;
10373 const range_minus_one = try dest_max_val.shl(one, unsigned_operand_ty, sema.arena, pt);
10374 const result = try arith.addWithOverflow(sema, unsigned_operand_ty, range_minus_one, one);
10375 assert(result.overflow_bit.compareAllWithZero(.eq, zcu));
10376 break :range_val result.wrapped_result;
10377 } else try pt.getCoerced(dest_max_val, unsigned_operand_ty);
10378 const dest_range = Air.internedToRef(dest_range_val.toIntern());
10379
10380 const ok = if (is_vector) ok: {
10381 const is_in_range = try block.addCmpVector(diff_unsigned, dest_range, .lte);
10382 const all_in_range = try block.addReduce(is_in_range, .And);
10383 break :ok all_in_range;
10384 } else ok: {
10385 const is_in_range = try block.addBinOp(.cmp_lte, diff_unsigned, dest_range);
10386 break :ok is_in_range;
10387 };
10388 // TODO negative_to_unsigned?
10389 try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
10390 } else {
10391 const ok = if (is_vector) ok: {
10392 const is_in_range = try block.addCmpVector(operand, dest_max, .lte);
10393 const all_in_range = try block.addReduce(is_in_range, .And);
10394 break :ok all_in_range;
10395 } else ok: {
10396 const is_in_range = try block.addBinOp(.cmp_lte, operand, dest_max);
10397 break :ok is_in_range;
10398 };
10399 try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .cast_truncated_data);
10400 }
10401 } else if (actual_info.signedness == .signed and wanted_info.signedness == .unsigned) {
10402 // no shrinkage, yes sign loss
10403 // requirement: signed to unsigned >= 0
10404 const ok = if (is_vector) ok: {
10405 const scalar_zero = try pt.intValue(operand_scalar_ty, 0);
10406 const zero_val = try sema.splat(operand_ty, scalar_zero);
10407 const zero_inst = Air.internedToRef(zero_val.toIntern());
10408 const is_in_range = try block.addCmpVector(operand, zero_inst, .gte);
10409 const all_in_range = try block.addReduce(is_in_range, .And);
10410 break :ok all_in_range;
10411 } else ok: {
10412 const zero_inst = Air.internedToRef((try pt.intValue(operand_ty, 0)).toIntern());
10413 const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst);
10414 break :ok is_in_range;
10415 };
10416 try sema.addSafetyCheck(block, src, ok, if (safety_panics_are_enum) .invalid_enum_value else .negative_to_unsigned);
1041710326 }
10327 return block.addTyOp(.intcast_safe, dest_ty, operand);
1041810328 }
1041910329 return block.addTyOp(.intcast, dest_ty, operand);
1042010330}
......@@ -14316,7 +14226,7 @@ fn zirShl(
1431614226 }
1431714227
1431814228 if (air_tag == .shl_exact) {
14319 const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty);
14229 const op_ov_tuple_ty = try pt.overflowArithmeticTupleType(lhs_ty);
1432014230 const op_ov = try block.addInst(.{
1432114231 .tag = .shl_with_overflow,
1432214232 .data = .{ .ty_pl = .{
......@@ -16111,7 +16021,7 @@ fn zirOverflowArithmetic(
1611116021 const maybe_lhs_val = try sema.resolveValue(lhs);
1611216022 const maybe_rhs_val = try sema.resolveValue(rhs);
1611316023
16114 const tuple_ty = try sema.overflowArithmeticTupleType(dest_ty);
16024 const tuple_ty = try pt.overflowArithmeticTupleType(dest_ty);
1611516025 const overflow_ty: Type = .fromInterned(ip.indexToKey(tuple_ty.toIntern()).tuple_type.types.get(ip)[1]);
1611616026
1611716027 var result: struct {
......@@ -16284,24 +16194,6 @@ fn splat(sema: *Sema, ty: Type, val: Value) !Value {
1628416194 return Value.fromInterned(repeated);
1628516195}
1628616196
16287fn overflowArithmeticTupleType(sema: *Sema, ty: Type) !Type {
16288 const pt = sema.pt;
16289 const zcu = pt.zcu;
16290 const ip = &zcu.intern_pool;
16291 const ov_ty: Type = if (ty.zigTypeTag(zcu) == .vector) try pt.vectorType(.{
16292 .len = ty.vectorLen(zcu),
16293 .child = .u1_type,
16294 }) else .u1;
16295
16296 const types = [2]InternPool.Index{ ty.toIntern(), ov_ty.toIntern() };
16297 const values = [2]InternPool.Index{ .none, .none };
16298 const tuple_ty = try ip.getTupleType(zcu.gpa, pt.tid, .{
16299 .types = &types,
16300 .values = &values,
16301 });
16302 return .fromInterned(tuple_ty);
16303}
16304
1630516197fn analyzeArithmetic(
1630616198 sema: *Sema,
1630716199 block: *Block,
......@@ -16477,41 +16369,10 @@ fn analyzeArithmetic(
1647716369 }
1647816370
1647916371 if (block.wantSafety() and want_safety and scalar_tag == .int) {
16480 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {
16481 if (air_tag != air_tag_safe) {
16482 _ = try sema.preparePanicId(src, .integer_overflow);
16483 }
16484 return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
16485 } else {
16486 const maybe_op_ov: ?Air.Inst.Tag = switch (air_tag) {
16487 .add => .add_with_overflow,
16488 .sub => .sub_with_overflow,
16489 .mul => .mul_with_overflow,
16490 else => null,
16491 };
16492 if (maybe_op_ov) |op_ov_tag| {
16493 const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(resolved_type);
16494 const op_ov = try block.addInst(.{
16495 .tag = op_ov_tag,
16496 .data = .{ .ty_pl = .{
16497 .ty = Air.internedToRef(op_ov_tuple_ty.toIntern()),
16498 .payload = try sema.addExtra(Air.Bin{
16499 .lhs = casted_lhs,
16500 .rhs = casted_rhs,
16501 }),
16502 } },
16503 });
16504 const ov_bit = try sema.tupleFieldValByIndex(block, op_ov, 1, op_ov_tuple_ty);
16505 const any_ov_bit = if (resolved_type.zigTypeTag(zcu) == .vector)
16506 try block.addReduce(ov_bit, .Or)
16507 else
16508 ov_bit;
16509 const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, .zero_u1);
16510
16511 try sema.addSafetyCheck(block, src, no_ov, .integer_overflow);
16512 return sema.tupleFieldValByIndex(block, op_ov, 0, op_ov_tuple_ty);
16513 }
16372 if (air_tag != air_tag_safe and zcu.backendSupportsFeature(.panic_fn)) {
16373 _ = try sema.preparePanicId(src, .integer_overflow);
1651416374 }
16375 return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
1651516376 }
1651616377 return block.addBinOp(air_tag, casted_lhs, casted_rhs);
1651716378}
src/Zcu.zig-9
......@@ -3829,15 +3829,6 @@ pub const Feature = enum {
38293829 is_named_enum_value,
38303830 error_set_has_value,
38313831 field_reordering,
3832 /// When this feature is supported, the backend supports the following AIR instructions:
3833 /// * `Air.Inst.Tag.add_safe`
3834 /// * `Air.Inst.Tag.sub_safe`
3835 /// * `Air.Inst.Tag.mul_safe`
3836 /// * `Air.Inst.Tag.intcast_safe`
3837 /// The motivation for this feature is that it makes AIR smaller, and makes it easier
3838 /// to generate better machine code in the backends. All backends should migrate to
3839 /// enabling this feature.
3840 safety_checked_instructions,
38413832 /// If the backend supports running from another thread.
38423833 separate_thread,
38433834};
src/Zcu/PerThread.zig+15
......@@ -3844,6 +3844,21 @@ pub fn nullValue(pt: Zcu.PerThread, opt_ty: Type) Allocator.Error!Value {
38443844 } }));
38453845}
38463846
3847/// `ty` is an integer or a vector of integers.
3848pub fn overflowArithmeticTupleType(pt: Zcu.PerThread, ty: Type) !Type {
3849 const zcu = pt.zcu;
3850 const ip = &zcu.intern_pool;
3851 const ov_ty: Type = if (ty.zigTypeTag(zcu) == .vector) try pt.vectorType(.{
3852 .len = ty.vectorLen(zcu),
3853 .child = .u1_type,
3854 }) else .u1;
3855 const tuple_ty = try ip.getTupleType(zcu.gpa, pt.tid, .{
3856 .types = &.{ ty.toIntern(), ov_ty.toIntern() },
3857 .values = &.{ .none, .none },
3858 });
3859 return .fromInterned(tuple_ty);
3860}
3861
38473862pub fn smallestUnsignedInt(pt: Zcu.PerThread, max: u64) Allocator.Error!Type {
38483863 return pt.intType(.unsigned, Type.smallestUnsignedBits(max));
38493864}
src/arch/riscv64/CodeGen.zig+6-1
......@@ -52,7 +52,12 @@ const Instruction = encoding.Instruction;
5252const InnerError = CodeGenError || error{OutOfRegisters};
5353
5454pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
55 return null;
55 return comptime &.initMany(&.{
56 .expand_intcast_safe,
57 .expand_add_safe,
58 .expand_sub_safe,
59 .expand_mul_safe,
60 });
5661}
5762
5863pt: Zcu.PerThread,
src/arch/wasm/CodeGen.zig+6-1
......@@ -32,7 +32,12 @@ const compilerRtFloatAbbrev = target_util.compilerRtFloatAbbrev;
3232const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
3333
3434pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
35 return null;
35 return comptime &.initMany(&.{
36 .expand_intcast_safe,
37 .expand_add_safe,
38 .expand_sub_safe,
39 .expand_mul_safe,
40 });
3641}
3742
3843/// Reference to the function declaration the code
src/arch/x86_64/CodeGen.zig+4
......@@ -88,6 +88,10 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features
8888
8989 .unsplat_shift_rhs = false,
9090 .reduce_one_elem_to_bitcast = true,
91 .expand_intcast_safe = true,
92 .expand_add_safe = true,
93 .expand_sub_safe = true,
94 .expand_mul_safe = true,
9195 }),
9296 };
9397}
src/codegen/spirv.zig+6-1
......@@ -29,7 +29,12 @@ const SpvAssembler = @import("spirv/Assembler.zig");
2929const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
3030
3131pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
32 return null;
32 return comptime &.initMany(&.{
33 .expand_intcast_safe,
34 .expand_add_safe,
35 .expand_sub_safe,
36 .expand_mul_safe,
37 });
3338}
3439
3540pub const zig_call_abi_ver = 3;
src/target.zig-4
......@@ -842,10 +842,6 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt
842842 .stage2_c, .stage2_llvm, .stage2_x86_64 => true,
843843 else => false,
844844 },
845 .safety_checked_instructions => switch (backend) {
846 .stage2_llvm => true,
847 else => false,
848 },
849845 .separate_thread => switch (backend) {
850846 .stage2_llvm => false,
851847 else => true,