| ... | @@ -22984,104 +22984,127 @@ fn analyzeMinMax( | ... | @@ -22984,104 +22984,127 @@ fn analyzeMinMax( |
| 22984 | else => @compileError("unreachable"), | 22984 | else => @compileError("unreachable"), |
| 22985 | }; | 22985 | }; |
| 22986 | | 22986 | |
| 22987 | // First, find all comptime-known arguments, and get their min/max | 22987 | // The set of runtime-known operands. Set up in the loop below. |
| 22988 | var runtime_known = try std.DynamicBitSet.initFull(sema.arena, operands.len); | 22988 | var runtime_known = try std.DynamicBitSet.initFull(sema.arena, operands.len); |
| | 22989 | // The current minmax value - initially this will always be comptime-known, then we'll add |
| | 22990 | // runtime values into the mix later. |
| 22989 | var cur_minmax: ?Air.Inst.Ref = null; | 22991 | var cur_minmax: ?Air.Inst.Ref = null; |
| 22990 | var cur_minmax_src: LazySrcLoc = undefined; // defined if cur_minmax not null | 22992 | var cur_minmax_src: LazySrcLoc = undefined; // defined if cur_minmax not null |
| | 22993 | // The current known scalar bounds of the value. |
| | 22994 | var bounds_status: enum { |
| | 22995 | unknown, // We've only seen undef comptime_ints so far, so do not know the bounds. |
| | 22996 | defined, // We've seen only integers, so the bounds are defined. |
| | 22997 | non_integral, // There are floats in the mix, so the bounds aren't defined. |
| | 22998 | } = .unknown; |
| | 22999 | var cur_min_scalar: Value = undefined; |
| | 23000 | var cur_max_scalar: Value = undefined; |
| | 23001 | |
| | 23002 | // First, find all comptime-known arguments, and get their min/max |
| | 23003 | |
| 22991 | for (operands, operand_srcs, 0..) |operand, operand_src, operand_idx| { | 23004 | for (operands, operand_srcs, 0..) |operand, operand_src, operand_idx| { |
| 22992 | // Resolve the value now to avoid redundant calls to `checkSimdBinOp` - we'll have to call | 23005 | // Resolve the value now to avoid redundant calls to `checkSimdBinOp` - we'll have to call |
| 22993 | // it in the runtime path anyway since the result type may have been refined | 23006 | // it in the runtime path anyway since the result type may have been refined |
| 22994 | const uncasted_operand_val = (try sema.resolveMaybeUndefVal(operand)) orelse continue; | 23007 | const unresolved_uncoerced_val = try sema.resolveMaybeUndefVal(operand) orelse continue; |
| 22995 | if (cur_minmax) |cur| { | 23008 | const uncoerced_val = try sema.resolveLazyValue(unresolved_uncoerced_val); |
| 22996 | const simd_op = try sema.checkSimdBinOp(block, src, cur, operand, cur_minmax_src, operand_src); | 23009 | |
| 22997 | const cur_val = simd_op.lhs_val.?; // cur_minmax is comptime-known | 23010 | runtime_known.unset(operand_idx); |
| 22998 | const operand_val = simd_op.rhs_val.?; // we checked the operand was resolvable above | 23011 | |
| 22999 | | 23012 | switch (bounds_status) { |
| 23000 | runtime_known.unset(operand_idx); | 23013 | .unknown, .defined => refine_bounds: { |
| | 23014 | const ty = sema.typeOf(operand); |
| | 23015 | if (!ty.scalarType(mod).isInt(mod) and !ty.scalarType(mod).eql(Type.comptime_int, mod)) { |
| | 23016 | bounds_status = .non_integral; |
| | 23017 | break :refine_bounds; |
| | 23018 | } |
| | 23019 | const scalar_bounds: ?[2]Value = bounds: { |
| | 23020 | if (!ty.isVector(mod)) break :bounds try uncoerced_val.intValueBounds(mod); |
| | 23021 | var cur_bounds: [2]Value = try Value.intValueBounds(try uncoerced_val.elemValue(mod, 0), mod) orelse break :bounds null; |
| | 23022 | const len = try sema.usizeCast(block, src, ty.vectorLen(mod)); |
| | 23023 | for (1..len) |i| { |
| | 23024 | const elem = try uncoerced_val.elemValue(mod, i); |
| | 23025 | const elem_bounds = try elem.intValueBounds(mod) orelse break :bounds null; |
| | 23026 | cur_bounds = .{ |
| | 23027 | Value.numberMin(elem_bounds[0], cur_bounds[0], mod), |
| | 23028 | Value.numberMax(elem_bounds[1], cur_bounds[1], mod), |
| | 23029 | }; |
| | 23030 | } |
| | 23031 | break :bounds cur_bounds; |
| | 23032 | }; |
| | 23033 | if (scalar_bounds) |bounds| { |
| | 23034 | if (bounds_status == .unknown) { |
| | 23035 | cur_min_scalar = bounds[0]; |
| | 23036 | cur_max_scalar = bounds[1]; |
| | 23037 | bounds_status = .defined; |
| | 23038 | } else { |
| | 23039 | cur_min_scalar = opFunc(cur_min_scalar, bounds[0], mod); |
| | 23040 | cur_max_scalar = opFunc(cur_max_scalar, bounds[1], mod); |
| | 23041 | } |
| | 23042 | } |
| | 23043 | }, |
| | 23044 | .non_integral => {}, |
| | 23045 | } |
| 23001 | | 23046 | |
| 23002 | if (cur_val.isUndef(mod)) continue; // result is also undef | 23047 | const cur = cur_minmax orelse { |
| 23003 | if (operand_val.isUndef(mod)) { | 23048 | cur_minmax = operand; |
| 23004 | cur_minmax = try sema.addConstUndef(simd_op.result_ty); | 23049 | cur_minmax_src = operand_src; |
| 23005 | continue; | 23050 | continue; |
| 23006 | } | 23051 | }; |
| 23007 | | 23052 | |
| 23008 | const resolved_cur_val = try sema.resolveLazyValue(cur_val); | 23053 | const simd_op = try sema.checkSimdBinOp(block, src, cur, operand, cur_minmax_src, operand_src); |
| 23009 | const resolved_operand_val = try sema.resolveLazyValue(operand_val); | 23054 | const cur_val = try sema.resolveLazyValue(simd_op.lhs_val.?); // cur_minmax is comptime-known |
| | 23055 | const operand_val = try sema.resolveLazyValue(simd_op.rhs_val.?); // we checked the operand was resolvable above |
| 23010 | | 23056 | |
| 23011 | const vec_len = simd_op.len orelse { | 23057 | const vec_len = simd_op.len orelse { |
| 23012 | const result_val = opFunc(resolved_cur_val, resolved_operand_val, mod); | 23058 | const result_val = opFunc(cur_val, operand_val, mod); |
| 23013 | cur_minmax = try sema.addConstant(simd_op.result_ty, result_val); | 23059 | cur_minmax = try sema.addConstant(simd_op.result_ty, result_val); |
| 23014 | continue; | 23060 | continue; |
| 23015 | }; | 23061 | }; |
| 23016 | const elems = try sema.arena.alloc(InternPool.Index, vec_len); | 23062 | const elems = try sema.arena.alloc(InternPool.Index, vec_len); |
| 23017 | for (elems, 0..) |*elem, i| { | 23063 | for (elems, 0..) |*elem, i| { |
| 23018 | const lhs_elem_val = try resolved_cur_val.elemValue(mod, i); | 23064 | const lhs_elem_val = try cur_val.elemValue(mod, i); |
| 23019 | const rhs_elem_val = try resolved_operand_val.elemValue(mod, i); | 23065 | const rhs_elem_val = try operand_val.elemValue(mod, i); |
| 23020 | elem.* = try opFunc(lhs_elem_val, rhs_elem_val, mod).intern(simd_op.scalar_ty, mod); | 23066 | const uncoerced_elem = opFunc(lhs_elem_val, rhs_elem_val, mod); |
| 23021 | } | 23067 | elem.* = (try mod.getCoerced(uncoerced_elem, simd_op.scalar_ty)).toIntern(); |
| 23022 | cur_minmax = try sema.addConstant(simd_op.result_ty, (try mod.intern(.{ .aggregate = .{ | | |
| 23023 | .ty = simd_op.result_ty.toIntern(), | | |
| 23024 | .storage = .{ .elems = elems }, | | |
| 23025 | } })).toValue()); | | |
| 23026 | } else { | | |
| 23027 | runtime_known.unset(operand_idx); | | |
| 23028 | cur_minmax = try sema.addConstant(sema.typeOf(operand), uncasted_operand_val); | | |
| 23029 | cur_minmax_src = operand_src; | | |
| 23030 | } | 23068 | } |
| | 23069 | cur_minmax = try sema.addConstant(simd_op.result_ty, (try mod.intern(.{ .aggregate = .{ |
| | 23070 | .ty = simd_op.result_ty.toIntern(), |
| | 23071 | .storage = .{ .elems = elems }, |
| | 23072 | } })).toValue()); |
| 23031 | } | 23073 | } |
| 23032 | | 23074 | |
| 23033 | const opt_runtime_idx = runtime_known.findFirstSet(); | 23075 | const opt_runtime_idx = runtime_known.findFirstSet(); |
| 23034 | | 23076 | |
| 23035 | const comptime_refined_ty: ?Type = if (cur_minmax) |ct_minmax_ref| refined: { | 23077 | if (cur_minmax) |ct_minmax_ref| refine: { |
| 23036 | // Refine the comptime-known result type based on the operation | 23078 | // Refine the comptime-known result type based on the bounds. This isn't strictly necessary |
| | 23079 | // in the runtime case, since we'll refine the type again later, but keeping things as small |
| | 23080 | // as possible will allow us to emit more optimal AIR (if all the runtime operands have |
| | 23081 | // smaller types than the non-refined comptime type). |
| | 23082 | |
| 23037 | const val = (try sema.resolveMaybeUndefVal(ct_minmax_ref)).?; | 23083 | const val = (try sema.resolveMaybeUndefVal(ct_minmax_ref)).?; |
| 23038 | const orig_ty = sema.typeOf(ct_minmax_ref); | 23084 | const orig_ty = sema.typeOf(ct_minmax_ref); |
| 23039 | | 23085 | |
| 23040 | if (opt_runtime_idx == null and orig_ty.eql(Type.comptime_int, mod)) { | 23086 | if (opt_runtime_idx == null and orig_ty.scalarType(mod).eql(Type.comptime_int, mod)) { |
| 23041 | // If all arguments were `comptime_int`, and there are no runtime args, we'll preserve that type | 23087 | // If all arguments were `comptime_int`, and there are no runtime args, we'll preserve that type |
| 23042 | break :refined orig_ty; | 23088 | break :refine; |
| 23043 | } | 23089 | } |
| 23044 | | 23090 | |
| 23045 | const refined_ty = if (orig_ty.zigTypeTag(mod) == .Vector) blk: { | 23091 | // We can't refine float types |
| 23046 | const elem_ty = orig_ty.childType(mod); | 23092 | if (orig_ty.scalarType(mod).isAnyFloat()) break :refine; |
| 23047 | const len = orig_ty.vectorLen(mod); | | |
| 23048 | | | |
| 23049 | if (len == 0) break :blk orig_ty; | | |
| 23050 | if (elem_ty.isAnyFloat()) break :blk orig_ty; // can't refine floats | | |
| 23051 | | 23093 | |
| 23052 | var cur_min: Value = try val.elemValue(mod, 0); | 23094 | assert(bounds_status == .defined); // there was a non-comptime-int integral comptime-known arg |
| 23053 | var cur_max: Value = cur_min; | | |
| 23054 | for (1..len) |idx| { | | |
| 23055 | const elem_val = try val.elemValue(mod, idx); | | |
| 23056 | if (elem_val.isUndef(mod)) break :blk orig_ty; // can't refine undef | | |
| 23057 | if (Value.order(elem_val, cur_min, mod).compare(.lt)) cur_min = elem_val; | | |
| 23058 | if (Value.order(elem_val, cur_max, mod).compare(.gt)) cur_max = elem_val; | | |
| 23059 | } | | |
| 23060 | | 23095 | |
| 23061 | const refined_elem_ty = try mod.intFittingRange(cur_min, cur_max); | 23096 | const refined_scalar_ty = try mod.intFittingRange(cur_min_scalar, cur_max_scalar); |
| 23062 | break :blk try mod.vectorType(.{ | 23097 | const refined_ty = if (orig_ty.isVector(mod)) try mod.vectorType(.{ |
| 23063 | .len = len, | 23098 | .len = orig_ty.vectorLen(mod), |
| 23064 | .child = refined_elem_ty.toIntern(), | 23099 | .child = refined_scalar_ty.toIntern(), |
| 23065 | }); | 23100 | }) else refined_scalar_ty; |
| 23066 | } else blk: { | | |
| 23067 | if (orig_ty.isAnyFloat()) break :blk orig_ty; // can't refine floats | | |
| 23068 | if (val.isUndef(mod)) break :blk orig_ty; // can't refine undef | | |
| 23069 | break :blk try mod.intFittingRange(val, val); | | |
| 23070 | }; | | |
| 23071 | | 23101 | |
| 23072 | // Apply the refined type to the current value - this isn't strictly necessary in the | 23102 | // Apply the refined type to the current value |
| 23073 | // runtime case since we'll refine again afterwards, but keeping things as small as possible | 23103 | if (std.debug.runtime_safety) { |
| 23074 | // will allow us to emit more optimal AIR (if all the runtime operands have smaller types | 23104 | assert(try sema.intFitsInType(val, refined_ty, null)); |
| 23075 | // than the non-refined comptime type). | | |
| 23076 | if (!refined_ty.eql(orig_ty, mod)) { | | |
| 23077 | if (std.debug.runtime_safety) { | | |
| 23078 | assert(try sema.intFitsInType(val, refined_ty, null)); | | |
| 23079 | } | | |
| 23080 | cur_minmax = try sema.coerceInMemory(val, refined_ty); | | |
| 23081 | } | 23105 | } |
| 23082 | | 23106 | cur_minmax = try sema.coerceInMemory(val, refined_ty); |
| 23083 | break :refined refined_ty; | 23107 | } |
| 23084 | } else null; | | |
| 23085 | | 23108 | |
| 23086 | const runtime_idx = opt_runtime_idx orelse return cur_minmax.?; | 23109 | const runtime_idx = opt_runtime_idx orelse return cur_minmax.?; |
| 23087 | const runtime_src = operand_srcs[runtime_idx]; | 23110 | const runtime_src = operand_srcs[runtime_idx]; |
| ... | @@ -23102,6 +23125,11 @@ fn analyzeMinMax( | ... | @@ -23102,6 +23125,11 @@ fn analyzeMinMax( |
| 23102 | cur_minmax = operands[0]; | 23125 | cur_minmax = operands[0]; |
| 23103 | cur_minmax_src = runtime_src; | 23126 | cur_minmax_src = runtime_src; |
| 23104 | runtime_known.unset(0); // don't look at this operand in the loop below | 23127 | runtime_known.unset(0); // don't look at this operand in the loop below |
| | 23128 | const scalar_ty = sema.typeOf(cur_minmax.?).scalarType(mod); |
| | 23129 | if (scalar_ty.isInt(mod)) { |
| | 23130 | cur_min_scalar = try scalar_ty.minInt(mod, scalar_ty); |
| | 23131 | cur_max_scalar = try scalar_ty.maxInt(mod, scalar_ty); |
| | 23132 | } |
| 23105 | } | 23133 | } |
| 23106 | | 23134 | |
| 23107 | var it = runtime_known.iterator(.{}); | 23135 | var it = runtime_known.iterator(.{}); |
| ... | @@ -23112,49 +23140,49 @@ fn analyzeMinMax( | ... | @@ -23112,49 +23140,49 @@ fn analyzeMinMax( |
| 23112 | const rhs_src = operand_srcs[idx]; | 23140 | const rhs_src = operand_srcs[idx]; |
| 23113 | const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src); | 23141 | const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src); |
| 23114 | if (known_undef) { | 23142 | if (known_undef) { |
| 23115 | cur_minmax = try sema.addConstant(simd_op.result_ty, Value.undef); | 23143 | cur_minmax = try sema.addConstUndef(simd_op.result_ty); |
| 23116 | } else { | 23144 | } else { |
| 23117 | cur_minmax = try block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs); | 23145 | cur_minmax = try block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs); |
| 23118 | } | 23146 | } |
| | 23147 | // Compute the bounds of this type |
| | 23148 | switch (bounds_status) { |
| | 23149 | .unknown, .defined => refine_bounds: { |
| | 23150 | const scalar_ty = sema.typeOf(rhs).scalarType(mod); |
| | 23151 | if (scalar_ty.isAnyFloat()) { |
| | 23152 | bounds_status = .non_integral; |
| | 23153 | break :refine_bounds; |
| | 23154 | } |
| | 23155 | const scalar_min = try scalar_ty.minInt(mod, scalar_ty); |
| | 23156 | const scalar_max = try scalar_ty.maxInt(mod, scalar_ty); |
| | 23157 | if (bounds_status == .unknown) { |
| | 23158 | cur_min_scalar = scalar_min; |
| | 23159 | cur_max_scalar = scalar_max; |
| | 23160 | bounds_status = .defined; |
| | 23161 | } else { |
| | 23162 | cur_min_scalar = opFunc(cur_min_scalar, scalar_min, mod); |
| | 23163 | cur_max_scalar = opFunc(cur_max_scalar, scalar_max, mod); |
| | 23164 | } |
| | 23165 | }, |
| | 23166 | .non_integral => {}, |
| | 23167 | } |
| 23119 | } | 23168 | } |
| 23120 | | 23169 | |
| 23121 | if (comptime_refined_ty) |comptime_ty| refine: { | 23170 | // Finally, refine the type based on the known bounds. |
| 23122 | // Finally, refine the type based on the comptime-known bound. | 23171 | const unrefined_ty = sema.typeOf(cur_minmax.?); |
| 23123 | if (known_undef) break :refine; // can't refine undef | 23172 | if (unrefined_ty.scalarType(mod).isAnyFloat()) { |
| 23124 | const unrefined_ty = sema.typeOf(cur_minmax.?); | 23173 | // We can't refine floats, so we're done. |
| 23125 | const is_vector = unrefined_ty.zigTypeTag(mod) == .Vector; | 23174 | return cur_minmax.?; |
| 23126 | const comptime_elem_ty = if (is_vector) comptime_ty.childType(mod) else comptime_ty; | 23175 | } |
| 23127 | const unrefined_elem_ty = if (is_vector) unrefined_ty.childType(mod) else unrefined_ty; | 23176 | assert(bounds_status == .defined); // there were integral runtime operands |
| 23128 | | 23177 | const refined_scalar_ty = try mod.intFittingRange(cur_min_scalar, cur_max_scalar); |
| 23129 | if (unrefined_elem_ty.isAnyFloat()) break :refine; // we can't refine floats | 23178 | const refined_ty = if (unrefined_ty.isVector(mod)) try mod.vectorType(.{ |
| 23130 | | 23179 | .len = unrefined_ty.vectorLen(mod), |
| 23131 | // Compute the final bounds based on the runtime type and the comptime-known bound type | 23180 | .child = refined_scalar_ty.toIntern(), |
| 23132 | const min_val = switch (air_tag) { | 23181 | }) else refined_scalar_ty; |
| 23133 | .min => try unrefined_elem_ty.minInt(mod, unrefined_elem_ty), | | |
| 23134 | .max => try comptime_elem_ty.minInt(mod, comptime_elem_ty), // @max(ct, rt) >= ct | | |
| 23135 | else => unreachable, | | |
| 23136 | }; | | |
| 23137 | const max_val = switch (air_tag) { | | |
| 23138 | .min => try comptime_elem_ty.maxInt(mod, comptime_elem_ty), // @min(ct, rt) <= ct | | |
| 23139 | .max => try unrefined_elem_ty.maxInt(mod, unrefined_elem_ty), | | |
| 23140 | else => unreachable, | | |
| 23141 | }; | | |
| 23142 | | | |
| 23143 | // Find the smallest type which can contain these bounds | | |
| 23144 | const final_elem_ty = try mod.intFittingRange(min_val, max_val); | | |
| 23145 | | | |
| 23146 | const final_ty = if (is_vector) | | |
| 23147 | try mod.vectorType(.{ | | |
| 23148 | .len = unrefined_ty.vectorLen(mod), | | |
| 23149 | .child = final_elem_ty.toIntern(), | | |
| 23150 | }) | | |
| 23151 | else | | |
| 23152 | final_elem_ty; | | |
| 23153 | | 23182 | |
| 23154 | if (!final_ty.eql(unrefined_ty, mod)) { | 23183 | if (!refined_ty.eql(unrefined_ty, mod)) { |
| 23155 | // We've reduced the type - cast the result down | 23184 | // We've reduced the type - cast the result down |
| 23156 | return block.addTyOp(.intcast, final_ty, cur_minmax.?); | 23185 | return block.addTyOp(.intcast, refined_ty, cur_minmax.?); |
| 23157 | } | | |
| 23158 | } | 23186 | } |
| 23159 | | 23187 | |
| 23160 | return cur_minmax.?; | 23188 | return cur_minmax.?; |