| ... | @@ -875,7 +875,7 @@ fn analyzeBodyInner( | ... | @@ -875,7 +875,7 @@ 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), | 878 | .div => try sema.zirDiv(block, inst), |
| 879 | .div_exact => try sema.zirArithmetic(block, inst, .div_exact), | 879 | .div_exact => try sema.zirArithmetic(block, inst, .div_exact), |
| 880 | .div_floor => try sema.zirArithmetic(block, inst, .div_floor), | 880 | .div_floor => try sema.zirArithmetic(block, inst, .div_floor), |
| 881 | .div_trunc => try sema.zirArithmetic(block, inst, .div_trunc), | 881 | .div_trunc => try sema.zirArithmetic(block, inst, .div_trunc), |
| ... | @@ -10920,6 +10920,243 @@ fn zirArithmetic( | ... | @@ -10920,6 +10920,243 @@ fn zirArithmetic( |
| 10920 | return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); | 10920 | return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); |
| 10921 | } | 10921 | } |
| 10922 | | 10922 | |
| | 10923 | fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { |
| | 10924 | const inst_data = sema.code.instructions.items(.data)[inst].pl_node; |
| | 10925 | const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; |
| | 10926 | const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; |
| | 10927 | const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; |
| | 10928 | const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; |
| | 10929 | const lhs = try sema.resolveInst(extra.lhs); |
| | 10930 | const rhs = try sema.resolveInst(extra.rhs); |
| | 10931 | const lhs_ty = sema.typeOf(lhs); |
| | 10932 | const rhs_ty = sema.typeOf(rhs); |
| | 10933 | const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); |
| | 10934 | const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); |
| | 10935 | try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); |
| | 10936 | try sema.checkInvalidPtrArithmetic(block, src, lhs_ty, .div); |
| | 10937 | |
| | 10938 | const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; |
| | 10939 | const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ |
| | 10940 | .override = &[_]LazySrcLoc{ lhs_src, rhs_src }, |
| | 10941 | }); |
| | 10942 | |
| | 10943 | const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); |
| | 10944 | const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src); |
| | 10945 | |
| | 10946 | const lhs_scalar_ty = lhs_ty.scalarType(); |
| | 10947 | const rhs_scalar_ty = rhs_ty.scalarType(); |
| | 10948 | const scalar_tag = resolved_type.scalarType().zigTypeTag(); |
| | 10949 | |
| | 10950 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; |
| | 10951 | |
| | 10952 | try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div); |
| | 10953 | |
| | 10954 | const mod = sema.mod; |
| | 10955 | const target = mod.getTarget(); |
| | 10956 | const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); |
| | 10957 | const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); |
| | 10958 | |
| | 10959 | // TODO: emit compile error when .div is used on integers and there would be an |
| | 10960 | // ambiguous result between div_floor and div_trunc. |
| | 10961 | |
| | 10962 | // For integers: |
| | 10963 | // If the lhs is zero, then zero is returned regardless of rhs. |
| | 10964 | // If the rhs is zero, compile error for division by zero. |
| | 10965 | // If the rhs is undefined, compile error because there is a possible |
| | 10966 | // value (zero) for which the division would be illegal behavior. |
| | 10967 | // If the lhs is undefined: |
| | 10968 | // * if lhs type is signed: |
| | 10969 | // * if rhs is comptime-known and not -1, result is undefined |
| | 10970 | // * if rhs is -1 or runtime-known, compile error because there is a |
| | 10971 | // possible value (-min_int / -1) for which division would be |
| | 10972 | // illegal behavior. |
| | 10973 | // * if lhs type is unsigned, undef is returned regardless of rhs. |
| | 10974 | // |
| | 10975 | // For floats: |
| | 10976 | // If the rhs is zero: |
| | 10977 | // * comptime_float: compile error for division by zero. |
| | 10978 | // * other float type: |
| | 10979 | // * if the lhs is zero: QNaN |
| | 10980 | // * otherwise: +Inf or -Inf depending on lhs sign |
| | 10981 | // If the rhs is undefined: |
| | 10982 | // * comptime_float: compile error because there is a possible |
| | 10983 | // value (zero) for which the division would be illegal behavior. |
| | 10984 | // * other float type: result is undefined |
| | 10985 | // If the lhs is undefined, result is undefined. |
| | 10986 | switch (scalar_tag) { |
| | 10987 | .Int, .ComptimeInt, .ComptimeFloat => { |
| | 10988 | if (maybe_lhs_val) |lhs_val| { |
| | 10989 | if (!lhs_val.isUndef()) { |
| | 10990 | if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| | 10991 | return sema.addConstant(resolved_type, Value.zero); |
| | 10992 | } |
| | 10993 | } |
| | 10994 | } |
| | 10995 | if (maybe_rhs_val) |rhs_val| { |
| | 10996 | if (rhs_val.isUndef()) { |
| | 10997 | return sema.failWithUseOfUndef(block, rhs_src); |
| | 10998 | } |
| | 10999 | if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { |
| | 11000 | return sema.failWithDivideByZero(block, rhs_src); |
| | 11001 | } |
| | 11002 | } |
| | 11003 | }, |
| | 11004 | else => {}, |
| | 11005 | } |
| | 11006 | |
| | 11007 | const runtime_src = rs: { |
| | 11008 | if (maybe_lhs_val) |lhs_val| { |
| | 11009 | if (lhs_val.isUndef()) { |
| | 11010 | if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { |
| | 11011 | if (maybe_rhs_val) |rhs_val| { |
| | 11012 | if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { |
| | 11013 | return sema.addConstUndef(resolved_type); |
| | 11014 | } |
| | 11015 | } |
| | 11016 | return sema.failWithUseOfUndef(block, rhs_src); |
| | 11017 | } |
| | 11018 | return sema.addConstUndef(resolved_type); |
| | 11019 | } |
| | 11020 | |
| | 11021 | if (maybe_rhs_val) |rhs_val| { |
| | 11022 | if (is_int) { |
| | 11023 | return sema.addConstant( |
| | 11024 | resolved_type, |
| | 11025 | try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), |
| | 11026 | ); |
| | 11027 | } else { |
| | 11028 | return sema.addConstant( |
| | 11029 | resolved_type, |
| | 11030 | try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), |
| | 11031 | ); |
| | 11032 | } |
| | 11033 | } else { |
| | 11034 | break :rs rhs_src; |
| | 11035 | } |
| | 11036 | } else { |
| | 11037 | break :rs lhs_src; |
| | 11038 | } |
| | 11039 | }; |
| | 11040 | |
| | 11041 | try sema.requireRuntimeBlock(block, src, runtime_src); |
| | 11042 | |
| | 11043 | if (block.wantSafety()) { |
| | 11044 | int_overflow: { |
| | 11045 | if (!is_int) break :int_overflow; |
| | 11046 | |
| | 11047 | // If the LHS is unsigned, it cannot cause overflow. |
| | 11048 | if (!lhs_scalar_ty.isSignedInt()) break :int_overflow; |
| | 11049 | |
| | 11050 | // If the LHS is widened to a larger integer type, no overflow is possible. |
| | 11051 | if (lhs_scalar_ty.intInfo(target).bits < resolved_type.intInfo(target).bits) { |
| | 11052 | break :int_overflow; |
| | 11053 | } |
| | 11054 | |
| | 11055 | const min_int = try resolved_type.minInt(sema.arena, target); |
| | 11056 | const neg_one = try Value.Tag.int_i64.create(sema.arena, -1); |
| | 11057 | |
| | 11058 | // If the LHS is comptime-known to be not equal to the min int, |
| | 11059 | // no overflow is possible. |
| | 11060 | if (maybe_lhs_val) |lhs_val| { |
| | 11061 | if (!lhs_val.compare(.eq, min_int, resolved_type, mod)) break :int_overflow; |
| | 11062 | } |
| | 11063 | |
| | 11064 | // If the RHS is comptime-known to not be equal to -1, no overflow is possible. |
| | 11065 | if (maybe_rhs_val) |rhs_val| { |
| | 11066 | if (!rhs_val.compare(.eq, neg_one, resolved_type, mod)) break :int_overflow; |
| | 11067 | } |
| | 11068 | |
| | 11069 | var ok: Air.Inst.Ref = .none; |
| | 11070 | if (resolved_type.zigTypeTag() == .Vector) { |
| | 11071 | const vector_ty_ref = try sema.addType(resolved_type); |
| | 11072 | if (maybe_lhs_val == null) { |
| | 11073 | const min_int_ref = try sema.addConstant( |
| | 11074 | resolved_type, |
| | 11075 | try Value.Tag.repeated.create(sema.arena, min_int), |
| | 11076 | ); |
| | 11077 | ok = try block.addCmpVector(casted_lhs, min_int_ref, .neq, vector_ty_ref); |
| | 11078 | } |
| | 11079 | if (maybe_rhs_val == null) { |
| | 11080 | const neg_one_ref = try sema.addConstant( |
| | 11081 | resolved_type, |
| | 11082 | try Value.Tag.repeated.create(sema.arena, neg_one), |
| | 11083 | ); |
| | 11084 | const rhs_ok = try block.addCmpVector(casted_rhs, neg_one_ref, .neq, vector_ty_ref); |
| | 11085 | if (ok == .none) { |
| | 11086 | ok = rhs_ok; |
| | 11087 | } else { |
| | 11088 | ok = try block.addBinOp(.bool_or, ok, rhs_ok); |
| | 11089 | } |
| | 11090 | } |
| | 11091 | assert(ok != .none); |
| | 11092 | ok = try block.addInst(.{ |
| | 11093 | .tag = .reduce, |
| | 11094 | .data = .{ .reduce = .{ |
| | 11095 | .operand = ok, |
| | 11096 | .operation = .And, |
| | 11097 | } }, |
| | 11098 | }); |
| | 11099 | } else { |
| | 11100 | if (maybe_lhs_val == null) { |
| | 11101 | const min_int_ref = try sema.addConstant(resolved_type, min_int); |
| | 11102 | ok = try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref); |
| | 11103 | } |
| | 11104 | if (maybe_rhs_val == null) { |
| | 11105 | const neg_one_ref = try sema.addConstant(resolved_type, neg_one); |
| | 11106 | const rhs_ok = try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref); |
| | 11107 | if (ok == .none) { |
| | 11108 | ok = rhs_ok; |
| | 11109 | } else { |
| | 11110 | ok = try block.addBinOp(.bool_or, ok, rhs_ok); |
| | 11111 | } |
| | 11112 | } |
| | 11113 | assert(ok != .none); |
| | 11114 | } |
| | 11115 | try sema.addSafetyCheck(block, ok, .integer_overflow); |
| | 11116 | } |
| | 11117 | |
| | 11118 | div_by_zero: { |
| | 11119 | // Strict IEEE floats have well-defined division by zero. |
| | 11120 | if (!is_int and block.float_mode == .Strict) break :div_by_zero; |
| | 11121 | |
| | 11122 | // If rhs was comptime-known to be zero a compile error would have been |
| | 11123 | // emitted above. |
| | 11124 | if (maybe_rhs_val != null) break :div_by_zero; |
| | 11125 | |
| | 11126 | const ok = if (resolved_type.zigTypeTag() == .Vector) ok: { |
| | 11127 | const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); |
| | 11128 | const zero = try sema.addConstant(resolved_type, zero_val); |
| | 11129 | const ok = try block.addCmpVector(casted_rhs, zero, .neq, try sema.addType(resolved_type)); |
| | 11130 | break :ok try block.addInst(.{ |
| | 11131 | .tag = if (is_int) .reduce else .reduce_optimized, |
| | 11132 | .data = .{ .reduce = .{ |
| | 11133 | .operand = ok, |
| | 11134 | .operation = .And, |
| | 11135 | } }, |
| | 11136 | }); |
| | 11137 | } else ok: { |
| | 11138 | const zero = try sema.addConstant(resolved_type, Value.zero); |
| | 11139 | break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero); |
| | 11140 | }; |
| | 11141 | try sema.addSafetyCheck(block, ok, .divide_by_zero); |
| | 11142 | } |
| | 11143 | } |
| | 11144 | |
| | 11145 | const air_tag = if (is_int) Air.Inst.Tag.div_trunc else switch (block.float_mode) { |
| | 11146 | .Optimized => Air.Inst.Tag.div_float_optimized, |
| | 11147 | .Strict => Air.Inst.Tag.div_float, |
| | 11148 | }; |
| | 11149 | return block.addBinOp(air_tag, casted_lhs, casted_rhs); |
| | 11150 | } |
| | 11151 | |
| | 11152 | fn airTag(block: *Block, is_int: bool, normal: Air.Inst.Tag, optimized: Air.Inst.Tag) Air.Inst.Tag { |
| | 11153 | if (is_int) return normal; |
| | 11154 | return switch (block.float_mode) { |
| | 11155 | .Strict => normal, |
| | 11156 | .Optimized => optimized, |
| | 11157 | }; |
| | 11158 | } |
| | 11159 | |
| 10923 | fn zirOverflowArithmetic( | 11160 | fn zirOverflowArithmetic( |
| 10924 | sema: *Sema, | 11161 | sema: *Sema, |
| 10925 | block: *Block, | 11162 | block: *Block, |
| ... | @@ -11399,96 +11636,6 @@ fn analyzeArithmetic( | ... | @@ -11399,96 +11636,6 @@ fn analyzeArithmetic( |
| 11399 | } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; | 11636 | } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; |
| 11400 | } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; | 11637 | } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; |
| 11401 | }, | 11638 | }, |
| 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 => { | 11639 | .div_trunc => { |
| 11493 | // For integers: | 11640 | // For integers: |
| 11494 | // If the lhs is zero, then zero is returned regardless of rhs. | 11641 | // If the lhs is zero, then zero is returned regardless of rhs. |
| ... | @@ -16804,6 +16951,46 @@ fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileEr | ... | @@ -16804,6 +16951,46 @@ fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileEr |
| 16804 | } | 16951 | } |
| 16805 | } | 16952 | } |
| 16806 | | 16953 | |
| | 16954 | fn checkInvalidPtrArithmetic( |
| | 16955 | sema: *Sema, |
| | 16956 | block: *Block, |
| | 16957 | src: LazySrcLoc, |
| | 16958 | ty: Type, |
| | 16959 | zir_tag: Zir.Inst.Tag, |
| | 16960 | ) CompileError!void { |
| | 16961 | switch (try ty.zigTypeTagOrPoison()) { |
| | 16962 | .Pointer => switch (ty.ptrSize()) { |
| | 16963 | .One, .Slice => return, |
| | 16964 | .Many, .C => return sema.fail( |
| | 16965 | block, |
| | 16966 | src, |
| | 16967 | "invalid pointer arithmetic operand: '{s}''", |
| | 16968 | .{@tagName(zir_tag)}, |
| | 16969 | ), |
| | 16970 | }, |
| | 16971 | else => return, |
| | 16972 | } |
| | 16973 | } |
| | 16974 | |
| | 16975 | fn checkArithmeticOp( |
| | 16976 | sema: *Sema, |
| | 16977 | block: *Block, |
| | 16978 | src: LazySrcLoc, |
| | 16979 | scalar_tag: std.builtin.TypeId, |
| | 16980 | lhs_zig_ty_tag: std.builtin.TypeId, |
| | 16981 | rhs_zig_ty_tag: std.builtin.TypeId, |
| | 16982 | zir_tag: Zir.Inst.Tag, |
| | 16983 | ) CompileError!void { |
| | 16984 | const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; |
| | 16985 | const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; |
| | 16986 | |
| | 16987 | if (!is_int and !(is_float and floatOpAllowed(zir_tag))) { |
| | 16988 | return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{ |
| | 16989 | @tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag), |
| | 16990 | }); |
| | 16991 | } |
| | 16992 | } |
| | 16993 | |
| 16807 | fn checkPtrOperand( | 16994 | fn checkPtrOperand( |
| 16808 | sema: *Sema, | 16995 | sema: *Sema, |
| 16809 | block: *Block, | 16996 | block: *Block, |