authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-28 18:40:30-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-29 02:35:06-07:00
log1fc24e8d807a489254be46c9fcb951617a04f3b1
tree48ffbbb3cc12c3234e1c242f146dd2c9ecb5eca7
parenta48251735787f590491caf4e446dad74c66aa13c

Sema: enhance `div` instruction analysis

Concrete improvements: * Added safety for integer overflow (-MAX_INT/-1) * Omit division by zero safety check when RHS is comptime known to be non-zero. * Avoid emitting `_optimized` variants of AIR instructions for integers (this suffix is intended to be used for floats only). Subjective changes: I extracted the div logic out from analyzeArithmetic in order to reduce the amount of branches - not for performance reasons but for code clarity. It is more lines of code however, and some logic is duplicated.

1 files changed, 278 insertions(+), 91 deletions(-)

src/Sema.zig+278-91
...@@ -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}
1092210922
10923fn 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
11152fn 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
10923fn zirOverflowArithmetic(11160fn 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}
1680616953
16954fn 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
16975fn 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
16807fn checkPtrOperand(16994fn checkPtrOperand(
16808 sema: *Sema,16995 sema: *Sema,
16809 block: *Block,16996 block: *Block,