| ... | @@ -875,10 +875,6 @@ fn analyzeBodyInner( | ... | @@ -875,10 +875,6 @@ fn analyzeBodyInner( |
| 875 | .add => try sema.zirArithmetic(block, inst, .add), | 875 | .add => try sema.zirArithmetic(block, inst, .add), |
| 876 | .addwrap => try sema.zirArithmetic(block, inst, .addwrap), | 876 | .addwrap => try sema.zirArithmetic(block, inst, .addwrap), |
| 877 | .add_sat => try sema.zirArithmetic(block, inst, .add_sat), | 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 | .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem), | 878 | .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem), |
| 883 | .mod => try sema.zirArithmetic(block, inst, .mod), | 879 | .mod => try sema.zirArithmetic(block, inst, .mod), |
| 884 | .rem => try sema.zirArithmetic(block, inst, .rem), | 880 | .rem => try sema.zirArithmetic(block, inst, .rem), |
| ... | @@ -889,6 +885,11 @@ fn analyzeBodyInner( | ... | @@ -889,6 +885,11 @@ fn analyzeBodyInner( |
| 889 | .subwrap => try sema.zirArithmetic(block, inst, .subwrap), | 885 | .subwrap => try sema.zirArithmetic(block, inst, .subwrap), |
| 890 | .sub_sat => try sema.zirArithmetic(block, inst, .sub_sat), | 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 | .maximum => try sema.zirMinMax(block, inst, .max), | 893 | .maximum => try sema.zirMinMax(block, inst, .max), |
| 893 | .minimum => try sema.zirMinMax(block, inst, .min), | 894 | .minimum => try sema.zirMinMax(block, inst, .min), |
| 894 | | 895 | |
| ... | @@ -10920,226 +10921,856 @@ fn zirArithmetic( | ... | @@ -10920,226 +10921,856 @@ fn zirArithmetic( |
| 10920 | return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); | 10921 | return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); |
| 10921 | } | 10922 | } |
| 10922 | | 10923 | |
| 10923 | fn zirOverflowArithmetic( | 10924 | fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 10924 | sema: *Sema, | 10925 | const inst_data = sema.code.instructions.items(.data)[inst].pl_node; |
| 10925 | block: *Block, | 10926 | const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; |
| 10926 | extended: Zir.Inst.Extended.InstData, | 10927 | const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; |
| 10927 | zir_tag: Zir.Inst.Extended, | 10928 | const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; |
| 10928 | ) CompileError!Air.Inst.Ref { | 10929 | const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; |
| 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 | | | |
| 10939 | const lhs = try sema.resolveInst(extra.lhs); | 10930 | const lhs = try sema.resolveInst(extra.lhs); |
| 10940 | const rhs = try sema.resolveInst(extra.rhs); | 10931 | const rhs = try sema.resolveInst(extra.rhs); |
| 10941 | const ptr = try sema.resolveInst(extra.ptr); | | |
| 10942 | | | |
| 10943 | const lhs_ty = sema.typeOf(lhs); | 10932 | const lhs_ty = sema.typeOf(lhs); |
| 10944 | const rhs_ty = sema.typeOf(rhs); | 10933 | const rhs_ty = sema.typeOf(rhs); |
| 10945 | const mod = sema.mod; | 10934 | const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); |
| 10946 | const target = mod.getTarget(); | 10935 | const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); |
| 10947 | | | |
| 10948 | // Note, the types of lhs/rhs (also for shifting)/ptr are already correct as ensured by astgen. | | |
| 10949 | try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); | 10936 | try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); |
| 10950 | const dest_ty = lhs_ty; | 10937 | try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div); |
| 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 | } | | |
| 10954 | | 10938 | |
| 10955 | const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); | 10939 | const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; |
| 10956 | const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, 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); | 10944 | const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); |
| 10959 | const ov_ty = tuple_ty.tupleFields().types[1]; | 10945 | const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); |
| 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"; | | |
| 10963 | | 10946 | |
| 10964 | const result: struct { | 10947 | const lhs_scalar_ty = lhs_ty.scalarType(); |
| 10965 | /// TODO: Rename to `overflow_bit` and make of type `u1`. | 10948 | const rhs_scalar_ty = rhs_ty.scalarType(); |
| 10966 | overflowed: Air.Inst.Ref, | 10949 | const scalar_tag = resolved_type.scalarType().zigTypeTag(); |
| 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 | } | | |
| 10989 | | 10950 | |
| 10990 | const result = try sema.intAddWithOverflow(block, src, lhs_val, rhs_val, dest_ty); | 10951 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; |
| 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 | } | | |
| 11009 | | 10952 | |
| 11010 | const result = try sema.intSubWithOverflow(block, src, lhs_val, rhs_val, dest_ty); | 10953 | try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div); |
| 11011 | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); | 10954 | |
| 11012 | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); | 10955 | const mod = sema.mod; |
| 11013 | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; | 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 | }, | 10995 | } |
| 11017 | .mul_with_overflow => { | 10996 | if (maybe_rhs_val) |rhs_val| { |
| 11018 | // If either of the arguments is zero, the result is zero and no overflow occured. | 10997 | if (rhs_val.isUndef()) { |
| 11019 | // If either of the arguments is one, the result is the other and no overflow occured. | 10998 | return sema.failWithUseOfUndef(block, rhs_src); |
| 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 | } | | |
| 11029 | } | 10999 | } |
| 11030 | | 11000 | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| 11031 | if (maybe_rhs_val) |rhs_val| { | 11001 | return sema.failWithDivideByZero(block, rhs_src); |
| 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 | } | | |
| 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 | if (maybe_rhs_val) |rhs_val| { | 11013 | if (maybe_rhs_val) |rhs_val| { |
| 11043 | if (lhs_val.isUndef() or rhs_val.isUndef()) { | 11014 | if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { |
| 11044 | break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; | 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| { | 11020 | return sema.addConstUndef(resolved_type); |
| 11069 | if (maybe_rhs_val) |rhs_val| { | 11021 | } |
| 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 | } | | |
| 11073 | | 11022 | |
| 11074 | const result = try lhs_val.shlWithOverflow(rhs_val, dest_ty, sema.arena, target); | 11023 | if (maybe_rhs_val) |rhs_val| { |
| 11075 | const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); | 11024 | if (is_int) { |
| 11076 | const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); | 11025 | return sema.addConstant( |
| 11077 | break :result .{ .overflowed = overflowed, .wrapped = wrapped }; | 11026 | resolved_type, |
| 11078 | } | 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 | }, | 11035 | } else { |
| 11081 | else => unreachable, | 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) { | 11043 | try sema.requireRuntimeBlock(block, src, runtime_src); |
| 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); | | |
| 11108 | | 11044 | |
| 11109 | const overflow_bit = try sema.tupleFieldValByIndex(block, src, tuple, 1, tuple_ty); | 11045 | if (block.wantSafety()) { |
| 11110 | const zero_ov_val = if (dest_ty.zigTypeTag() == .Vector) try Value.Tag.repeated.create(sema.arena, Value.zero) else Value.zero; | 11046 | try sema.addDivIntOverflowSafety(block, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int); |
| 11111 | const zero_ov = try sema.addConstant(ov_ty, zero_ov_val); | 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) | 11050 | const air_tag = if (is_int) Air.Inst.Tag.div_trunc else switch (block.float_mode) { |
| 11114 | block.addCmpVector(overflow_bit, .zero, .neq, try sema.addType(ov_ty)) | 11051 | .Optimized => Air.Inst.Tag.div_float_optimized, |
| 11115 | else | 11052 | .Strict => Air.Inst.Tag.div_float, |
| 11116 | block.addBinOp(.cmp_neq, overflow_bit, zero_ov); | | |
| 11117 | return overflowed_inst; | | |
| 11118 | }; | 11053 | }; |
| 11119 | | 11054 | return block.addBinOp(air_tag, casted_lhs, casted_rhs); |
| 11120 | try sema.storePtr2(block, src, ptr, ptr_src, result.wrapped, src, .store); | | |
| 11121 | return result.overflowed; | | |
| 11122 | } | 11055 | } |
| 11123 | | 11056 | |
| 11124 | fn overflowArithmeticTupleType(sema: *Sema, ty: Type) !Type { | 11057 | fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| 11125 | const ov_ty = if (ty.zigTypeTag() == .Vector) try Type.vector(sema.arena, ty.vectorLen(), Type.@"u1") else Type.@"u1"; | 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); | 11072 | const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; |
| 11128 | const values = try sema.arena.alloc(Value, 2); | 11073 | const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ |
| 11129 | const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ | 11074 | .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, |
| 11130 | .types = types, | | |
| 11131 | .values = values, | | |
| 11132 | }); | 11075 | }); |
| 11133 | | 11076 | |
| 11134 | types[0] = ty; | 11077 | const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); |
| 11135 | types[1] = ov_ty; | 11078 | const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); |
| 11136 | values[0] = Value.initTag(.unreachable_value); | | |
| 11137 | values[1] = Value.initTag(.unreachable_value); | | |
| 11138 | | 11079 | |
| 11139 | return tuple_ty; | 11080 | const lhs_scalar_ty = lhs_ty.scalarType(); |
| 11140 | } | 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 | sema: *Sema, | 11774 | sema: *Sema, |
| 11144 | block: *Block, | 11775 | block: *Block, |
| 11145 | /// TODO performance investigation: make this comptime? | 11776 | /// TODO performance investigation: make this comptime? |
| ... | @@ -11185,14 +11816,9 @@ fn analyzeArithmetic( | ... | @@ -11185,14 +11816,9 @@ fn analyzeArithmetic( |
| 11185 | const rhs_scalar_ty = rhs_ty.scalarType(); | 11816 | const rhs_scalar_ty = rhs_ty.scalarType(); |
| 11186 | const scalar_tag = resolved_type.scalarType().zigTypeTag(); | 11817 | const scalar_tag = resolved_type.scalarType().zigTypeTag(); |
| 11187 | | 11818 | |
| 11188 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; | 11819 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; |
| 11189 | const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; | 11820 | |
| 11190 | | 11821 | try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, zir_tag); |
| 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 | } | | |
| 11196 | | 11822 | |
| 11197 | const mod = sema.mod; | 11823 | const mod = sema.mod; |
| 11198 | const target = mod.getTarget(); | 11824 | const target = mod.getTarget(); |
| ... | @@ -11399,277 +12025,6 @@ fn analyzeArithmetic( | ... | @@ -11399,277 +12025,6 @@ fn analyzeArithmetic( |
| 11399 | } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; | 12025 | } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; |
| 11400 | } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; | 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 | .mul => { | 12028 | .mul => { |
| 11674 | // For integers: | 12029 | // For integers: |
| 11675 | // If either of the operands are zero, the result is zero. | 12030 | // If either of the operands are zero, the result is zero. |
| ... | @@ -12048,28 +12403,6 @@ fn analyzeArithmetic( | ... | @@ -12048,28 +12403,6 @@ fn analyzeArithmetic( |
| 12048 | } | 12403 | } |
| 12049 | } | 12404 | } |
| 12050 | switch (rs.air_tag) { | 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 | .rem, .mod, .rem_optimized, .mod_optimized => { | 12406 | .rem, .mod, .rem_optimized, .mod_optimized => { |
| 12074 | const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { | 12407 | const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { |
| 12075 | const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); | 12408 | const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); |
| ... | @@ -12096,47 +12429,6 @@ fn analyzeArithmetic( | ... | @@ -12096,47 +12429,6 @@ fn analyzeArithmetic( |
| 12096 | }, | 12429 | }, |
| 12097 | else => {}, | 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 | return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); | 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,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 | fn checkPtrOperand( | 17139 | fn checkPtrOperand( |
| 16808 | sema: *Sema, | 17140 | sema: *Sema, |
| 16809 | block: *Block, | 17141 | block: *Block, |