| ... | ... | @@ -875,10 +875,6 @@ fn analyzeBodyInner( |
| 875 | 875 | .add => try sema.zirArithmetic(block, inst, .add), |
| 876 | 876 | .addwrap => try sema.zirArithmetic(block, inst, .addwrap), |
| 877 | 877 | .add_sat => try sema.zirArithmetic(block, inst, .add_sat), |
| 878 | | .div => try sema.zirArithmetic(block, inst, .div), |
| 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), |
| 882 | 878 | .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem), |
| 883 | 879 | .mod => try sema.zirArithmetic(block, inst, .mod), |
| 884 | 880 | .rem => try sema.zirArithmetic(block, inst, .rem), |
| ... | ... | @@ -889,6 +885,11 @@ fn analyzeBodyInner( |
| 889 | 885 | .subwrap => try sema.zirArithmetic(block, inst, .subwrap), |
| 890 | 886 | .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat), |
| 891 | 887 | |
| 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 | |
| 892 | 893 | .maximum => try sema.zirMinMax(block, inst, .max), |
| 893 | 894 | .minimum => try sema.zirMinMax(block, inst, .min), |
| 894 | 895 | |
| ... | ... | @@ -10920,226 +10921,856 @@ fn zirArithmetic( |
| 10920 | 10921 | return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); |
| 10921 | 10922 | } |
| 10922 | 10923 | |
| 10923 | | fn zirOverflowArithmetic( |
| 10924 | | sema: *Sema, |
| 10925 | | block: *Block, |
| 10926 | | extended: Zir.Inst.Extended.InstData, |
| 10927 | | zir_tag: Zir.Inst.Extended, |
| 10928 | | ) CompileError!Air.Inst.Ref { |
| 10929 | | const tracy = trace(@src()); |
| 10930 | | defer tracy.end(); |
| 10931 | | |
| 10932 | | const extra = sema.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data; |
| 10933 | | const src = LazySrcLoc.nodeOffset(extra.node); |
| 10934 | | |
| 10935 | | const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; |
| 10936 | | const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; |
| 10937 | | const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = extra.node }; |
| 10938 | | |
| 10924 | fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 10925 | const inst_data = sema.code.instructions.items(.data)[inst].pl_node; |
| 10926 | const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; |
| 10927 | const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; |
| 10928 | const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; |
| 10929 | const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; |
| 10939 | 10930 | const lhs = try sema.resolveInst(extra.lhs); |
| 10940 | 10931 | const rhs = try sema.resolveInst(extra.rhs); |
| 10941 | | const ptr = try sema.resolveInst(extra.ptr); |
| 10942 | | |
| 10943 | 10932 | const lhs_ty = sema.typeOf(lhs); |
| 10944 | 10933 | const rhs_ty = sema.typeOf(rhs); |
| 10945 | | const mod = sema.mod; |
| 10946 | | const target = mod.getTarget(); |
| 10947 | | |
| 10948 | | // Note, the types of lhs/rhs (also for shifting)/ptr are already correct as ensured by astgen. |
| 10934 | const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); |
| 10935 | const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); |
| 10949 | 10936 | try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); |
| 10950 | | const dest_ty = lhs_ty; |
| 10951 | | if (dest_ty.scalarType().zigTypeTag() != .Int) { |
| 10952 | | return sema.fail(block, src, "expected vector of integers or integer tag type, found '{}'", .{dest_ty.fmt(mod)}); |
| 10953 | | } |
| 10937 | try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div); |
| 10954 | 10938 | |
| 10955 | | const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); |
| 10956 | | const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); |
| 10939 | const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; |
| 10940 | const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ |
| 10941 | .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, |
| 10942 | }); |
| 10957 | 10943 | |
| 10958 | | const tuple_ty = try sema.overflowArithmeticTupleType(dest_ty); |
| 10959 | | const ov_ty = tuple_ty.tupleFields().types[1]; |
| 10960 | | // TODO: Remove and use `ov_ty` instead. |
| 10961 | | // This is a temporary type used until overflow arithmetic properly returns `u1` instead of `bool`. |
| 10962 | | const overflowed_ty = if (dest_ty.zigTypeTag() == .Vector) try Type.vector(sema.arena, dest_ty.vectorLen(), Type.@"bool") else Type.@"bool"; |
| 10944 | const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); |
| 10945 | const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); |
| 10963 | 10946 | |
| 10964 | | const result: struct { |
| 10965 | | /// TODO: Rename to `overflow_bit` and make of type `u1`. |
| 10966 | | overflowed: Air.Inst.Ref, |
| 10967 | | wrapped: Air.Inst.Ref, |
| 10968 | | } = result: { |
| 10969 | | switch (zir_tag) { |
| 10970 | | .add_with_overflow => { |
| 10971 | | // If either of the arguments is zero, `false` is returned and the other is stored |
| 10972 | | // to the result, even if it is undefined.. |
| 10973 | | // Otherwise, if either of the argument is undefined, undefined is returned. |
| 10974 | | if (maybe_lhs_val) |lhs_val| { |
| 10975 | | if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { |
| 10976 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; |
| 10977 | | } |
| 10978 | | } |
| 10979 | | if (maybe_rhs_val) |rhs_val| { |
| 10980 | | if (!rhs_val.isUndef() and (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { |
| 10981 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 10982 | | } |
| 10983 | | } |
| 10984 | | if (maybe_lhs_val) |lhs_val| { |
| 10985 | | if (maybe_rhs_val) |rhs_val| { |
| 10986 | | if (lhs_val.isUndef() or rhs_val.isUndef()) { |
| 10987 | | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 10988 | | } |
| 10947 | const lhs_scalar_ty = lhs_ty.scalarType(); |
| 10948 | const rhs_scalar_ty = rhs_ty.scalarType(); |
| 10949 | const scalar_tag = resolved_type.scalarType().zigTypeTag(); |
| 10989 | 10950 | |
| 10990 | | const result = try sema.intAddWithOverflow(block, src, lhs_val, rhs_val, dest_ty); |
| 10991 | | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); |
| 10992 | | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); |
| 10993 | | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; |
| 10994 | | } |
| 10995 | | } |
| 10996 | | }, |
| 10997 | | .sub_with_overflow => { |
| 10998 | | // If the rhs is zero, then the result is lhs and no overflow occured. |
| 10999 | | // Otherwise, if either result is undefined, both results are undefined. |
| 11000 | | if (maybe_rhs_val) |rhs_val| { |
| 11001 | | if (rhs_val.isUndef()) { |
| 11002 | | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11003 | | } else if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11004 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11005 | | } else if (maybe_lhs_val) |lhs_val| { |
| 11006 | | if (lhs_val.isUndef()) { |
| 11007 | | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11008 | | } |
| 10951 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; |
| 11009 | 10952 | |
| 11010 | | const result = try sema.intSubWithOverflow(block, src, lhs_val, rhs_val, dest_ty); |
| 11011 | | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); |
| 11012 | | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); |
| 11013 | | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; |
| 10953 | try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div); |
| 10954 | |
| 10955 | const mod = sema.mod; |
| 10956 | const target = mod.getTarget(); |
| 10957 | const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); |
| 10958 | const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); |
| 10959 | |
| 10960 | // TODO: emit compile error when .div is used on integers and there would be an |
| 10961 | // ambiguous result between div_floor and div_trunc. |
| 10962 | |
| 10963 | // For integers: |
| 10964 | // If the lhs is zero, then zero is returned regardless of rhs. |
| 10965 | // If the rhs is zero, compile error for division by zero. |
| 10966 | // If the rhs is undefined, compile error because there is a possible |
| 10967 | // value (zero) for which the division would be illegal behavior. |
| 10968 | // If the lhs is undefined: |
| 10969 | // * if lhs type is signed: |
| 10970 | // * if rhs is comptime-known and not -1, result is undefined |
| 10971 | // * if rhs is -1 or runtime-known, compile error because there is a |
| 10972 | // possible value (-min_int / -1) for which division would be |
| 10973 | // illegal behavior. |
| 10974 | // * if lhs type is unsigned, undef is returned regardless of rhs. |
| 10975 | // |
| 10976 | // For floats: |
| 10977 | // If the rhs is zero: |
| 10978 | // * comptime_float: compile error for division by zero. |
| 10979 | // * other float type: |
| 10980 | // * if the lhs is zero: QNaN |
| 10981 | // * otherwise: +Inf or -Inf depending on lhs sign |
| 10982 | // If the rhs is undefined: |
| 10983 | // * comptime_float: compile error because there is a possible |
| 10984 | // value (zero) for which the division would be illegal behavior. |
| 10985 | // * other float type: result is undefined |
| 10986 | // If the lhs is undefined, result is undefined. |
| 10987 | switch (scalar_tag) { |
| 10988 | .Int, .ComptimeInt, .ComptimeFloat => { |
| 10989 | if (maybe_lhs_val) |lhs_val| { |
| 10990 | if (!lhs_val.isUndef()) { |
| 10991 | if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 10992 | return sema.addConstant(resolved_type, Value.zero); |
| 11014 | 10993 | } |
| 11015 | 10994 | } |
| 11016 | | }, |
| 11017 | | .mul_with_overflow => { |
| 11018 | | // If either of the arguments is zero, the result is zero and no overflow occured. |
| 11019 | | // If either of the arguments is one, the result is the other and no overflow occured. |
| 11020 | | // Otherwise, if either of the arguments is undefined, both results are undefined. |
| 11021 | | if (maybe_lhs_val) |lhs_val| { |
| 11022 | | if (!lhs_val.isUndef()) { |
| 11023 | | if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11024 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11025 | | } else if (try sema.compare(block, src, lhs_val, .eq, Value.one, dest_ty)) { |
| 11026 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; |
| 11027 | | } |
| 11028 | | } |
| 10995 | } |
| 10996 | if (maybe_rhs_val) |rhs_val| { |
| 10997 | if (rhs_val.isUndef()) { |
| 10998 | return sema.failWithUseOfUndef(block, rhs_src); |
| 11029 | 10999 | } |
| 11030 | | |
| 11031 | | if (maybe_rhs_val) |rhs_val| { |
| 11032 | | if (!rhs_val.isUndef()) { |
| 11033 | | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11034 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; |
| 11035 | | } else if (try sema.compare(block, src, rhs_val, .eq, Value.one, dest_ty)) { |
| 11036 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11037 | | } |
| 11038 | | } |
| 11000 | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11001 | return sema.failWithDivideByZero(block, rhs_src); |
| 11039 | 11002 | } |
| 11003 | // TODO: if the RHS is one, return the LHS directly |
| 11004 | } |
| 11005 | }, |
| 11006 | else => {}, |
| 11007 | } |
| 11040 | 11008 | |
| 11041 | | if (maybe_lhs_val) |lhs_val| { |
| 11009 | const runtime_src = rs: { |
| 11010 | if (maybe_lhs_val) |lhs_val| { |
| 11011 | if (lhs_val.isUndef()) { |
| 11012 | if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { |
| 11042 | 11013 | if (maybe_rhs_val) |rhs_val| { |
| 11043 | | if (lhs_val.isUndef() or rhs_val.isUndef()) { |
| 11044 | | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11014 | if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { |
| 11015 | return sema.addConstUndef(resolved_type); |
| 11045 | 11016 | } |
| 11046 | | |
| 11047 | | const result = try lhs_val.intMulWithOverflow(rhs_val, dest_ty, sema.arena, target); |
| 11048 | | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); |
| 11049 | | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); |
| 11050 | | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; |
| 11051 | | } |
| 11052 | | } |
| 11053 | | }, |
| 11054 | | .shl_with_overflow => { |
| 11055 | | // If lhs is zero, the result is zero and no overflow occurred. |
| 11056 | | // If rhs is zero, the result is lhs (even if undefined) and no overflow occurred. |
| 11057 | | // Oterhwise if either of the arguments is undefined, both results are undefined. |
| 11058 | | if (maybe_lhs_val) |lhs_val| { |
| 11059 | | if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { |
| 11060 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11061 | | } |
| 11062 | | } |
| 11063 | | if (maybe_rhs_val) |rhs_val| { |
| 11064 | | if (!rhs_val.isUndef() and (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { |
| 11065 | | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11066 | 11017 | } |
| 11018 | return sema.failWithUseOfUndef(block, rhs_src); |
| 11067 | 11019 | } |
| 11068 | | if (maybe_lhs_val) |lhs_val| { |
| 11069 | | if (maybe_rhs_val) |rhs_val| { |
| 11070 | | if (lhs_val.isUndef() or rhs_val.isUndef()) { |
| 11071 | | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11072 | | } |
| 11020 | return sema.addConstUndef(resolved_type); |
| 11021 | } |
| 11073 | 11022 | |
| 11074 | | const result = try lhs_val.shlWithOverflow(rhs_val, dest_ty, sema.arena, target); |
| 11075 | | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); |
| 11076 | | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); |
| 11077 | | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; |
| 11078 | | } |
| 11023 | if (maybe_rhs_val) |rhs_val| { |
| 11024 | if (is_int) { |
| 11025 | return sema.addConstant( |
| 11026 | resolved_type, |
| 11027 | try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), |
| 11028 | ); |
| 11029 | } else { |
| 11030 | return sema.addConstant( |
| 11031 | resolved_type, |
| 11032 | try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), |
| 11033 | ); |
| 11079 | 11034 | } |
| 11080 | | }, |
| 11081 | | else => unreachable, |
| 11035 | } else { |
| 11036 | break :rs rhs_src; |
| 11037 | } |
| 11038 | } else { |
| 11039 | break :rs lhs_src; |
| 11082 | 11040 | } |
| 11041 | }; |
| 11083 | 11042 | |
| 11084 | | const air_tag: Air.Inst.Tag = switch (zir_tag) { |
| 11085 | | .add_with_overflow => .add_with_overflow, |
| 11086 | | .mul_with_overflow => .mul_with_overflow, |
| 11087 | | .sub_with_overflow => .sub_with_overflow, |
| 11088 | | .shl_with_overflow => .shl_with_overflow, |
| 11089 | | else => unreachable, |
| 11090 | | }; |
| 11091 | | |
| 11092 | | const runtime_src = if (maybe_lhs_val == null) lhs_src else rhs_src; |
| 11093 | | try sema.requireRuntimeBlock(block, src, runtime_src); |
| 11094 | | |
| 11095 | | const tuple = try block.addInst(.{ |
| 11096 | | .tag = air_tag, |
| 11097 | | .data = .{ .ty_pl = .{ |
| 11098 | | .ty = try block.sema.addType(tuple_ty), |
| 11099 | | .payload = try block.sema.addExtra(Air.Bin{ |
| 11100 | | .lhs = lhs, |
| 11101 | | .rhs = rhs, |
| 11102 | | }), |
| 11103 | | } }, |
| 11104 | | }); |
| 11105 | | |
| 11106 | | const wrapped = try sema.tupleFieldValByIndex(block, src, tuple, 0, tuple_ty); |
| 11107 | | try sema.storePtr2(block, src, ptr, ptr_src, wrapped, src, .store); |
| 11043 | try sema.requireRuntimeBlock(block, src, runtime_src); |
| 11108 | 11044 | |
| 11109 | | const overflow_bit = try sema.tupleFieldValByIndex(block, src, tuple, 1, tuple_ty); |
| 11110 | | const zero_ov_val = if (dest_ty.zigTypeTag() == .Vector) try Value.Tag.repeated.create(sema.arena, Value.zero) else Value.zero; |
| 11111 | | const zero_ov = try sema.addConstant(ov_ty, zero_ov_val); |
| 11045 | if (block.wantSafety()) { |
| 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 | } |
| 11112 | 11049 | |
| 11113 | | const overflowed_inst = if (dest_ty.zigTypeTag() == .Vector) |
| 11114 | | block.addCmpVector(overflow_bit, .zero, .neq, try sema.addType(ov_ty)) |
| 11115 | | else |
| 11116 | | block.addBinOp(.cmp_neq, overflow_bit, zero_ov); |
| 11117 | | return overflowed_inst; |
| 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, |
| 11118 | 11053 | }; |
| 11119 | | |
| 11120 | | try sema.storePtr2(block, src, ptr, ptr_src, result.wrapped, src, .store); |
| 11121 | | return result.overflowed; |
| 11054 | return block.addBinOp(air_tag, casted_lhs, casted_rhs); |
| 11122 | 11055 | } |
| 11123 | 11056 | |
| 11124 | | fn overflowArithmeticTupleType(sema: *Sema, ty: Type) !Type { |
| 11125 | | const ov_ty = if (ty.zigTypeTag() == .Vector) try Type.vector(sema.arena, ty.vectorLen(), Type.@"u1") else Type.@"u1"; |
| 11057 | fn 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); |
| 11126 | 11071 | |
| 11127 | | const types = try sema.arena.alloc(Type, 2); |
| 11128 | | const values = try sema.arena.alloc(Value, 2); |
| 11129 | | const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ |
| 11130 | | .types = types, |
| 11131 | | .values = values, |
| 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 }, |
| 11132 | 11075 | }); |
| 11133 | 11076 | |
| 11134 | | types[0] = ty; |
| 11135 | | types[1] = ov_ty; |
| 11136 | | values[0] = Value.initTag(.unreachable_value); |
| 11137 | | values[1] = Value.initTag(.unreachable_value); |
| 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); |
| 11138 | 11079 | |
| 11139 | | return tuple_ty; |
| 11140 | | } |
| 11080 | const lhs_scalar_ty = lhs_ty.scalarType(); |
| 11081 | const scalar_tag = resolved_type.scalarType().zigTypeTag(); |
| 11141 | 11082 | |
| 11142 | | fn analyzeArithmetic( |
| 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( |
| 11132 | resolved_type, |
| 11133 | try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), |
| 11134 | ); |
| 11135 | } else { |
| 11136 | // TODO: emit compile error if there is a remainder |
| 11137 | return sema.addConstant( |
| 11138 | resolved_type, |
| 11139 | try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), |
| 11140 | ); |
| 11141 | } |
| 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 | }, |
| 11168 | .data = .{ .reduce = .{ |
| 11169 | .operand = eql, |
| 11170 | .operation = .And, |
| 11171 | } }, |
| 11172 | }); |
| 11173 | } else { |
| 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; |
| 11179 | } |
| 11180 | } else ok: { |
| 11181 | const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs); |
| 11182 | |
| 11183 | if (resolved_type.zigTypeTag() == .Vector) { |
| 11184 | const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); |
| 11185 | const zero = try sema.addConstant(resolved_type, zero_val); |
| 11186 | const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type)); |
| 11187 | break :ok try block.addInst(.{ |
| 11188 | .tag = .reduce, |
| 11189 | .data = .{ .reduce = .{ |
| 11190 | .operand = eql, |
| 11191 | .operation = .And, |
| 11192 | } }, |
| 11193 | }); |
| 11194 | } else { |
| 11195 | const zero = try sema.addConstant(resolved_type, Value.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 | |
| 11207 | fn 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 |
| 11278 | } |
| 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); |
| 11313 | } |
| 11314 | |
| 11315 | return block.addBinOp(airTag(block, is_int, .div_floor, .div_floor_optimized), casted_lhs, casted_rhs); |
| 11316 | } |
| 11317 | |
| 11318 | fn 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; |
| 11416 | }; |
| 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 | |
| 11428 | fn 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_scalar = try Value.Tag.int_i64.create(sema.arena, -1); |
| 11454 | const neg_one = if (resolved_type.zigTypeTag() == .Vector) |
| 11455 | try Value.Tag.repeated.create(sema.arena, neg_one_scalar) |
| 11456 | else |
| 11457 | neg_one_scalar; |
| 11458 | |
| 11459 | // If the LHS is comptime-known to be not equal to the min int, |
| 11460 | // no overflow is possible. |
| 11461 | if (maybe_lhs_val) |lhs_val| { |
| 11462 | if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) return; |
| 11463 | } |
| 11464 | |
| 11465 | // If the RHS is comptime-known to not be equal to -1, no overflow is possible. |
| 11466 | if (maybe_rhs_val) |rhs_val| { |
| 11467 | if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) return; |
| 11468 | } |
| 11469 | |
| 11470 | var ok: Air.Inst.Ref = .none; |
| 11471 | if (resolved_type.zigTypeTag() == .Vector) { |
| 11472 | const vector_ty_ref = try sema.addType(resolved_type); |
| 11473 | if (maybe_lhs_val == null) { |
| 11474 | const min_int_ref = try sema.addConstant(resolved_type, min_int); |
| 11475 | ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref); |
| 11476 | } |
| 11477 | if (maybe_rhs_val == null) { |
| 11478 | const neg_one_ref = try sema.addConstant(resolved_type, neg_one); |
| 11479 | const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref); |
| 11480 | if (ok == .none) { |
| 11481 | ok = rhs_ok; |
| 11482 | } else { |
| 11483 | ok = try block.addBinOp(.bool_or, ok, rhs_ok); |
| 11484 | } |
| 11485 | } |
| 11486 | assert(ok != .none); |
| 11487 | ok = try block.addInst(.{ |
| 11488 | .tag = .reduce, |
| 11489 | .data = .{ .reduce = .{ |
| 11490 | .operand = ok, |
| 11491 | .operation = .And, |
| 11492 | } }, |
| 11493 | }); |
| 11494 | } else { |
| 11495 | if (maybe_lhs_val == null) { |
| 11496 | const min_int_ref = try sema.addConstant(resolved_type, min_int); |
| 11497 | ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref); |
| 11498 | } |
| 11499 | if (maybe_rhs_val == null) { |
| 11500 | const neg_one_ref = try sema.addConstant(resolved_type, neg_one); |
| 11501 | const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref); |
| 11502 | if (ok == .none) { |
| 11503 | ok = rhs_ok; |
| 11504 | } else { |
| 11505 | ok = try block.addBinOp(.bool_or, ok, rhs_ok); |
| 11506 | } |
| 11507 | } |
| 11508 | assert(ok != .none); |
| 11509 | } |
| 11510 | try sema.addSafetyCheck(block, ok, .integer_overflow); |
| 11511 | } |
| 11512 | |
| 11513 | fn addDivByZeroSafety( |
| 11514 | sema: *Sema, |
| 11515 | block: *Block, |
| 11516 | resolved_type: Type, |
| 11517 | maybe_rhs_val: ?Value, |
| 11518 | casted_rhs: Air.Inst.Ref, |
| 11519 | is_int: bool, |
| 11520 | ) CompileError!void { |
| 11521 | // Strict IEEE floats have well-defined division by zero. |
| 11522 | if (!is_int and block.float_mode == .Strict) return; |
| 11523 | |
| 11524 | // If rhs was comptime-known to be zero a compile error would have been |
| 11525 | // emitted above. |
| 11526 | if (maybe_rhs_val != null) return; |
| 11527 | |
| 11528 | const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { |
| 11529 | const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); |
| 11530 | const zero = try sema.addConstant(resolved_type, zero_val); |
| 11531 | const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type)); |
| 11532 | break :ok try block.addInst(.{ |
| 11533 | .tag = if (is_int) .reduce else .reduce_optimized, |
| 11534 | .data = .{ .reduce = .{ |
| 11535 | .operand = ok, |
| 11536 | .operation = .And, |
| 11537 | } }, |
| 11538 | }); |
| 11539 | } else ok: { |
| 11540 | const zero = try sema.addConstant(resolved_type, Value.zero); |
| 11541 | break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero); |
| 11542 | }; |
| 11543 | try sema.addSafetyCheck(block, ok, .divide_by_zero); |
| 11544 | } |
| 11545 | |
| 11546 | fn airTag(block: *Block, is_int: bool, normal: Air.Inst.Tag, optimized: Air.Inst.Tag) Air.Inst.Tag { |
| 11547 | if (is_int) return normal; |
| 11548 | return switch (block.float_mode) { |
| 11549 | .Strict => normal, |
| 11550 | .Optimized => optimized, |
| 11551 | }; |
| 11552 | } |
| 11553 | |
| 11554 | fn zirOverflowArithmetic( |
| 11555 | sema: *Sema, |
| 11556 | block: *Block, |
| 11557 | extended: Zir.Inst.Extended.InstData, |
| 11558 | zir_tag: Zir.Inst.Extended, |
| 11559 | ) CompileError!Air.Inst.Ref { |
| 11560 | const tracy = trace(@src()); |
| 11561 | defer tracy.end(); |
| 11562 | |
| 11563 | const extra = sema.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data; |
| 11564 | const src = LazySrcLoc.nodeOffset(extra.node); |
| 11565 | |
| 11566 | const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; |
| 11567 | const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; |
| 11568 | const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = extra.node }; |
| 11569 | |
| 11570 | const lhs = try sema.resolveInst(extra.lhs); |
| 11571 | const rhs = try sema.resolveInst(extra.rhs); |
| 11572 | const ptr = try sema.resolveInst(extra.ptr); |
| 11573 | |
| 11574 | const lhs_ty = sema.typeOf(lhs); |
| 11575 | const rhs_ty = sema.typeOf(rhs); |
| 11576 | const mod = sema.mod; |
| 11577 | const target = mod.getTarget(); |
| 11578 | |
| 11579 | // Note, the types of lhs/rhs (also for shifting)/ptr are already correct as ensured by astgen. |
| 11580 | try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); |
| 11581 | const dest_ty = lhs_ty; |
| 11582 | if (dest_ty.scalarType().zigTypeTag() != .Int) { |
| 11583 | return sema.fail(block, src, "expected vector of integers or integer tag type, found '{}'", .{dest_ty.fmt(mod)}); |
| 11584 | } |
| 11585 | |
| 11586 | const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); |
| 11587 | const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); |
| 11588 | |
| 11589 | const tuple_ty = try sema.overflowArithmeticTupleType(dest_ty); |
| 11590 | const ov_ty = tuple_ty.tupleFields().types[1]; |
| 11591 | // TODO: Remove and use `ov_ty` instead. |
| 11592 | // This is a temporary type used until overflow arithmetic properly returns `u1` instead of `bool`. |
| 11593 | const overflowed_ty = if (dest_ty.zigTypeTag() == .Vector) try Type.vector(sema.arena, dest_ty.vectorLen(), Type.@"bool") else Type.@"bool"; |
| 11594 | |
| 11595 | const result: struct { |
| 11596 | /// TODO: Rename to `overflow_bit` and make of type `u1`. |
| 11597 | overflowed: Air.Inst.Ref, |
| 11598 | wrapped: Air.Inst.Ref, |
| 11599 | } = result: { |
| 11600 | switch (zir_tag) { |
| 11601 | .add_with_overflow => { |
| 11602 | // If either of the arguments is zero, `false` is returned and the other is stored |
| 11603 | // to the result, even if it is undefined.. |
| 11604 | // Otherwise, if either of the argument is undefined, undefined is returned. |
| 11605 | if (maybe_lhs_val) |lhs_val| { |
| 11606 | if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { |
| 11607 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; |
| 11608 | } |
| 11609 | } |
| 11610 | if (maybe_rhs_val) |rhs_val| { |
| 11611 | if (!rhs_val.isUndef() and (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { |
| 11612 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11613 | } |
| 11614 | } |
| 11615 | if (maybe_lhs_val) |lhs_val| { |
| 11616 | if (maybe_rhs_val) |rhs_val| { |
| 11617 | if (lhs_val.isUndef() or rhs_val.isUndef()) { |
| 11618 | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11619 | } |
| 11620 | |
| 11621 | const result = try sema.intAddWithOverflow(block, src, lhs_val, rhs_val, dest_ty); |
| 11622 | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); |
| 11623 | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); |
| 11624 | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; |
| 11625 | } |
| 11626 | } |
| 11627 | }, |
| 11628 | .sub_with_overflow => { |
| 11629 | // If the rhs is zero, then the result is lhs and no overflow occured. |
| 11630 | // Otherwise, if either result is undefined, both results are undefined. |
| 11631 | if (maybe_rhs_val) |rhs_val| { |
| 11632 | if (rhs_val.isUndef()) { |
| 11633 | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11634 | } else if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11635 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11636 | } else if (maybe_lhs_val) |lhs_val| { |
| 11637 | if (lhs_val.isUndef()) { |
| 11638 | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11639 | } |
| 11640 | |
| 11641 | const result = try sema.intSubWithOverflow(block, src, lhs_val, rhs_val, dest_ty); |
| 11642 | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); |
| 11643 | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); |
| 11644 | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; |
| 11645 | } |
| 11646 | } |
| 11647 | }, |
| 11648 | .mul_with_overflow => { |
| 11649 | // If either of the arguments is zero, the result is zero and no overflow occured. |
| 11650 | // If either of the arguments is one, the result is the other and no overflow occured. |
| 11651 | // Otherwise, if either of the arguments is undefined, both results are undefined. |
| 11652 | if (maybe_lhs_val) |lhs_val| { |
| 11653 | if (!lhs_val.isUndef()) { |
| 11654 | if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11655 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11656 | } else if (try sema.compare(block, src, lhs_val, .eq, Value.one, dest_ty)) { |
| 11657 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; |
| 11658 | } |
| 11659 | } |
| 11660 | } |
| 11661 | |
| 11662 | if (maybe_rhs_val) |rhs_val| { |
| 11663 | if (!rhs_val.isUndef()) { |
| 11664 | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11665 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; |
| 11666 | } else if (try sema.compare(block, src, rhs_val, .eq, Value.one, dest_ty)) { |
| 11667 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11668 | } |
| 11669 | } |
| 11670 | } |
| 11671 | |
| 11672 | if (maybe_lhs_val) |lhs_val| { |
| 11673 | if (maybe_rhs_val) |rhs_val| { |
| 11674 | if (lhs_val.isUndef() or rhs_val.isUndef()) { |
| 11675 | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11676 | } |
| 11677 | |
| 11678 | const result = try lhs_val.intMulWithOverflow(rhs_val, dest_ty, sema.arena, target); |
| 11679 | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); |
| 11680 | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); |
| 11681 | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; |
| 11682 | } |
| 11683 | } |
| 11684 | }, |
| 11685 | .shl_with_overflow => { |
| 11686 | // If lhs is zero, the result is zero and no overflow occurred. |
| 11687 | // If rhs is zero, the result is lhs (even if undefined) and no overflow occurred. |
| 11688 | // Oterhwise if either of the arguments is undefined, both results are undefined. |
| 11689 | if (maybe_lhs_val) |lhs_val| { |
| 11690 | if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { |
| 11691 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11692 | } |
| 11693 | } |
| 11694 | if (maybe_rhs_val) |rhs_val| { |
| 11695 | if (!rhs_val.isUndef() and (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { |
| 11696 | break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; |
| 11697 | } |
| 11698 | } |
| 11699 | if (maybe_lhs_val) |lhs_val| { |
| 11700 | if (maybe_rhs_val) |rhs_val| { |
| 11701 | if (lhs_val.isUndef() or rhs_val.isUndef()) { |
| 11702 | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; |
| 11703 | } |
| 11704 | |
| 11705 | const result = try lhs_val.shlWithOverflow(rhs_val, dest_ty, sema.arena, target); |
| 11706 | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); |
| 11707 | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); |
| 11708 | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; |
| 11709 | } |
| 11710 | } |
| 11711 | }, |
| 11712 | else => unreachable, |
| 11713 | } |
| 11714 | |
| 11715 | const air_tag: Air.Inst.Tag = switch (zir_tag) { |
| 11716 | .add_with_overflow => .add_with_overflow, |
| 11717 | .mul_with_overflow => .mul_with_overflow, |
| 11718 | .sub_with_overflow => .sub_with_overflow, |
| 11719 | .shl_with_overflow => .shl_with_overflow, |
| 11720 | else => unreachable, |
| 11721 | }; |
| 11722 | |
| 11723 | const runtime_src = if (maybe_lhs_val == null) lhs_src else rhs_src; |
| 11724 | try sema.requireRuntimeBlock(block, src, runtime_src); |
| 11725 | |
| 11726 | const tuple = try block.addInst(.{ |
| 11727 | .tag = air_tag, |
| 11728 | .data = .{ .ty_pl = .{ |
| 11729 | .ty = try block.sema.addType(tuple_ty), |
| 11730 | .payload = try block.sema.addExtra(Air.Bin{ |
| 11731 | .lhs = lhs, |
| 11732 | .rhs = rhs, |
| 11733 | }), |
| 11734 | } }, |
| 11735 | }); |
| 11736 | |
| 11737 | const wrapped = try sema.tupleFieldValByIndex(block, src, tuple, 0, tuple_ty); |
| 11738 | try sema.storePtr2(block, src, ptr, ptr_src, wrapped, src, .store); |
| 11739 | |
| 11740 | const overflow_bit = try sema.tupleFieldValByIndex(block, src, tuple, 1, tuple_ty); |
| 11741 | const zero_ov_val = if (dest_ty.zigTypeTag() == .Vector) try Value.Tag.repeated.create(sema.arena, Value.zero) else Value.zero; |
| 11742 | const zero_ov = try sema.addConstant(ov_ty, zero_ov_val); |
| 11743 | |
| 11744 | const overflowed_inst = if (dest_ty.zigTypeTag() == .Vector) |
| 11745 | block.addCmpVector(overflow_bit, .zero, .neq, try sema.addType(ov_ty)) |
| 11746 | else |
| 11747 | block.addBinOp(.cmp_neq, overflow_bit, zero_ov); |
| 11748 | return overflowed_inst; |
| 11749 | }; |
| 11750 | |
| 11751 | try sema.storePtr2(block, src, ptr, ptr_src, result.wrapped, src, .store); |
| 11752 | return result.overflowed; |
| 11753 | } |
| 11754 | |
| 11755 | fn overflowArithmeticTupleType(sema: *Sema, ty: Type) !Type { |
| 11756 | const ov_ty = if (ty.zigTypeTag() == .Vector) try Type.vector(sema.arena, ty.vectorLen(), Type.@"u1") else Type.@"u1"; |
| 11757 | |
| 11758 | const types = try sema.arena.alloc(Type, 2); |
| 11759 | const values = try sema.arena.alloc(Value, 2); |
| 11760 | const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ |
| 11761 | .types = types, |
| 11762 | .values = values, |
| 11763 | }); |
| 11764 | |
| 11765 | types[0] = ty; |
| 11766 | types[1] = ov_ty; |
| 11767 | values[0] = Value.initTag(.unreachable_value); |
| 11768 | values[1] = Value.initTag(.unreachable_value); |
| 11769 | |
| 11770 | return tuple_ty; |
| 11771 | } |
| 11772 | |
| 11773 | fn analyzeArithmetic( |
| 11143 | 11774 | sema: *Sema, |
| 11144 | 11775 | block: *Block, |
| 11145 | 11776 | /// TODO performance investigation: make this comptime? |
| ... | ... | @@ -11185,14 +11816,9 @@ fn analyzeArithmetic( |
| 11185 | 11816 | const rhs_scalar_ty = rhs_ty.scalarType(); |
| 11186 | 11817 | const scalar_tag = resolved_type.scalarType().zigTypeTag(); |
| 11187 | 11818 | |
| 11188 | | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; |
| 11189 | | const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; |
| 11190 | | |
| 11191 | | if (!is_int and !(is_float and floatOpAllowed(zir_tag))) { |
| 11192 | | return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ |
| 11193 | | @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag), |
| 11194 | | }); |
| 11195 | | } |
| 11819 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; |
| 11820 | |
| 11821 | try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, zir_tag); |
| 11196 | 11822 | |
| 11197 | 11823 | const mod = sema.mod; |
| 11198 | 11824 | const target = mod.getTarget(); |
| ... | ... | @@ -11399,277 +12025,6 @@ fn analyzeArithmetic( |
| 11399 | 12025 | } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; |
| 11400 | 12026 | } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; |
| 11401 | 12027 | }, |
| 11402 | | .div => { |
| 11403 | | // TODO: emit compile error when .div is used on integers and there would be an |
| 11404 | | // ambiguous result between div_floor and div_trunc. |
| 11405 | | |
| 11406 | | // For integers: |
| 11407 | | // If the lhs is zero, then zero is returned regardless of rhs. |
| 11408 | | // If the rhs is zero, compile error for division by zero. |
| 11409 | | // If the rhs is undefined, compile error because there is a possible |
| 11410 | | // value (zero) for which the division would be illegal behavior. |
| 11411 | | // If the lhs is undefined: |
| 11412 | | // * if lhs type is signed: |
| 11413 | | // * if rhs is comptime-known and not -1, result is undefined |
| 11414 | | // * if rhs is -1 or runtime-known, compile error because there is a |
| 11415 | | // possible value (-min_int / -1) for which division would be |
| 11416 | | // illegal behavior. |
| 11417 | | // * if lhs type is unsigned, undef is returned regardless of rhs. |
| 11418 | | // TODO: emit runtime safety for division by zero |
| 11419 | | // |
| 11420 | | // For floats: |
| 11421 | | // If the rhs is zero: |
| 11422 | | // * comptime_float: compile error for division by zero. |
| 11423 | | // * other float type: |
| 11424 | | // * if the lhs is zero: QNaN |
| 11425 | | // * otherwise: +Inf or -Inf depending on lhs sign |
| 11426 | | // If the rhs is undefined: |
| 11427 | | // * comptime_float: compile error because there is a possible |
| 11428 | | // value (zero) for which the division would be illegal behavior. |
| 11429 | | // * other float type: result is undefined |
| 11430 | | // If the lhs is undefined, result is undefined. |
| 11431 | | switch (scalar_tag) { |
| 11432 | | .Int, .ComptimeInt, .ComptimeFloat => { |
| 11433 | | if (maybe_lhs_val) |lhs_val| { |
| 11434 | | if (!lhs_val.isUndef()) { |
| 11435 | | if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11436 | | return sema.addConstant(resolved_type, Value.zero); |
| 11437 | | } |
| 11438 | | } |
| 11439 | | } |
| 11440 | | if (maybe_rhs_val) |rhs_val| { |
| 11441 | | if (rhs_val.isUndef()) { |
| 11442 | | return sema.failWithUseOfUndef(block, rhs_src); |
| 11443 | | } |
| 11444 | | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11445 | | return sema.failWithDivideByZero(block, rhs_src); |
| 11446 | | } |
| 11447 | | } |
| 11448 | | }, |
| 11449 | | else => {}, |
| 11450 | | } |
| 11451 | | |
| 11452 | | if (maybe_lhs_val) |lhs_val| { |
| 11453 | | if (lhs_val.isUndef()) { |
| 11454 | | if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { |
| 11455 | | if (maybe_rhs_val) |rhs_val| { |
| 11456 | | if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { |
| 11457 | | return sema.addConstUndef(resolved_type); |
| 11458 | | } |
| 11459 | | } |
| 11460 | | return sema.failWithUseOfUndef(block, rhs_src); |
| 11461 | | } |
| 11462 | | return sema.addConstUndef(resolved_type); |
| 11463 | | } |
| 11464 | | |
| 11465 | | if (maybe_rhs_val) |rhs_val| { |
| 11466 | | if (is_int) { |
| 11467 | | return sema.addConstant( |
| 11468 | | resolved_type, |
| 11469 | | try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), |
| 11470 | | ); |
| 11471 | | } else { |
| 11472 | | return sema.addConstant( |
| 11473 | | resolved_type, |
| 11474 | | try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), |
| 11475 | | ); |
| 11476 | | } |
| 11477 | | } else { |
| 11478 | | if (is_int) { |
| 11479 | | break :rs .{ .src = rhs_src, .air_tag = .div_trunc }; |
| 11480 | | } else { |
| 11481 | | break :rs .{ .src = rhs_src, .air_tag = if (block.float_mode == .Optimized) .div_float_optimized else .div_float }; |
| 11482 | | } |
| 11483 | | } |
| 11484 | | } else { |
| 11485 | | if (is_int) { |
| 11486 | | break :rs .{ .src = lhs_src, .air_tag = .div_trunc }; |
| 11487 | | } else { |
| 11488 | | break :rs .{ .src = lhs_src, .air_tag = if (block.float_mode == .Optimized) .div_float_optimized else .div_float }; |
| 11489 | | } |
| 11490 | | } |
| 11491 | | }, |
| 11492 | | .div_trunc => { |
| 11493 | | // For integers: |
| 11494 | | // If the lhs is zero, then zero is returned regardless of rhs. |
| 11495 | | // If the rhs is zero, compile error for division by zero. |
| 11496 | | // If the rhs is undefined, compile error because there is a possible |
| 11497 | | // value (zero) for which the division would be illegal behavior. |
| 11498 | | // If the lhs is undefined: |
| 11499 | | // * if lhs type is signed: |
| 11500 | | // * if rhs is comptime-known and not -1, result is undefined |
| 11501 | | // * if rhs is -1 or runtime-known, compile error because there is a |
| 11502 | | // possible value (-min_int / -1) for which division would be |
| 11503 | | // illegal behavior. |
| 11504 | | // * if lhs type is unsigned, undef is returned regardless of rhs. |
| 11505 | | // TODO: emit runtime safety for division by zero |
| 11506 | | // |
| 11507 | | // For floats: |
| 11508 | | // If the rhs is zero, compile error for division by zero. |
| 11509 | | // If the rhs is undefined, compile error because there is a possible |
| 11510 | | // value (zero) for which the division would be illegal behavior. |
| 11511 | | // If the lhs is undefined, result is undefined. |
| 11512 | | if (maybe_lhs_val) |lhs_val| { |
| 11513 | | if (!lhs_val.isUndef()) { |
| 11514 | | if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11515 | | return sema.addConstant(resolved_type, Value.zero); |
| 11516 | | } |
| 11517 | | } |
| 11518 | | } |
| 11519 | | if (maybe_rhs_val) |rhs_val| { |
| 11520 | | if (rhs_val.isUndef()) { |
| 11521 | | return sema.failWithUseOfUndef(block, rhs_src); |
| 11522 | | } |
| 11523 | | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11524 | | return sema.failWithDivideByZero(block, rhs_src); |
| 11525 | | } |
| 11526 | | } |
| 11527 | | const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_trunc_optimized else .div_trunc; |
| 11528 | | if (maybe_lhs_val) |lhs_val| { |
| 11529 | | if (lhs_val.isUndef()) { |
| 11530 | | if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { |
| 11531 | | if (maybe_rhs_val) |rhs_val| { |
| 11532 | | if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { |
| 11533 | | return sema.addConstUndef(resolved_type); |
| 11534 | | } |
| 11535 | | } |
| 11536 | | return sema.failWithUseOfUndef(block, rhs_src); |
| 11537 | | } |
| 11538 | | return sema.addConstUndef(resolved_type); |
| 11539 | | } |
| 11540 | | |
| 11541 | | if (maybe_rhs_val) |rhs_val| { |
| 11542 | | if (is_int) { |
| 11543 | | return sema.addConstant( |
| 11544 | | resolved_type, |
| 11545 | | try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), |
| 11546 | | ); |
| 11547 | | } else { |
| 11548 | | return sema.addConstant( |
| 11549 | | resolved_type, |
| 11550 | | try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target), |
| 11551 | | ); |
| 11552 | | } |
| 11553 | | } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; |
| 11554 | | } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; |
| 11555 | | }, |
| 11556 | | .div_floor => { |
| 11557 | | // For integers: |
| 11558 | | // If the lhs is zero, then zero is returned regardless of rhs. |
| 11559 | | // If the rhs is zero, compile error for division by zero. |
| 11560 | | // If the rhs is undefined, compile error because there is a possible |
| 11561 | | // value (zero) for which the division would be illegal behavior. |
| 11562 | | // If the lhs is undefined: |
| 11563 | | // * if lhs type is signed: |
| 11564 | | // * if rhs is comptime-known and not -1, result is undefined |
| 11565 | | // * if rhs is -1 or runtime-known, compile error because there is a |
| 11566 | | // possible value (-min_int / -1) for which division would be |
| 11567 | | // illegal behavior. |
| 11568 | | // * if lhs type is unsigned, undef is returned regardless of rhs. |
| 11569 | | // TODO: emit runtime safety for division by zero |
| 11570 | | // |
| 11571 | | // For floats: |
| 11572 | | // If the rhs is zero, compile error for division by zero. |
| 11573 | | // If the rhs is undefined, compile error because there is a possible |
| 11574 | | // value (zero) for which the division would be illegal behavior. |
| 11575 | | // If the lhs is undefined, result is undefined. |
| 11576 | | if (maybe_lhs_val) |lhs_val| { |
| 11577 | | if (!lhs_val.isUndef()) { |
| 11578 | | if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11579 | | return sema.addConstant(resolved_type, Value.zero); |
| 11580 | | } |
| 11581 | | } |
| 11582 | | } |
| 11583 | | if (maybe_rhs_val) |rhs_val| { |
| 11584 | | if (rhs_val.isUndef()) { |
| 11585 | | return sema.failWithUseOfUndef(block, rhs_src); |
| 11586 | | } |
| 11587 | | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11588 | | return sema.failWithDivideByZero(block, rhs_src); |
| 11589 | | } |
| 11590 | | } |
| 11591 | | const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_floor_optimized else .div_floor; |
| 11592 | | if (maybe_lhs_val) |lhs_val| { |
| 11593 | | if (lhs_val.isUndef()) { |
| 11594 | | if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { |
| 11595 | | if (maybe_rhs_val) |rhs_val| { |
| 11596 | | if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { |
| 11597 | | return sema.addConstUndef(resolved_type); |
| 11598 | | } |
| 11599 | | } |
| 11600 | | return sema.failWithUseOfUndef(block, rhs_src); |
| 11601 | | } |
| 11602 | | return sema.addConstUndef(resolved_type); |
| 11603 | | } |
| 11604 | | |
| 11605 | | if (maybe_rhs_val) |rhs_val| { |
| 11606 | | if (is_int) { |
| 11607 | | return sema.addConstant( |
| 11608 | | resolved_type, |
| 11609 | | try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, target), |
| 11610 | | ); |
| 11611 | | } else { |
| 11612 | | return sema.addConstant( |
| 11613 | | resolved_type, |
| 11614 | | try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target), |
| 11615 | | ); |
| 11616 | | } |
| 11617 | | } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; |
| 11618 | | } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; |
| 11619 | | }, |
| 11620 | | .div_exact => { |
| 11621 | | // For integers: |
| 11622 | | // If the lhs is zero, then zero is returned regardless of rhs. |
| 11623 | | // If the rhs is zero, compile error for division by zero. |
| 11624 | | // If the rhs is undefined, compile error because there is a possible |
| 11625 | | // value (zero) for which the division would be illegal behavior. |
| 11626 | | // If the lhs is undefined, compile error because there is a possible |
| 11627 | | // value for which the division would result in a remainder. |
| 11628 | | // TODO: emit runtime safety for if there is a remainder |
| 11629 | | // TODO: emit runtime safety for division by zero |
| 11630 | | // |
| 11631 | | // For floats: |
| 11632 | | // If the rhs is zero, compile error for division by zero. |
| 11633 | | // If the rhs is undefined, compile error because there is a possible |
| 11634 | | // value (zero) for which the division would be illegal behavior. |
| 11635 | | // If the lhs is undefined, compile error because there is a possible |
| 11636 | | // value for which the division would result in a remainder. |
| 11637 | | if (maybe_lhs_val) |lhs_val| { |
| 11638 | | if (lhs_val.isUndef()) { |
| 11639 | | return sema.failWithUseOfUndef(block, rhs_src); |
| 11640 | | } else { |
| 11641 | | if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11642 | | return sema.addConstant(resolved_type, Value.zero); |
| 11643 | | } |
| 11644 | | } |
| 11645 | | } |
| 11646 | | if (maybe_rhs_val) |rhs_val| { |
| 11647 | | if (rhs_val.isUndef()) { |
| 11648 | | return sema.failWithUseOfUndef(block, rhs_src); |
| 11649 | | } |
| 11650 | | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11651 | | return sema.failWithDivideByZero(block, rhs_src); |
| 11652 | | } |
| 11653 | | } |
| 11654 | | const air_tag: Air.Inst.Tag = if (block.float_mode == .Optimized) .div_exact_optimized else .div_exact; |
| 11655 | | if (maybe_lhs_val) |lhs_val| { |
| 11656 | | if (maybe_rhs_val) |rhs_val| { |
| 11657 | | if (is_int) { |
| 11658 | | // TODO: emit compile error if there is a remainder |
| 11659 | | return sema.addConstant( |
| 11660 | | resolved_type, |
| 11661 | | try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), |
| 11662 | | ); |
| 11663 | | } else { |
| 11664 | | // TODO: emit compile error if there is a remainder |
| 11665 | | return sema.addConstant( |
| 11666 | | resolved_type, |
| 11667 | | try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), |
| 11668 | | ); |
| 11669 | | } |
| 11670 | | } else break :rs .{ .src = rhs_src, .air_tag = air_tag }; |
| 11671 | | } else break :rs .{ .src = lhs_src, .air_tag = air_tag }; |
| 11672 | | }, |
| 11673 | 12028 | .mul => { |
| 11674 | 12029 | // For integers: |
| 11675 | 12030 | // If either of the operands are zero, the result is zero. |
| ... | ... | @@ -12048,28 +12403,6 @@ fn analyzeArithmetic( |
| 12048 | 12403 | } |
| 12049 | 12404 | } |
| 12050 | 12405 | switch (rs.air_tag) { |
| 12051 | | // zig fmt: off |
| 12052 | | .div_float, .div_exact, .div_trunc, .div_floor, .div_float_optimized, |
| 12053 | | .div_exact_optimized, .div_trunc_optimized, .div_floor_optimized |
| 12054 | | // zig fmt: on |
| 12055 | | => if (scalar_tag == .Int or block.float_mode == .Optimized) { |
| 12056 | | const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { |
| 12057 | | const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); |
| 12058 | | const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val); |
| 12059 | | const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type)); |
| 12060 | | break :ok try block.addInst(.{ |
| 12061 | | .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, |
| 12062 | | .data = .{ .reduce = .{ |
| 12063 | | .operand = ok, |
| 12064 | | .operation = .And, |
| 12065 | | } }, |
| 12066 | | }); |
| 12067 | | } else ok: { |
| 12068 | | const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero); |
| 12069 | | break :ok try block.addBinOp(if (block.float_mode == .Optimized) .cmp_neq_optimized else .cmp_neq, casted_rhs, zero); |
| 12070 | | }; |
| 12071 | | try sema.addSafetyCheck(block, ok, .divide_by_zero); |
| 12072 | | }, |
| 12073 | 12406 | .rem, .mod, .rem_optimized, .mod_optimized => { |
| 12074 | 12407 | const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { |
| 12075 | 12408 | const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); |
| ... | ... | @@ -12096,47 +12429,6 @@ fn analyzeArithmetic( |
| 12096 | 12429 | }, |
| 12097 | 12430 | else => {}, |
| 12098 | 12431 | } |
| 12099 | | if (rs.air_tag == .div_exact or rs.air_tag == .div_exact_optimized) { |
| 12100 | | const result = try block.addBinOp(.div_exact, casted_lhs, casted_rhs); |
| 12101 | | const ok = if (scalar_tag == .Float) ok: { |
| 12102 | | const floored = try block.addUnOp(.floor, result); |
| 12103 | | |
| 12104 | | if (resolved_type.zigTypeTag() == .Vector) { |
| 12105 | | const eql = try block.addCmpVector(result, floored, .eq, try sema.addType(resolved_type)); |
| 12106 | | break :ok try block.addInst(.{ |
| 12107 | | .tag = if (block.float_mode == .Optimized) .reduce_optimized else .reduce, |
| 12108 | | .data = .{ .reduce = .{ |
| 12109 | | .operand = eql, |
| 12110 | | .operation = .And, |
| 12111 | | } }, |
| 12112 | | }); |
| 12113 | | } else { |
| 12114 | | const is_in_range = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, result, floored); |
| 12115 | | break :ok is_in_range; |
| 12116 | | } |
| 12117 | | } else ok: { |
| 12118 | | const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs); |
| 12119 | | |
| 12120 | | if (resolved_type.zigTypeTag() == .Vector) { |
| 12121 | | const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); |
| 12122 | | const zero = try sema.addConstant(sema.typeOf(casted_rhs), zero_val); |
| 12123 | | const eql = try block.addCmpVector(remainder, zero, .eq, try sema.addType(resolved_type)); |
| 12124 | | break :ok try block.addInst(.{ |
| 12125 | | .tag = .reduce, |
| 12126 | | .data = .{ .reduce = .{ |
| 12127 | | .operand = eql, |
| 12128 | | .operation = .And, |
| 12129 | | } }, |
| 12130 | | }); |
| 12131 | | } else { |
| 12132 | | const zero = try sema.addConstant(sema.typeOf(casted_rhs), Value.zero); |
| 12133 | | const is_in_range = try block.addBinOp(if (block.float_mode == .Optimized) .cmp_eq_optimized else .cmp_eq, remainder, zero); |
| 12134 | | break :ok is_in_range; |
| 12135 | | } |
| 12136 | | }; |
| 12137 | | try sema.addSafetyCheck(block, ok, .exact_division_remainder); |
| 12138 | | return result; |
| 12139 | | } |
| 12140 | 12432 | } |
| 12141 | 12433 | return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); |
| 12142 | 12434 | } |
| ... | ... | @@ -16804,6 +17096,46 @@ fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileEr |
| 16804 | 17096 | } |
| 16805 | 17097 | } |
| 16806 | 17098 | |
| 17099 | fn checkInvalidPtrArithmetic( |
| 17100 | sema: *Sema, |
| 17101 | block: *Block, |
| 17102 | src: LazySrcLoc, |
| 17103 | ty: Type, |
| 17104 | zir_tag: Zir.Inst.Tag, |
| 17105 | ) CompileError!void { |
| 17106 | switch (try ty.zigTypeTagOrPoison()) { |
| 17107 | .Pointer => switch (ty.ptrSize()) { |
| 17108 | .One, .Slice => return, |
| 17109 | .Many, .C => return sema.fail( |
| 17110 | block, |
| 17111 | src, |
| 17112 | "invalid pointer arithmetic operand: '{s}''", |
| 17113 | .{@tagName(zir_tag)}, |
| 17114 | ), |
| 17115 | }, |
| 17116 | else => return, |
| 17117 | } |
| 17118 | } |
| 17119 | |
| 17120 | fn checkArithmeticOp( |
| 17121 | sema: *Sema, |
| 17122 | block: *Block, |
| 17123 | src: LazySrcLoc, |
| 17124 | scalar_tag: std.builtin.TypeId, |
| 17125 | lhs_zig_ty_tag: std.builtin.TypeId, |
| 17126 | rhs_zig_ty_tag: std.builtin.TypeId, |
| 17127 | zir_tag: Zir.Inst.Tag, |
| 17128 | ) CompileError!void { |
| 17129 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; |
| 17130 | const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; |
| 17131 | |
| 17132 | if (!is_int and !(is_float and floatOpAllowed(zir_tag))) { |
| 17133 | return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ |
| 17134 | @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag), |
| 17135 | }); |
| 17136 | } |
| 17137 | } |
| 17138 | |
| 16807 | 17139 | fn checkPtrOperand( |
| 16808 | 17140 | sema: *Sema, |
| 16809 | 17141 | block: *Block, |