| ... | ... | @@ -22984,104 +22984,127 @@ fn analyzeMinMax( |
| 22984 | 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 | 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 | 22991 | var cur_minmax: ?Air.Inst.Ref = null; |
| 22990 | 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 | 23004 | for (operands, operand_srcs, 0..) |operand, operand_src, operand_idx| { |
| 22992 | 23005 | // Resolve the value now to avoid redundant calls to `checkSimdBinOp` - we'll have to call |
| 22993 | 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; |
| 22995 | | if (cur_minmax) |cur| { |
| 22996 | | const simd_op = try sema.checkSimdBinOp(block, src, cur, operand, cur_minmax_src, operand_src); |
| 22997 | | const cur_val = simd_op.lhs_val.?; // cur_minmax is comptime-known |
| 22998 | | const operand_val = simd_op.rhs_val.?; // we checked the operand was resolvable above |
| 22999 | | |
| 23000 | | runtime_known.unset(operand_idx); |
| 23007 | const unresolved_uncoerced_val = try sema.resolveMaybeUndefVal(operand) orelse continue; |
| 23008 | const uncoerced_val = try sema.resolveLazyValue(unresolved_uncoerced_val); |
| 23009 | |
| 23010 | runtime_known.unset(operand_idx); |
| 23011 | |
| 23012 | switch (bounds_status) { |
| 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 |
| 23003 | | if (operand_val.isUndef(mod)) { |
| 23004 | | cur_minmax = try sema.addConstUndef(simd_op.result_ty); |
| 23005 | | continue; |
| 23006 | | } |
| 23047 | const cur = cur_minmax orelse { |
| 23048 | cur_minmax = operand; |
| 23049 | cur_minmax_src = operand_src; |
| 23050 | continue; |
| 23051 | }; |
| 23007 | 23052 | |
| 23008 | | const resolved_cur_val = try sema.resolveLazyValue(cur_val); |
| 23009 | | const resolved_operand_val = try sema.resolveLazyValue(operand_val); |
| 23053 | const simd_op = try sema.checkSimdBinOp(block, src, cur, operand, cur_minmax_src, operand_src); |
| 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 { |
| 23012 | | const result_val = opFunc(resolved_cur_val, resolved_operand_val, mod); |
| 23013 | | cur_minmax = try sema.addConstant(simd_op.result_ty, result_val); |
| 23014 | | continue; |
| 23015 | | }; |
| 23016 | | const elems = try sema.arena.alloc(InternPool.Index, vec_len); |
| 23017 | | for (elems, 0..) |*elem, i| { |
| 23018 | | const lhs_elem_val = try resolved_cur_val.elemValue(mod, i); |
| 23019 | | const rhs_elem_val = try resolved_operand_val.elemValue(mod, i); |
| 23020 | | elem.* = try opFunc(lhs_elem_val, rhs_elem_val, mod).intern(simd_op.scalar_ty, mod); |
| 23021 | | } |
| 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; |
| 23057 | const vec_len = simd_op.len orelse { |
| 23058 | const result_val = opFunc(cur_val, operand_val, mod); |
| 23059 | cur_minmax = try sema.addConstant(simd_op.result_ty, result_val); |
| 23060 | continue; |
| 23061 | }; |
| 23062 | const elems = try sema.arena.alloc(InternPool.Index, vec_len); |
| 23063 | for (elems, 0..) |*elem, i| { |
| 23064 | const lhs_elem_val = try cur_val.elemValue(mod, i); |
| 23065 | const rhs_elem_val = try operand_val.elemValue(mod, i); |
| 23066 | const uncoerced_elem = opFunc(lhs_elem_val, rhs_elem_val, mod); |
| 23067 | elem.* = (try mod.getCoerced(uncoerced_elem, simd_op.scalar_ty)).toIntern(); |
| 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 | 23075 | const opt_runtime_idx = runtime_known.findFirstSet(); |
| 23034 | 23076 | |
| 23035 | | const comptime_refined_ty: ?Type = if (cur_minmax) |ct_minmax_ref| refined: { |
| 23036 | | // Refine the comptime-known result type based on the operation |
| 23077 | if (cur_minmax) |ct_minmax_ref| refine: { |
| 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 | 23083 | const val = (try sema.resolveMaybeUndefVal(ct_minmax_ref)).?; |
| 23038 | 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 | 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: { |
| 23046 | | const elem_ty = orig_ty.childType(mod); |
| 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 |
| 23091 | // We can't refine float types |
| 23092 | if (orig_ty.scalarType(mod).isAnyFloat()) break :refine; |
| 23051 | 23093 | |
| 23052 | | var cur_min: Value = try val.elemValue(mod, 0); |
| 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 | | } |
| 23094 | assert(bounds_status == .defined); // there was a non-comptime-int integral comptime-known arg |
| 23060 | 23095 | |
| 23061 | | const refined_elem_ty = try mod.intFittingRange(cur_min, cur_max); |
| 23062 | | break :blk try mod.vectorType(.{ |
| 23063 | | .len = len, |
| 23064 | | .child = refined_elem_ty.toIntern(), |
| 23065 | | }); |
| 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 | | }; |
| 23096 | const refined_scalar_ty = try mod.intFittingRange(cur_min_scalar, cur_max_scalar); |
| 23097 | const refined_ty = if (orig_ty.isVector(mod)) try mod.vectorType(.{ |
| 23098 | .len = orig_ty.vectorLen(mod), |
| 23099 | .child = refined_scalar_ty.toIntern(), |
| 23100 | }) else refined_scalar_ty; |
| 23071 | 23101 | |
| 23072 | | // Apply the refined type to the current value - this isn't strictly necessary in the |
| 23073 | | // runtime case since we'll refine again afterwards, but keeping things as small as possible |
| 23074 | | // will allow us to emit more optimal AIR (if all the runtime operands have smaller types |
| 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); |
| 23102 | // Apply the refined type to the current value |
| 23103 | if (std.debug.runtime_safety) { |
| 23104 | assert(try sema.intFitsInType(val, refined_ty, null)); |
| 23081 | 23105 | } |
| 23082 | | |
| 23083 | | break :refined refined_ty; |
| 23084 | | } else null; |
| 23106 | cur_minmax = try sema.coerceInMemory(val, refined_ty); |
| 23107 | } |
| 23085 | 23108 | |
| 23086 | 23109 | const runtime_idx = opt_runtime_idx orelse return cur_minmax.?; |
| 23087 | 23110 | const runtime_src = operand_srcs[runtime_idx]; |
| ... | ... | @@ -23102,6 +23125,11 @@ fn analyzeMinMax( |
| 23102 | 23125 | cur_minmax = operands[0]; |
| 23103 | 23126 | cur_minmax_src = runtime_src; |
| 23104 | 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 | 23135 | var it = runtime_known.iterator(.{}); |
| ... | ... | @@ -23112,49 +23140,49 @@ fn analyzeMinMax( |
| 23112 | 23140 | const rhs_src = operand_srcs[idx]; |
| 23113 | 23141 | const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src); |
| 23114 | 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 | 23144 | } else { |
| 23117 | 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: { |
| 23122 | | // Finally, refine the type based on the comptime-known bound. |
| 23123 | | if (known_undef) break :refine; // can't refine undef |
| 23124 | | const unrefined_ty = sema.typeOf(cur_minmax.?); |
| 23125 | | const is_vector = unrefined_ty.zigTypeTag(mod) == .Vector; |
| 23126 | | const comptime_elem_ty = if (is_vector) comptime_ty.childType(mod) else comptime_ty; |
| 23127 | | const unrefined_elem_ty = if (is_vector) unrefined_ty.childType(mod) else unrefined_ty; |
| 23128 | | |
| 23129 | | if (unrefined_elem_ty.isAnyFloat()) break :refine; // we can't refine floats |
| 23130 | | |
| 23131 | | // Compute the final bounds based on the runtime type and the comptime-known bound type |
| 23132 | | const min_val = switch (air_tag) { |
| 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; |
| 23170 | // Finally, refine the type based on the known bounds. |
| 23171 | const unrefined_ty = sema.typeOf(cur_minmax.?); |
| 23172 | if (unrefined_ty.scalarType(mod).isAnyFloat()) { |
| 23173 | // We can't refine floats, so we're done. |
| 23174 | return cur_minmax.?; |
| 23175 | } |
| 23176 | assert(bounds_status == .defined); // there were integral runtime operands |
| 23177 | const refined_scalar_ty = try mod.intFittingRange(cur_min_scalar, cur_max_scalar); |
| 23178 | const refined_ty = if (unrefined_ty.isVector(mod)) try mod.vectorType(.{ |
| 23179 | .len = unrefined_ty.vectorLen(mod), |
| 23180 | .child = refined_scalar_ty.toIntern(), |
| 23181 | }) else refined_scalar_ty; |
| 23153 | 23182 | |
| 23154 | | if (!final_ty.eql(unrefined_ty, mod)) { |
| 23155 | | // We've reduced the type - cast the result down |
| 23156 | | return block.addTyOp(.intcast, final_ty, cur_minmax.?); |
| 23157 | | } |
| 23183 | if (!refined_ty.eql(unrefined_ty, mod)) { |
| 23184 | // We've reduced the type - cast the result down |
| 23185 | return block.addTyOp(.intcast, refined_ty, cur_minmax.?); |
| 23158 | 23186 | } |
| 23159 | 23187 | |
| 23160 | 23188 | return cur_minmax.?; |