authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-28 20:46:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-29 02:35:06-07:00
log40f8f0134f5da9baaefd0fdab529d5585fa46199
treed0204669c74b50f4ab971ea9422ac93685baafb5
parent1fc24e8d807a489254be46c9fcb951617a04f3b1

Sema: enhance div_trunc, div_exact, div_floor

* No longer emit div_exact AIR instruction that can produce a remainder, invoking undefined behavior. * div_trunc, div_exact, div_floor are extracted from analyzeArithmetic and directly handled similarly to div_trunc, integrating them with integer overflow safety checking. * Also they no longer emit divide-by-zero safety checking when RHS is comptime known to be non-zero.

2 files changed, 480 insertions(+), 332 deletions(-)

src/Air.zig+3-2
......@@ -111,8 +111,9 @@ pub const Inst = struct {
111111 div_floor,
112112 /// Same as `div_floor` with optimized float mode.
113113 div_floor_optimized,
114 /// Integer or float division. Guaranteed no remainder.
115 /// For integers, wrapping is undefined behavior.
114 /// Integer or float division.
115 /// If a remainder would be produced, undefined behavior occurs.
116 /// For integers, overflow is undefined behavior.
116117 /// Both operands are guaranteed to be the same type, and the result type
117118 /// is the same as both operands.
118119 /// Uses the `bin_op` field.
src/Sema.zig+477-330
......@@ -875,10 +875,6 @@ fn analyzeBodyInner(
875875 .add => try sema.zirArithmetic(block, inst, .add),
876876 .addwrap => try sema.zirArithmetic(block, inst, .addwrap),
877877 .add_sat => try sema.zirArithmetic(block, inst, .add_sat),
878 .div => try sema.zirDiv(block, inst),
879 .div_exact => try sema.zirArithmetic(block, inst, .div_exact),
880 .div_floor => try sema.zirArithmetic(block, inst, .div_floor),
881 .div_trunc => try sema.zirArithmetic(block, inst, .div_trunc),
882878 .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem),
883879 .mod => try sema.zirArithmetic(block, inst, .mod),
884880 .rem => try sema.zirArithmetic(block, inst, .rem),
......@@ -889,6 +885,11 @@ fn analyzeBodyInner(
889885 .subwrap => try sema.zirArithmetic(block, inst, .subwrap),
890886 .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
891887
888 .div => try sema.zirDiv(block, inst),
889 .div_exact => try sema.zirDivExact(block, inst),
890 .div_floor => try sema.zirDivFloor(block, inst),
891 .div_trunc => try sema.zirDivTrunc(block, inst),
892
892893 .maximum => try sema.zirMinMax(block, inst, .max),
893894 .minimum => try sema.zirMinMax(block, inst, .min),
894895
......@@ -10999,6 +11000,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
1099911000 if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
1100011001 return sema.failWithDivideByZero(block, rhs_src);
1100111002 }
11003 // TODO: if the RHS is one, return the LHS directly
1100211004 }
1100311005 },
1100411006 else => {},
......@@ -11041,112 +11043,506 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
1104111043 try sema.requireRuntimeBlock(block, src, runtime_src);
1104211044
1104311045 if (block.wantSafety()) {
11044 int_overflow: {
11045 if (!is_int) break :int_overflow;
11046 try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
11047 try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int);
11048 }
1104611049
11047 // If the LHS is unsigned, it cannot cause overflow.
11048 if (!lhs_scalar_ty.isSignedInt()) break :int_overflow;
11050 const air_tag = if (is_int) Air.Inst.Tag.div_trunc else switch (block.float_mode) {
11051 .Optimized => Air.Inst.Tag.div_float_optimized,
11052 .Strict => Air.Inst.Tag.div_float,
11053 };
11054 return block.addBinOp(air_tag, casted_lhs, casted_rhs);
11055}
1104911056
11050 // If the LHS is widened to a larger integer type, no overflow is possible.
11051 if (lhs_scalar_ty.intInfo(target).bits < resolved_type.intInfo(target).bits) {
11052 break :int_overflow;
11053 }
11057fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
11058 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
11059 const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
11060 const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
11061 const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
11062 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
11063 const lhs = try sema.resolveInst(extra.lhs);
11064 const rhs = try sema.resolveInst(extra.rhs);
11065 const lhs_ty = sema.typeOf(lhs);
11066 const rhs_ty = sema.typeOf(rhs);
11067 const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
11068 const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
11069 try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
11070 try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_exact);
1105411071
11055 const min_int = try resolved_type.minInt(sema.arena, target);
11056 const neg_one = try Value.Tag.int_i64.create(sema.arena, -1);
11072 const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
11073 const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
11074 .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
11075 });
1105711076
11058 // If the LHS is comptime-known to be not equal to the min int,
11059 // no overflow is possible.
11060 if (maybe_lhs_val) |lhs_val| {
11061 if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) break :int_overflow;
11062 }
11077 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
11078 const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
1106311079
11064 // If the RHS is comptime-known to not be equal to -1, no overflow is possible.
11065 if (maybe_rhs_val) |rhs_val| {
11066 if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) break :int_overflow;
11067 }
11080 const lhs_scalar_ty = lhs_ty.scalarType();
11081 const scalar_tag = resolved_type.scalarType().zigTypeTag();
1106811082
11069 var ok: Air.Inst.Ref = .none;
11070 if (resolved_type.zigTypeTag() == .Vector) {
11071 const vector_ty_ref = try sema.addType(resolved_type);
11072 if (maybe_lhs_val == null) {
11073 const min_int_ref = try sema.addConstant(
11083 const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
11084
11085 try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_exact);
11086
11087 const mod = sema.mod;
11088 const target = mod.getTarget();
11089 const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs);
11090 const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs);
11091
11092 const runtime_src = rs: {
11093 // For integers:
11094 // If the lhs is zero, then zero is returned regardless of rhs.
11095 // If the rhs is zero, compile error for division by zero.
11096 // If the rhs is undefined, compile error because there is a possible
11097 // value (zero) for which the division would be illegal behavior.
11098 // If the lhs is undefined, compile error because there is a possible
11099 // value for which the division would result in a remainder.
11100 // TODO: emit runtime safety for if there is a remainder
11101 // TODO: emit runtime safety for division by zero
11102 //
11103 // For floats:
11104 // If the rhs is zero, compile error for division by zero.
11105 // If the rhs is undefined, compile error because there is a possible
11106 // value (zero) for which the division would be illegal behavior.
11107 // If the lhs is undefined, compile error because there is a possible
11108 // value for which the division would result in a remainder.
11109 if (maybe_lhs_val) |lhs_val| {
11110 if (lhs_val.isUndef()) {
11111 return sema.failWithUseOfUndef(block, rhs_src);
11112 } else {
11113 if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11114 return sema.addConstant(resolved_type, Value.zero);
11115 }
11116 }
11117 }
11118 if (maybe_rhs_val) |rhs_val| {
11119 if (rhs_val.isUndef()) {
11120 return sema.failWithUseOfUndef(block, rhs_src);
11121 }
11122 if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11123 return sema.failWithDivideByZero(block, rhs_src);
11124 }
11125 // TODO: if the RHS is one, return the LHS directly
11126 }
11127 if (maybe_lhs_val) |lhs_val| {
11128 if (maybe_rhs_val) |rhs_val| {
11129 if (is_int) {
11130 // TODO: emit compile error if there is a remainder
11131 return sema.addConstant(
1107411132 resolved_type,
11075 try Value.Tag.repeated.create(sema.arena, min_int),
11133 try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
1107611134 );
11077 ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref);
11078 }
11079 if (maybe_rhs_val == null) {
11080 const neg_one_ref = try sema.addConstant(
11135 } else {
11136 // TODO: emit compile error if there is a remainder
11137 return sema.addConstant(
1108111138 resolved_type,
11082 try Value.Tag.repeated.create(sema.arena, neg_one),
11139 try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target),
1108311140 );
11084 const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref);
11085 if (ok == .none) {
11086 ok = rhs_ok;
11087 } else {
11088 ok = try block.addBinOp(.bool_or, ok, rhs_ok);
11089 }
1109011141 }
11091 assert(ok != .none);
11092 ok = try block.addInst(.{
11093 .tag = .reduce,
11142 } else break :rs rhs_src;
11143 } else break :rs lhs_src;
11144 };
11145
11146 try sema.requireRuntimeBlock(block, src, runtime_src);
11147
11148 // Depending on whether safety is enabled, we will have a slightly different strategy
11149 // here. The `div_exact` AIR instruction causes undefined behavior if a remainder
11150 // is produced, so in the safety check case, it cannot be used. Instead we do a
11151 // div_trunc and check for remainder.
11152
11153 if (block.wantSafety()) {
11154 try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
11155 try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int);
11156
11157 const result = try block.addBinOp(.div_trunc, casted_lhs, casted_rhs);
11158 const ok = if (!is_int) ok: {
11159 const floored = try block.addUnOp(.floor, result);
11160
11161 if (resolved_type.zigTypeTag() == .Vector) {
11162 const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type));
11163 break :ok try block.addInst(.{
11164 .tag = switch (block.float_mode) {
11165 .Strict => .reduce,
11166 .Optimized => .reduce_optimized,
11167 },
1109411168 .data = .{ .reduce = .{
11095 .operand = ok,
11169 .operand = eql,
1109611170 .operation = .And,
1109711171 } },
1109811172 });
1109911173 } else {
11100 if (maybe_lhs_val == null) {
11101 const min_int_ref = try sema.addConstant(resolved_type, min_int);
11102 ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref);
11103 }
11104 if (maybe_rhs_val == null) {
11105 const neg_one_ref = try sema.addConstant(resolved_type, neg_one);
11106 const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref);
11107 if (ok == .none) {
11108 ok = rhs_ok;
11109 } else {
11110 ok = try block.addBinOp(.bool_or, ok, rhs_ok);
11111 }
11112 }
11113 assert(ok != .none);
11174 const is_in_range = try block.addBinOp(switch (block.float_mode) {
11175 .Strict => .cmp_eq,
11176 .Optimized => .cmp_eq_optimized,
11177 }, result, floored);
11178 break :ok is_in_range;
1111411179 }
11115 try sema.addSafetyCheck(block, ok, .integer_overflow);
11116 }
11117
11118 div_by_zero: {
11119 // Strict IEEE floats have well-defined division by zero.
11120 if (!is_int and block.float_mode == .Strict) break :div_by_zero;
11180 } else ok: {
11181 const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs);
1112111182
11122 // If rhs was comptime-known to be zero a compile error would have been
11123 // emitted above.
11124 if (maybe_rhs_val != null) break :div_by_zero;
11125
11126 const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
11183 if (resolved_type.zigTypeTag() == .Vector) {
1112711184 const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
1112811185 const zero = try sema.addConstant(resolved_type, zero_val);
11129 const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type));
11186 const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type));
1113011187 break :ok try block.addInst(.{
11131 .tag = if (is_int) .reduce else .reduce_optimized,
11188 .tag = .reduce,
1113211189 .data = .{ .reduce = .{
11133 .operand = ok,
11190 .operand = eql,
1113411191 .operation = .And,
1113511192 } },
1113611193 });
11137 } else ok: {
11194 } else {
1113811195 const zero = try sema.addConstant(resolved_type, Value.zero);
11139 break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero);
11140 };
11141 try sema.addSafetyCheck(block, ok, .divide_by_zero);
11196 const is_in_range = try block.addBinOp(.cmp_eq, remainder, zero);
11197 break :ok is_in_range;
11198 }
11199 };
11200 try sema.addSafetyCheck(block, ok, .exact_division_remainder);
11201 return result;
11202 }
11203
11204 return block.addBinOp(airTag(block, is_int, .div_exact, .div_exact_optimized), casted_lhs, casted_rhs);
11205}
11206
11207fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
11208 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
11209 const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
11210 const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
11211 const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
11212 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
11213 const lhs = try sema.resolveInst(extra.lhs);
11214 const rhs = try sema.resolveInst(extra.rhs);
11215 const lhs_ty = sema.typeOf(lhs);
11216 const rhs_ty = sema.typeOf(rhs);
11217 const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
11218 const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
11219 try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
11220 try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_floor);
11221
11222 const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
11223 const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
11224 .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
11225 });
11226
11227 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
11228 const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
11229
11230 const lhs_scalar_ty = lhs_ty.scalarType();
11231 const rhs_scalar_ty = rhs_ty.scalarType();
11232 const scalar_tag = resolved_type.scalarType().zigTypeTag();
11233
11234 const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
11235
11236 try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_floor);
11237
11238 const mod = sema.mod;
11239 const target = mod.getTarget();
11240 const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs);
11241 const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs);
11242
11243 const runtime_src = rs: {
11244 // For integers:
11245 // If the lhs is zero, then zero is returned regardless of rhs.
11246 // If the rhs is zero, compile error for division by zero.
11247 // If the rhs is undefined, compile error because there is a possible
11248 // value (zero) for which the division would be illegal behavior.
11249 // If the lhs is undefined:
11250 // * if lhs type is signed:
11251 // * if rhs is comptime-known and not -1, result is undefined
11252 // * if rhs is -1 or runtime-known, compile error because there is a
11253 // possible value (-min_int / -1) for which division would be
11254 // illegal behavior.
11255 // * if lhs type is unsigned, undef is returned regardless of rhs.
11256 // TODO: emit runtime safety for division by zero
11257 //
11258 // For floats:
11259 // If the rhs is zero, compile error for division by zero.
11260 // If the rhs is undefined, compile error because there is a possible
11261 // value (zero) for which the division would be illegal behavior.
11262 // If the lhs is undefined, result is undefined.
11263 if (maybe_lhs_val) |lhs_val| {
11264 if (!lhs_val.isUndef()) {
11265 if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11266 return sema.addConstant(resolved_type, Value.zero);
11267 }
11268 }
11269 }
11270 if (maybe_rhs_val) |rhs_val| {
11271 if (rhs_val.isUndef()) {
11272 return sema.failWithUseOfUndef(block, rhs_src);
11273 }
11274 if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11275 return sema.failWithDivideByZero(block, rhs_src);
11276 }
11277 // TODO: if the RHS is one, return the LHS directly
1114211278 }
11279 if (maybe_lhs_val) |lhs_val| {
11280 if (lhs_val.isUndef()) {
11281 if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
11282 if (maybe_rhs_val) |rhs_val| {
11283 if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
11284 return sema.addConstUndef(resolved_type);
11285 }
11286 }
11287 return sema.failWithUseOfUndef(block, rhs_src);
11288 }
11289 return sema.addConstUndef(resolved_type);
11290 }
11291
11292 if (maybe_rhs_val) |rhs_val| {
11293 if (is_int) {
11294 return sema.addConstant(
11295 resolved_type,
11296 try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, target),
11297 );
11298 } else {
11299 return sema.addConstant(
11300 resolved_type,
11301 try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target),
11302 );
11303 }
11304 } else break :rs rhs_src;
11305 } else break :rs lhs_src;
11306 };
11307
11308 try sema.requireRuntimeBlock(block, src, runtime_src);
11309
11310 if (block.wantSafety()) {
11311 try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
11312 try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int);
1114311313 }
1114411314
11145 const air_tag = if (is_int) Air.Inst.Tag.div_trunc else switch (block.float_mode) {
11146 .Optimized => Air.Inst.Tag.div_float_optimized,
11147 .Strict => Air.Inst.Tag.div_float,
11315 return block.addBinOp(airTag(block, is_int, .div_floor, .div_floor_optimized), casted_lhs, casted_rhs);
11316}
11317
11318fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
11319 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
11320 const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node };
11321 const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
11322 const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node };
11323 const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
11324 const lhs = try sema.resolveInst(extra.lhs);
11325 const rhs = try sema.resolveInst(extra.rhs);
11326 const lhs_ty = sema.typeOf(lhs);
11327 const rhs_ty = sema.typeOf(rhs);
11328 const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison();
11329 const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison();
11330 try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
11331 try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div_trunc);
11332
11333 const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
11334 const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{
11335 .override = &[_]LazySrcLoc{ lhs_src, rhs_src },
11336 });
11337
11338 const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
11339 const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
11340
11341 const lhs_scalar_ty = lhs_ty.scalarType();
11342 const rhs_scalar_ty = rhs_ty.scalarType();
11343 const scalar_tag = resolved_type.scalarType().zigTypeTag();
11344
11345 const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
11346
11347 try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_trunc);
11348
11349 const mod = sema.mod;
11350 const target = mod.getTarget();
11351 const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs);
11352 const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs);
11353
11354 const runtime_src = rs: {
11355 // For integers:
11356 // If the lhs is zero, then zero is returned regardless of rhs.
11357 // If the rhs is zero, compile error for division by zero.
11358 // If the rhs is undefined, compile error because there is a possible
11359 // value (zero) for which the division would be illegal behavior.
11360 // If the lhs is undefined:
11361 // * if lhs type is signed:
11362 // * if rhs is comptime-known and not -1, result is undefined
11363 // * if rhs is -1 or runtime-known, compile error because there is a
11364 // possible value (-min_int / -1) for which division would be
11365 // illegal behavior.
11366 // * if lhs type is unsigned, undef is returned regardless of rhs.
11367 // TODO: emit runtime safety for division by zero
11368 //
11369 // For floats:
11370 // If the rhs is zero, compile error for division by zero.
11371 // If the rhs is undefined, compile error because there is a possible
11372 // value (zero) for which the division would be illegal behavior.
11373 // If the lhs is undefined, result is undefined.
11374 if (maybe_lhs_val) |lhs_val| {
11375 if (!lhs_val.isUndef()) {
11376 if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11377 return sema.addConstant(resolved_type, Value.zero);
11378 }
11379 }
11380 }
11381 if (maybe_rhs_val) |rhs_val| {
11382 if (rhs_val.isUndef()) {
11383 return sema.failWithUseOfUndef(block, rhs_src);
11384 }
11385 if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11386 return sema.failWithDivideByZero(block, rhs_src);
11387 }
11388 }
11389 if (maybe_lhs_val) |lhs_val| {
11390 if (lhs_val.isUndef()) {
11391 if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
11392 if (maybe_rhs_val) |rhs_val| {
11393 if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
11394 return sema.addConstUndef(resolved_type);
11395 }
11396 }
11397 return sema.failWithUseOfUndef(block, rhs_src);
11398 }
11399 return sema.addConstUndef(resolved_type);
11400 }
11401
11402 if (maybe_rhs_val) |rhs_val| {
11403 if (is_int) {
11404 return sema.addConstant(
11405 resolved_type,
11406 try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
11407 );
11408 } else {
11409 return sema.addConstant(
11410 resolved_type,
11411 try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target),
11412 );
11413 }
11414 } else break :rs rhs_src;
11415 } else break :rs lhs_src;
1114811416 };
11149 return block.addBinOp(air_tag, casted_lhs, casted_rhs);
11417
11418 try sema.requireRuntimeBlock(block, src, runtime_src);
11419
11420 if (block.wantSafety()) {
11421 try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
11422 try sema.addDivByZeroSafety(block, resolved_type, maybe_rhs_val, casted_rhs, is_int);
11423 }
11424
11425 return block.addBinOp(airTag(block, is_int, .div_trunc, .div_trunc_optimized), casted_lhs, casted_rhs);
11426}
11427
11428fn addDivIntOverflowSafety(
11429 sema: *Sema,
11430 block: *Block,
11431 resolved_type: Type,
11432 lhs_scalar_ty: Type,
11433 maybe_lhs_val: ?Value,
11434 maybe_rhs_val: ?Value,
11435 casted_lhs: Air.Inst.Ref,
11436 casted_rhs: Air.Inst.Ref,
11437 is_int: bool,
11438) CompileError!void {
11439 if (!is_int) return;
11440
11441 // If the LHS is unsigned, it cannot cause overflow.
11442 if (!lhs_scalar_ty.isSignedInt()) return;
11443
11444 const mod = sema.mod;
11445 const target = mod.getTarget();
11446
11447 // If the LHS is widened to a larger integer type, no overflow is possible.
11448 if (lhs_scalar_ty.intInfo(target).bits < resolved_type.intInfo(target).bits) {
11449 return;
11450 }
11451
11452 const min_int = try resolved_type.minInt(sema.arena, target);
11453 const neg_one = try Value.Tag.int_i64.create(sema.arena, -1);
11454
11455 // If the LHS is comptime-known to be not equal to the min int,
11456 // no overflow is possible.
11457 if (maybe_lhs_val) |lhs_val| {
11458 if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) return;
11459 }
11460
11461 // If the RHS is comptime-known to not be equal to -1, no overflow is possible.
11462 if (maybe_rhs_val) |rhs_val| {
11463 if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) return;
11464 }
11465
11466 var ok: Air.Inst.Ref = .none;
11467 if (resolved_type.zigTypeTag() == .Vector) {
11468 const vector_ty_ref = try sema.addType(resolved_type);
11469 if (maybe_lhs_val == null) {
11470 const min_int_ref = try sema.addConstant(
11471 resolved_type,
11472 try Value.Tag.repeated.create(sema.arena, min_int),
11473 );
11474 ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref);
11475 }
11476 if (maybe_rhs_val == null) {
11477 const neg_one_ref = try sema.addConstant(
11478 resolved_type,
11479 try Value.Tag.repeated.create(sema.arena, neg_one),
11480 );
11481 const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref);
11482 if (ok == .none) {
11483 ok = rhs_ok;
11484 } else {
11485 ok = try block.addBinOp(.bool_or, ok, rhs_ok);
11486 }
11487 }
11488 assert(ok != .none);
11489 ok = try block.addInst(.{
11490 .tag = .reduce,
11491 .data = .{ .reduce = .{
11492 .operand = ok,
11493 .operation = .And,
11494 } },
11495 });
11496 } else {
11497 if (maybe_lhs_val == null) {
11498 const min_int_ref = try sema.addConstant(resolved_type, min_int);
11499 ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref);
11500 }
11501 if (maybe_rhs_val == null) {
11502 const neg_one_ref = try sema.addConstant(resolved_type, neg_one);
11503 const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref);
11504 if (ok == .none) {
11505 ok = rhs_ok;
11506 } else {
11507 ok = try block.addBinOp(.bool_or, ok, rhs_ok);
11508 }
11509 }
11510 assert(ok != .none);
11511 }
11512 try sema.addSafetyCheck(block, ok, .integer_overflow);
11513}
11514
11515fn addDivByZeroSafety(
11516 sema: *Sema,
11517 block: *Block,
11518 resolved_type: Type,
11519 maybe_rhs_val: ?Value,
11520 casted_rhs: Air.Inst.Ref,
11521 is_int: bool,
11522) CompileError!void {
11523 // Strict IEEE floats have well-defined division by zero.
11524 if (!is_int and block.float_mode == .Strict) return;
11525
11526 // If rhs was comptime-known to be zero a compile error would have been
11527 // emitted above.
11528 if (maybe_rhs_val != null) return;
11529
11530 const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
11531 const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
11532 const zero = try sema.addConstant(resolved_type, zero_val);
11533 const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type));
11534 break :ok try block.addInst(.{
11535 .tag = if (is_int) .reduce else .reduce_optimized,
11536 .data = .{ .reduce = .{
11537 .operand = ok,
11538 .operation = .And,
11539 } },
11540 });
11541 } else ok: {
11542 const zero = try sema.addConstant(resolved_type, Value.zero);
11543 break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero);
11544 };
11545 try sema.addSafetyCheck(block, ok, .divide_by_zero);
1115011546}
1115111547
1115211548fn airTag(block: *Block, is_int: bool, normal: Air.Inst.Tag, optimized: Air.Inst.Tag) Air.Inst.Tag {
......@@ -11423,13 +11819,8 @@ fn analyzeArithmetic(
1142311819 const scalar_tag = resolved_type.scalarType().zigTypeTag();
1142411820
1142511821 const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt;
11426 const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat;
1142711822
11428 if (!is_int and !(is_float and floatOpAllowed(zir_tag))) {
11429 return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
11430 @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
11431 });
11432 }
11823 try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, zir_tag);
1143311824
1143411825 const mod = sema.mod;
1143511826 const target = mod.getTarget();
......@@ -11636,187 +12027,6 @@ fn analyzeArithmetic(
1163612027 } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat };
1163712028 } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat };
1163812029 },
11639 .div_trunc => {
11640 // For integers:
11641 // If the lhs is zero, then zero is returned regardless of rhs.
11642 // If the rhs is zero, compile error for division by zero.
11643 // If the rhs is undefined, compile error because there is a possible
11644 // value (zero) for which the division would be illegal behavior.
11645 // If the lhs is undefined:
11646 // * if lhs type is signed:
11647 // * if rhs is comptime-known and not -1, result is undefined
11648 // * if rhs is -1 or runtime-known, compile error because there is a
11649 // possible value (-min_int / -1) for which division would be
11650 // illegal behavior.
11651 // * if lhs type is unsigned, undef is returned regardless of rhs.
11652 // TODO: emit runtime safety for division by zero
11653 //
11654 // For floats:
11655 // If the rhs is zero, compile error for division by zero.
11656 // If the rhs is undefined, compile error because there is a possible
11657 // value (zero) for which the division would be illegal behavior.
11658 // If the lhs is undefined, result is undefined.
11659 if (maybe_lhs_val) |lhs_val| {
11660 if (!lhs_val.isUndef()) {
11661 if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11662 return sema.addConstant(resolved_type, Value.zero);
11663 }
11664 }
11665 }
11666 if (maybe_rhs_val) |rhs_val| {
11667 if (rhs_val.isUndef()) {
11668 return sema.failWithUseOfUndef(block, rhs_src);
11669 }
11670 if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11671 return sema.failWithDivideByZero(block, rhs_src);
11672 }
11673 }
11674 const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_trunc_optimized else .div_trunc;
11675 if (maybe_lhs_val) |lhs_val| {
11676 if (lhs_val.isUndef()) {
11677 if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
11678 if (maybe_rhs_val) |rhs_val| {
11679 if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
11680 return sema.addConstUndef(resolved_type);
11681 }
11682 }
11683 return sema.failWithUseOfUndef(block, rhs_src);
11684 }
11685 return sema.addConstUndef(resolved_type);
11686 }
11687
11688 if (maybe_rhs_val) |rhs_val| {
11689 if (is_int) {
11690 return sema.addConstant(
11691 resolved_type,
11692 try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
11693 );
11694 } else {
11695 return sema.addConstant(
11696 resolved_type,
11697 try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target),
11698 );
11699 }
11700 } else break :rs .{ .src = rhs_src, .air_tag = air_tag };
11701 } else break :rs .{ .src = lhs_src, .air_tag = air_tag };
11702 },
11703 .div_floor => {
11704 // For integers:
11705 // If the lhs is zero, then zero is returned regardless of rhs.
11706 // If the rhs is zero, compile error for division by zero.
11707 // If the rhs is undefined, compile error because there is a possible
11708 // value (zero) for which the division would be illegal behavior.
11709 // If the lhs is undefined:
11710 // * if lhs type is signed:
11711 // * if rhs is comptime-known and not -1, result is undefined
11712 // * if rhs is -1 or runtime-known, compile error because there is a
11713 // possible value (-min_int / -1) for which division would be
11714 // illegal behavior.
11715 // * if lhs type is unsigned, undef is returned regardless of rhs.
11716 // TODO: emit runtime safety for division by zero
11717 //
11718 // For floats:
11719 // If the rhs is zero, compile error for division by zero.
11720 // If the rhs is undefined, compile error because there is a possible
11721 // value (zero) for which the division would be illegal behavior.
11722 // If the lhs is undefined, result is undefined.
11723 if (maybe_lhs_val) |lhs_val| {
11724 if (!lhs_val.isUndef()) {
11725 if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11726 return sema.addConstant(resolved_type, Value.zero);
11727 }
11728 }
11729 }
11730 if (maybe_rhs_val) |rhs_val| {
11731 if (rhs_val.isUndef()) {
11732 return sema.failWithUseOfUndef(block, rhs_src);
11733 }
11734 if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11735 return sema.failWithDivideByZero(block, rhs_src);
11736 }
11737 }
11738 const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_floor_optimized else .div_floor;
11739 if (maybe_lhs_val) |lhs_val| {
11740 if (lhs_val.isUndef()) {
11741 if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) {
11742 if (maybe_rhs_val) |rhs_val| {
11743 if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) {
11744 return sema.addConstUndef(resolved_type);
11745 }
11746 }
11747 return sema.failWithUseOfUndef(block, rhs_src);
11748 }
11749 return sema.addConstUndef(resolved_type);
11750 }
11751
11752 if (maybe_rhs_val) |rhs_val| {
11753 if (is_int) {
11754 return sema.addConstant(
11755 resolved_type,
11756 try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, target),
11757 );
11758 } else {
11759 return sema.addConstant(
11760 resolved_type,
11761 try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target),
11762 );
11763 }
11764 } else break :rs .{ .src = rhs_src, .air_tag = air_tag };
11765 } else break :rs .{ .src = lhs_src, .air_tag = air_tag };
11766 },
11767 .div_exact => {
11768 // For integers:
11769 // If the lhs is zero, then zero is returned regardless of rhs.
11770 // If the rhs is zero, compile error for division by zero.
11771 // If the rhs is undefined, compile error because there is a possible
11772 // value (zero) for which the division would be illegal behavior.
11773 // If the lhs is undefined, compile error because there is a possible
11774 // value for which the division would result in a remainder.
11775 // TODO: emit runtime safety for if there is a remainder
11776 // TODO: emit runtime safety for division by zero
11777 //
11778 // For floats:
11779 // If the rhs is zero, compile error for division by zero.
11780 // If the rhs is undefined, compile error because there is a possible
11781 // value (zero) for which the division would be illegal behavior.
11782 // If the lhs is undefined, compile error because there is a possible
11783 // value for which the division would result in a remainder.
11784 if (maybe_lhs_val) |lhs_val| {
11785 if (lhs_val.isUndef()) {
11786 return sema.failWithUseOfUndef(block, rhs_src);
11787 } else {
11788 if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11789 return sema.addConstant(resolved_type, Value.zero);
11790 }
11791 }
11792 }
11793 if (maybe_rhs_val) |rhs_val| {
11794 if (rhs_val.isUndef()) {
11795 return sema.failWithUseOfUndef(block, rhs_src);
11796 }
11797 if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) {
11798 return sema.failWithDivideByZero(block, rhs_src);
11799 }
11800 }
11801 const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_exact_optimized else .div_exact;
11802 if (maybe_lhs_val) |lhs_val| {
11803 if (maybe_rhs_val) |rhs_val| {
11804 if (is_int) {
11805 // TODO: emit compile error if there is a remainder
11806 return sema.addConstant(
11807 resolved_type,
11808 try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target),
11809 );
11810 } else {
11811 // TODO: emit compile error if there is a remainder
11812 return sema.addConstant(
11813 resolved_type,
11814 try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target),
11815 );
11816 }
11817 } else break :rs .{ .src = rhs_src, .air_tag = air_tag };
11818 } else break :rs .{ .src = lhs_src, .air_tag = air_tag };
11819 },
1182012030 .mul => {
1182112031 // For integers:
1182212032 // If either of the operands are zero, the result is zero.
......@@ -12195,28 +12405,6 @@ fn analyzeArithmetic(
1219512405 }
1219612406 }
1219712407 switch (rs.air_tag) {
12198 // zig fmt: off
12199 .div_float, .div_exact, .div_trunc, .div_floor, .div_float_optimized,
12200 .div_exact_optimized, .div_trunc_optimized, .div_floor_optimized
12201 // zig fmt: on
12202 => if (scalar_tag == .Int or block.float_mode == .Optimized) {
12203 const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
12204 const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
12205 const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val);
12206 const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type));
12207 break :ok try block.addInst(.{
12208 .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
12209 .data = .{ .reduce = .{
12210 .operand = ok,
12211 .operation = .And,
12212 } },
12213 });
12214 } else ok: {
12215 const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero);
12216 break :ok try block.addBinOp(if (block.float_mode == .Optimized) .cmp_neq_optimized else .cmp_neq, casted_rhs, zero);
12217 };
12218 try sema.addSafetyCheck(block, ok, .divide_by_zero);
12219 },
1222012408 .rem, .mod, .rem_optimized, .mod_optimized => {
1222112409 const ok = if (resolved_type.zigTypeTag() == .Vector) ok: {
1222212410 const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
......@@ -12243,47 +12431,6 @@ fn analyzeArithmetic(
1224312431 },
1224412432 else => {},
1224512433 }
12246 if (rs.air_tag == .div_exact or rs.air_tag == .div_exact_optimized) {
12247 const result = try block.addBinOp(.div_exact, casted_lhs, casted_rhs);
12248 const ok = if (scalar_tag == .Float) ok: {
12249 const floored = try block.addUnOp(.floor, result);
12250
12251 if (resolved_type.zigTypeTag() == .Vector) {
12252 const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type));
12253 break :ok try block.addInst(.{
12254 .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce,
12255 .data = .{ .reduce = .{
12256 .operand = eql,
12257 .operation = .And,
12258 } },
12259 });
12260 } else {
12261 const is_in_range = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, result, floored);
12262 break :ok is_in_range;
12263 }
12264 } else ok: {
12265 const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs);
12266
12267 if (resolved_type.zigTypeTag() == .Vector) {
12268 const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero);
12269 const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val);
12270 const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type));
12271 break :ok try block.addInst(.{
12272 .tag = .reduce,
12273 .data = .{ .reduce = .{
12274 .operand = eql,
12275 .operation = .And,
12276 } },
12277 });
12278 } else {
12279 const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero);
12280 const is_in_range = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, remainder, zero);
12281 break :ok is_in_range;
12282 }
12283 };
12284 try sema.addSafetyCheck(block, ok, .exact_division_remainder);
12285 return result;
12286 }
1228712434 }
1228812435 return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs);
1228912436}