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 {...@@ -50,8 +50,6 @@ pub const Inst = struct {
50 /// is the same as both operands.50 /// is the same as both operands.
51 /// The panic handler function must be populated before lowering AIR51 /// The panic handler function must be populated before lowering AIR
52 /// that contains this instruction.52 /// that contains this instruction.
53 /// This instruction will only be emitted if the backend has the
54 /// feature `safety_checked_instructions`.
55 /// Uses the `bin_op` field.53 /// Uses the `bin_op` field.
56 add_safe,54 add_safe,
57 /// Float addition. The instruction is allowed to have equal or more55 /// Float addition. The instruction is allowed to have equal or more
...@@ -79,8 +77,6 @@ pub const Inst = struct {...@@ -79,8 +77,6 @@ pub const Inst = struct {
79 /// is the same as both operands.77 /// is the same as both operands.
80 /// The panic handler function must be populated before lowering AIR78 /// The panic handler function must be populated before lowering AIR
81 /// that contains this instruction.79 /// that contains this instruction.
82 /// This instruction will only be emitted if the backend has the
83 /// feature `safety_checked_instructions`.
84 /// Uses the `bin_op` field.80 /// Uses the `bin_op` field.
85 sub_safe,81 sub_safe,
86 /// Float subtraction. The instruction is allowed to have equal or more82 /// Float subtraction. The instruction is allowed to have equal or more
...@@ -108,8 +104,6 @@ pub const Inst = struct {...@@ -108,8 +104,6 @@ pub const Inst = struct {
108 /// is the same as both operands.104 /// is the same as both operands.
109 /// The panic handler function must be populated before lowering AIR105 /// The panic handler function must be populated before lowering AIR
110 /// that contains this instruction.106 /// that contains this instruction.
111 /// This instruction will only be emitted if the backend has the
112 /// feature `safety_checked_instructions`.
113 /// Uses the `bin_op` field.107 /// Uses the `bin_op` field.
114 mul_safe,108 mul_safe,
115 /// Float multiplication. The instruction is allowed to have equal or more109 /// 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 {...@@ -81,6 +81,19 @@ pub const Feature = enum {
81 /// Legalize reduce of a one element vector to a bitcast81 /// Legalize reduce of a one element vector to a bitcast
82 reduce_one_elem_to_bitcast,82 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
84 fn scalarize(tag: Air.Inst.Tag) Feature {97 fn scalarize(tag: Air.Inst.Tag) Feature {
85 return switch (tag) {98 return switch (tag) {
86 else => unreachable,99 else => unreachable,
...@@ -205,17 +218,14 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -205,17 +218,14 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
205 .arg,218 .arg,
206 => {},219 => {},
207 inline .add,220 inline .add,
208 .add_safe,
209 .add_optimized,221 .add_optimized,
210 .add_wrap,222 .add_wrap,
211 .add_sat,223 .add_sat,
212 .sub,224 .sub,
213 .sub_safe,
214 .sub_optimized,225 .sub_optimized,
215 .sub_wrap,226 .sub_wrap,
216 .sub_sat,227 .sub_sat,
217 .mul,228 .mul,
218 .mul_safe,
219 .mul_optimized,229 .mul_optimized,
220 .mul_wrap,230 .mul_wrap,
221 .mul_sat,231 .mul_sat,
...@@ -240,6 +250,27 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -240,6 +250,27 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
240 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;250 const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
241 if (l.typeOf(bin_op.lhs).isVector(zcu)) continue :inst try l.scalarize(inst, .bin_op);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 .ptr_add,274 .ptr_add,
244 .ptr_sub,275 .ptr_sub,
245 .add_with_overflow,276 .add_with_overflow,
...@@ -295,7 +326,6 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -295,7 +326,6 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
295 .fptrunc,326 .fptrunc,
296 .fpext,327 .fpext,
297 .intcast,328 .intcast,
298 .intcast_safe,
299 .trunc,329 .trunc,
300 .int_from_float,330 .int_from_float,
301 .int_from_float_optimized,331 .int_from_float_optimized,
...@@ -312,6 +342,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {...@@ -312,6 +342,13 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
312 if (to_ty.isVector(zcu) and from_ty.isVector(zcu) and to_ty.vectorLen(zcu) == from_ty.vectorLen(zcu))342 if (to_ty.isVector(zcu) and from_ty.isVector(zcu) and to_ty.vectorLen(zcu) == from_ty.vectorLen(zcu))
313 continue :inst try l.scalarize(inst, .ty_op);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 .block,352 .block,
316 .loop,353 .loop,
317 => {354 => {
...@@ -550,81 +587,83 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_...@@ -550,81 +587,83 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_
550 const expected_instructions_len = l.air_instructions.len + (6 + arity + 8);587 const expected_instructions_len = l.air_instructions.len + (6 + arity + 8);
551 try l.air_instructions.ensureTotalCapacity(gpa, expected_instructions_len);588 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);
554 {592 {
555 const res_alloc_inst = res_block.add(l.addInstAssumeCapacity(.{593 const res_alloc_inst = res_block.add(l, .{
556 .tag = .alloc,594 .tag = .alloc,
557 .data = .{ .ty = try pt.singleMutPtrType(res_ty) },595 .data = .{ .ty = try pt.singleMutPtrType(res_ty) },
558 }));596 });
559 const index_alloc_inst = res_block.add(l.addInstAssumeCapacity(.{597 const index_alloc_inst = res_block.add(l, .{
560 .tag = .alloc,598 .tag = .alloc,
561 .data = .{ .ty = .ptr_usize },599 .data = .{ .ty = .ptr_usize },
562 }));600 });
563 _ = res_block.add(l.addInstAssumeCapacity(.{601 _ = res_block.add(l, .{
564 .tag = .store,602 .tag = .store,
565 .data = .{ .bin_op = .{603 .data = .{ .bin_op = .{
566 .lhs = index_alloc_inst.toRef(),604 .lhs = index_alloc_inst.toRef(),
567 .rhs = .zero_usize,605 .rhs = .zero_usize,
568 } },606 } },
569 }));607 });
570608
571 const loop_inst: Air.Inst.Index = @enumFromInt(l.air_instructions.len + (3 + arity + 7));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 .tag = .load,614 .tag = .load,
576 .data = .{ .ty_op = .{615 .data = .{ .ty_op = .{
577 .ty = .usize_type,616 .ty = .usize_type,
578 .operand = index_alloc_inst.toRef(),617 .operand = index_alloc_inst.toRef(),
579 } },618 } },
580 }));619 });
581 _ = loop_block.add(l.addInstAssumeCapacity(.{620 _ = loop_block.add(l, .{
582 .tag = .vector_store_elem,621 .tag = .vector_store_elem,
583 .data = .{ .vector_store_elem = .{622 .data = .{ .vector_store_elem = .{
584 .vector_ptr = res_alloc_inst.toRef(),623 .vector_ptr = res_alloc_inst.toRef(),
585 .payload = try l.addExtra(Air.Bin, .{624 .payload = try l.addExtra(Air.Bin, .{
586 .lhs = cur_index_inst.toRef(),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 .un_op => .{627 .un_op => .{
589 .tag = orig.tag,628 .tag = orig.tag,
590 .data = .{ .un_op = loop_block.add(l.addInstAssumeCapacity(.{629 .data = .{ .un_op = loop_block.add(l, .{
591 .tag = .array_elem_val,630 .tag = .array_elem_val,
592 .data = .{ .bin_op = .{631 .data = .{ .bin_op = .{
593 .lhs = orig.data.un_op,632 .lhs = orig.data.un_op,
594 .rhs = cur_index_inst.toRef(),633 .rhs = cur_index_inst.toRef(),
595 } },634 } },
596 })).toRef() },635 }).toRef() },
597 },636 },
598 .ty_op => .{637 .ty_op => .{
599 .tag = orig.tag,638 .tag = orig.tag,
600 .data = .{ .ty_op = .{639 .data = .{ .ty_op = .{
601 .ty = Air.internedToRef(orig.data.ty_op.ty.toType().scalarType(zcu).toIntern()),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 .tag = .array_elem_val,642 .tag = .array_elem_val,
604 .data = .{ .bin_op = .{643 .data = .{ .bin_op = .{
605 .lhs = orig.data.ty_op.operand,644 .lhs = orig.data.ty_op.operand,
606 .rhs = cur_index_inst.toRef(),645 .rhs = cur_index_inst.toRef(),
607 } },646 } },
608 })).toRef(),647 }).toRef(),
609 } },648 } },
610 },649 },
611 .bin_op => .{650 .bin_op => .{
612 .tag = orig.tag,651 .tag = orig.tag,
613 .data = .{ .bin_op = .{652 .data = .{ .bin_op = .{
614 .lhs = loop_block.add(l.addInstAssumeCapacity(.{653 .lhs = loop_block.add(l, .{
615 .tag = .array_elem_val,654 .tag = .array_elem_val,
616 .data = .{ .bin_op = .{655 .data = .{ .bin_op = .{
617 .lhs = orig.data.bin_op.lhs,656 .lhs = orig.data.bin_op.lhs,
618 .rhs = cur_index_inst.toRef(),657 .rhs = cur_index_inst.toRef(),
619 } },658 } },
620 })).toRef(),659 }).toRef(),
621 .rhs = loop_block.add(l.addInstAssumeCapacity(.{660 .rhs = loop_block.add(l, .{
622 .tag = .array_elem_val,661 .tag = .array_elem_val,
623 .data = .{ .bin_op = .{662 .data = .{ .bin_op = .{
624 .lhs = orig.data.bin_op.rhs,663 .lhs = orig.data.bin_op.rhs,
625 .rhs = cur_index_inst.toRef(),664 .rhs = cur_index_inst.toRef(),
626 } },665 } },
627 })).toRef(),666 }).toRef(),
628 } },667 } },
629 },668 },
630 .ty_pl_vector_cmp => {669 .ty_pl_vector_cmp => {
...@@ -650,20 +689,20 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_...@@ -650,20 +689,20 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_
650 },689 },
651 },690 },
652 .data = .{ .bin_op = .{691 .data = .{ .bin_op = .{
653 .lhs = loop_block.add(l.addInstAssumeCapacity(.{692 .lhs = loop_block.add(l, .{
654 .tag = .array_elem_val,693 .tag = .array_elem_val,
655 .data = .{ .bin_op = .{694 .data = .{ .bin_op = .{
656 .lhs = extra.lhs,695 .lhs = extra.lhs,
657 .rhs = cur_index_inst.toRef(),696 .rhs = cur_index_inst.toRef(),
658 } },697 } },
659 })).toRef(),698 }).toRef(),
660 .rhs = loop_block.add(l.addInstAssumeCapacity(.{699 .rhs = loop_block.add(l, .{
661 .tag = .array_elem_val,700 .tag = .array_elem_val,
662 .data = .{ .bin_op = .{701 .data = .{ .bin_op = .{
663 .lhs = extra.rhs,702 .lhs = extra.rhs,
664 .rhs = cur_index_inst.toRef(),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,94 +712,96 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_
673 .tag = orig.tag,712 .tag = orig.tag,
674 .data = .{ .pl_op = .{713 .data = .{ .pl_op = .{
675 .payload = try l.addExtra(Air.Bin, .{714 .payload = try l.addExtra(Air.Bin, .{
676 .lhs = loop_block.add(l.addInstAssumeCapacity(.{715 .lhs = loop_block.add(l, .{
677 .tag = .array_elem_val,716 .tag = .array_elem_val,
678 .data = .{ .bin_op = .{717 .data = .{ .bin_op = .{
679 .lhs = extra.lhs,718 .lhs = extra.lhs,
680 .rhs = cur_index_inst.toRef(),719 .rhs = cur_index_inst.toRef(),
681 } },720 } },
682 })).toRef(),721 }).toRef(),
683 .rhs = loop_block.add(l.addInstAssumeCapacity(.{722 .rhs = loop_block.add(l, .{
684 .tag = .array_elem_val,723 .tag = .array_elem_val,
685 .data = .{ .bin_op = .{724 .data = .{ .bin_op = .{
686 .lhs = extra.rhs,725 .lhs = extra.rhs,
687 .rhs = cur_index_inst.toRef(),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 .tag = .array_elem_val,731 .tag = .array_elem_val,
693 .data = .{ .bin_op = .{732 .data = .{ .bin_op = .{
694 .lhs = orig.data.pl_op.operand,733 .lhs = orig.data.pl_op.operand,
695 .rhs = cur_index_inst.toRef(),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 }));743 });
705 const not_done_inst = loop_block.add(l.addInstAssumeCapacity(.{744 const not_done_inst = loop_block.add(l, .{
706 .tag = .cmp_lt,745 .tag = .cmp_lt,
707 .data = .{ .bin_op = .{746 .data = .{ .bin_op = .{
708 .lhs = cur_index_inst.toRef(),747 .lhs = cur_index_inst.toRef(),
709 .rhs = try pt.intRef(.usize, res_ty.vectorLen(zcu) - 1),748 .rhs = try pt.intRef(.usize, res_ty.vectorLen(zcu) - 1),
710 } },749 } },
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);
714 {754 {
715 _ = not_done_block.add(l.addInstAssumeCapacity(.{755 _ = not_done_block.add(l, .{
716 .tag = .store,756 .tag = .store,
717 .data = .{ .bin_op = .{757 .data = .{ .bin_op = .{
718 .lhs = index_alloc_inst.toRef(),758 .lhs = index_alloc_inst.toRef(),
719 .rhs = not_done_block.add(l.addInstAssumeCapacity(.{759 .rhs = not_done_block.add(l, .{
720 .tag = .add,760 .tag = .add,
721 .data = .{ .bin_op = .{761 .data = .{ .bin_op = .{
722 .lhs = cur_index_inst.toRef(),762 .lhs = cur_index_inst.toRef(),
723 .rhs = .one_usize,763 .rhs = .one_usize,
724 } },764 } },
725 })).toRef(),765 }).toRef(),
726 } },766 } },
727 }));767 });
728 _ = not_done_block.add(l.addInstAssumeCapacity(.{768 _ = not_done_block.add(l, .{
729 .tag = .repeat,769 .tag = .repeat,
730 .data = .{ .repeat = .{ .loop_inst = loop_inst } },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 .tag = .br,777 .tag = .br,
737 .data = .{ .br = .{778 .data = .{ .br = .{
738 .block_inst = orig_inst,779 .block_inst = orig_inst,
739 .operand = done_block.add(l.addInstAssumeCapacity(.{780 .operand = done_block.add(l, .{
740 .tag = .load,781 .tag = .load,
741 .data = .{ .ty_op = .{782 .data = .{ .ty_op = .{
742 .ty = Air.internedToRef(res_ty.toIntern()),783 .ty = Air.internedToRef(res_ty.toIntern()),
743 .operand = res_alloc_inst.toRef(),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 .tag = .cond_br,791 .tag = .cond_br,
751 .data = .{ .pl_op = .{792 .data = .{ .pl_op = .{
752 .operand = not_done_inst.toRef(),793 .operand = not_done_inst.toRef(),
753 .payload = try l.addCondBrBodies(not_done_block.body(), done_block.body()),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 .tag = .loop,799 .tag = .loop,
759 .data = .{ .ty_pl = .{800 .data = .{ .ty_pl = .{
760 .ty = .noreturn_type,801 .ty = .noreturn_type,
761 .payload = try l.addBlockBody(loop_block.body()),802 .payload = try l.addBlockBody(loop_block.body()),
762 } },803 } },
763 })));804 }));
764 }805 }
765 assert(l.air_instructions.len == expected_instructions_len);806 assert(l.air_instructions.len == expected_instructions_len);
766 return .{ .ty_pl = .{807 return .{ .ty_pl = .{
...@@ -768,29 +809,423 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_...@@ -768,29 +809,423 @@ fn scalarizeBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, comptime data_
768 .payload = try l.addBlockBody(res_block.body()),809 .payload = try l.addBlockBody(res_block.body()),
769 } };810 } };
770}811}
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 {1081const Block = struct {
773 return struct {1082 instructions: []Air.Inst.Index,
774 instructions: [capacity]Air.Inst.Index,1083 len: usize,
775 len: usize,
7761084
777 const empty: @This() = .{1085 /// There are two common usages of the API:
778 .instructions = undefined,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 .len = 0,1091 .len = 0,
780 };1092 };
1093 }
7811094
782 fn add(b: *@This(), inst: Air.Inst.Index) Air.Inst.Index {1095 /// Like `Legalize.addInstAssumeCapacity`, but also appends the instruction to `b`.
783 b.instructions[b.len] = inst;1096 fn add(b: *Block, l: *Legalize, inst_data: Air.Inst) Air.Inst.Index {
784 b.len += 1;1097 const inst = l.addInstAssumeCapacity(inst_data);
785 return inst;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 }
7871127
788 fn body(b: *const @This()) []const Air.Inst.Index {1128 /// Adds a `cmp_*` instruction (including maybe `cmp_vector`) to `b`. This is a fairly thin wrapper
789 assert(b.len == b.instructions.len);1129 /// around `add`, although it does compute the result type if `is_vector` (`@Vector(n, bool)`).
790 return &b.instructions;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
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,
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};
7941229
795fn addInstAssumeCapacity(l: *Legalize, inst: Air.Inst) Air.Inst.Index {1230fn addInstAssumeCapacity(l: *Legalize, inst: Air.Inst) Air.Inst.Index {
796 defer l.air_instructions.appendAssumeCapacity(inst);1231 defer l.air_instructions.appendAssumeCapacity(inst);
...@@ -818,17 +1253,20 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 {...@@ -818,17 +1253,20 @@ fn addBlockBody(l: *Legalize, body: []const Air.Inst.Index) Error!u32 {
818}1253}
8191254
820fn addCondBrBodies(l: *Legalize, then_body: []const Air.Inst.Index, else_body: []const Air.Inst.Index) Error!u32 {1255fn 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 {
821 try l.air_extra.ensureUnusedCapacity(l.pt.zcu.gpa, 3 + then_body.len + else_body.len);1264 try l.air_extra.ensureUnusedCapacity(l.pt.zcu.gpa, 3 + then_body.len + else_body.len);
822 defer {1265 defer {
823 l.air_extra.appendSliceAssumeCapacity(&.{1266 l.air_extra.appendSliceAssumeCapacity(&.{
824 @intCast(then_body.len),1267 @intCast(then_body.len),
825 @intCast(else_body.len),1268 @intCast(else_body.len),
826 @bitCast(Air.CondBr.BranchHints{1269 @bitCast(hints),
827 .true = .none,
828 .false = .none,
829 .then_cov = .none,
830 .else_cov = .none,
831 }),
832 });1270 });
833 l.air_extra.appendSliceAssumeCapacity(@ptrCast(then_body));1271 l.air_extra.appendSliceAssumeCapacity(@ptrCast(then_body));
834 l.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));1272 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...@@ -8912,21 +8912,10 @@ fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
89128912
8913 try sema.requireRuntimeBlock(block, src, operand_src);8913 try sema.requireRuntimeBlock(block, src, operand_src);
8914 if (block.wantSafety()) {8914 if (block.wantSafety()) {
8915 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {8915 if (zcu.backendSupportsFeature(.panic_fn)) {
8916 _ = try sema.preparePanicId(src, .invalid_enum_value);8916 _ = 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;
8929 }8917 }
8918 return block.addTyOp(.intcast_safe, dest_ty, operand);
8930 }8919 }
8931 return block.addTyOp(.intcast, dest_ty, operand);8920 return block.addTyOp(.intcast, dest_ty, operand);
8932}8921}
...@@ -10331,90 +10320,11 @@ fn intCast(...@@ -10331,90 +10320,11 @@ fn intCast(
1033110320
10332 try sema.requireRuntimeBlock(block, src, operand_src);10321 try sema.requireRuntimeBlock(block, src, operand_src);
10333 if (runtime_safety and block.wantSafety()) {10322 if (runtime_safety and block.wantSafety()) {
10334 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {10323 if (zcu.backendSupportsFeature(.panic_fn)) {
10335 _ = try sema.preparePanicId(src, .negative_to_unsigned);10324 _ = try sema.preparePanicId(src, .negative_to_unsigned);
10336 _ = try sema.preparePanicId(src, .cast_truncated_data);10325 _ = 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);
10417 }10326 }
10327 return block.addTyOp(.intcast_safe, dest_ty, operand);
10418 }10328 }
10419 return block.addTyOp(.intcast, dest_ty, operand);10329 return block.addTyOp(.intcast, dest_ty, operand);
10420}10330}
...@@ -14316,7 +14226,7 @@ fn zirShl(...@@ -14316,7 +14226,7 @@ fn zirShl(
14316 }14226 }
1431714227
14318 if (air_tag == .shl_exact) {14228 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);
14320 const op_ov = try block.addInst(.{14230 const op_ov = try block.addInst(.{
14321 .tag = .shl_with_overflow,14231 .tag = .shl_with_overflow,
14322 .data = .{ .ty_pl = .{14232 .data = .{ .ty_pl = .{
...@@ -16111,7 +16021,7 @@ fn zirOverflowArithmetic(...@@ -16111,7 +16021,7 @@ fn zirOverflowArithmetic(
16111 const maybe_lhs_val = try sema.resolveValue(lhs);16021 const maybe_lhs_val = try sema.resolveValue(lhs);
16112 const maybe_rhs_val = try sema.resolveValue(rhs);16022 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);
16115 const overflow_ty: Type = .fromInterned(ip.indexToKey(tuple_ty.toIntern()).tuple_type.types.get(ip)[1]);16025 const overflow_ty: Type = .fromInterned(ip.indexToKey(tuple_ty.toIntern()).tuple_type.types.get(ip)[1]);
1611616026
16117 var result: struct {16027 var result: struct {
...@@ -16284,24 +16194,6 @@ fn splat(sema: *Sema, ty: Type, val: Value) !Value {...@@ -16284,24 +16194,6 @@ fn splat(sema: *Sema, ty: Type, val: Value) !Value {
16284 return Value.fromInterned(repeated);16194 return Value.fromInterned(repeated);
16285}16195}
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
16305fn analyzeArithmetic(16197fn analyzeArithmetic(
16306 sema: *Sema,16198 sema: *Sema,
16307 block: *Block,16199 block: *Block,
...@@ -16477,41 +16369,10 @@ fn analyzeArithmetic(...@@ -16477,41 +16369,10 @@ fn analyzeArithmetic(
16477 }16369 }
1647816370
16479 if (block.wantSafety() and want_safety and scalar_tag == .int) {16371 if (block.wantSafety() and want_safety and scalar_tag == .int) {
16480 if (zcu.backendSupportsFeature(.safety_checked_instructions)) {16372 if (air_tag != air_tag_safe and zcu.backendSupportsFeature(.panic_fn)) {
16481 if (air_tag != air_tag_safe) {16373 _ = try sema.preparePanicId(src, .integer_overflow);
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 }
16514 }16374 }
16375 return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
16515 }16376 }
16516 return block.addBinOp(air_tag, casted_lhs, casted_rhs);16377 return block.addBinOp(air_tag, casted_lhs, casted_rhs);
16517}16378}
src/Zcu.zig-9
...@@ -3829,15 +3829,6 @@ pub const Feature = enum {...@@ -3829,15 +3829,6 @@ pub const Feature = enum {
3829 is_named_enum_value,3829 is_named_enum_value,
3830 error_set_has_value,3830 error_set_has_value,
3831 field_reordering,3831 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,
3841 /// If the backend supports running from another thread.3832 /// If the backend supports running from another thread.
3842 separate_thread,3833 separate_thread,
3843};3834};
src/Zcu/PerThread.zig+15
...@@ -3844,6 +3844,21 @@ pub fn nullValue(pt: Zcu.PerThread, opt_ty: Type) Allocator.Error!Value {...@@ -3844,6 +3844,21 @@ pub fn nullValue(pt: Zcu.PerThread, opt_ty: Type) Allocator.Error!Value {
3844 } }));3844 } }));
3845}3845}
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
3847pub fn smallestUnsignedInt(pt: Zcu.PerThread, max: u64) Allocator.Error!Type {3862pub fn smallestUnsignedInt(pt: Zcu.PerThread, max: u64) Allocator.Error!Type {
3848 return pt.intType(.unsigned, Type.smallestUnsignedBits(max));3863 return pt.intType(.unsigned, Type.smallestUnsignedBits(max));
3849}3864}
src/arch/riscv64/CodeGen.zig+6-1
...@@ -52,7 +52,12 @@ const Instruction = encoding.Instruction;...@@ -52,7 +52,12 @@ const Instruction = encoding.Instruction;
52const InnerError = CodeGenError || error{OutOfRegisters};52const InnerError = CodeGenError || error{OutOfRegisters};
5353
54pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {54pub 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 });
56}61}
5762
58pt: Zcu.PerThread,63pt: Zcu.PerThread,
src/arch/wasm/CodeGen.zig+6-1
...@@ -32,7 +32,12 @@ const compilerRtFloatAbbrev = target_util.compilerRtFloatAbbrev;...@@ -32,7 +32,12 @@ const compilerRtFloatAbbrev = target_util.compilerRtFloatAbbrev;
32const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;32const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
3333
34pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {34pub 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 });
36}41}
3742
38/// Reference to the function declaration the code43/// 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...@@ -88,6 +88,10 @@ pub fn legalizeFeatures(target: *const std.Target) *const Air.Legalize.Features
8888
89 .unsplat_shift_rhs = false,89 .unsplat_shift_rhs = false,
90 .reduce_one_elem_to_bitcast = true,90 .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,
91 }),95 }),
92 };96 };
93}97}
src/codegen/spirv.zig+6-1
...@@ -29,7 +29,12 @@ const SpvAssembler = @import("spirv/Assembler.zig");...@@ -29,7 +29,12 @@ const SpvAssembler = @import("spirv/Assembler.zig");
29const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);29const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
3030
31pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {31pub 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 });
33}38}
3439
35pub const zig_call_abi_ver = 3;40pub const zig_call_abi_ver = 3;
src/target.zig-4
...@@ -842,10 +842,6 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt...@@ -842,10 +842,6 @@ pub inline fn backendSupportsFeature(backend: std.builtin.CompilerBackend, compt
842 .stage2_c, .stage2_llvm, .stage2_x86_64 => true,842 .stage2_c, .stage2_llvm, .stage2_x86_64 => true,
843 else => false,843 else => false,
844 },844 },
845 .safety_checked_instructions => switch (backend) {
846 .stage2_llvm => true,
847 else => false,
848 },
849 .separate_thread => switch (backend) {845 .separate_thread => switch (backend) {
850 .stage2_llvm => false,846 .stage2_llvm => false,
851 else => true,847 else => true,