| ... | @@ -25380,8 +25380,6 @@ fn zirMinMax( | ... | @@ -25380,8 +25380,6 @@ fn zirMinMax( |
| 25380 | const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1); | 25380 | const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1); |
| 25381 | const lhs = try sema.resolveInst(extra.lhs); | 25381 | const lhs = try sema.resolveInst(extra.lhs); |
| 25382 | const rhs = try sema.resolveInst(extra.rhs); | 25382 | const rhs = try sema.resolveInst(extra.rhs); |
| 25383 | try sema.checkNumericType(block, lhs_src, sema.typeOf(lhs)); | | |
| 25384 | try sema.checkNumericType(block, rhs_src, sema.typeOf(rhs)); | | |
| 25385 | return sema.analyzeMinMax(block, src, air_tag, &.{ lhs, rhs }, &.{ lhs_src, rhs_src }); | 25383 | return sema.analyzeMinMax(block, src, air_tag, &.{ lhs, rhs }, &.{ lhs_src, rhs_src }); |
| 25386 | } | 25384 | } |
| 25387 | | 25385 | |
| ... | @@ -25402,7 +25400,6 @@ fn zirMinMaxMulti( | ... | @@ -25402,7 +25400,6 @@ fn zirMinMaxMulti( |
| 25402 | for (operands, air_refs, operand_srcs, 0..) |zir_ref, *air_ref, *op_src, i| { | 25400 | for (operands, air_refs, operand_srcs, 0..) |zir_ref, *air_ref, *op_src, i| { |
| 25403 | op_src.* = block.builtinCallArgSrc(src_node, @intCast(i)); | 25401 | op_src.* = block.builtinCallArgSrc(src_node, @intCast(i)); |
| 25404 | air_ref.* = try sema.resolveInst(zir_ref); | 25402 | air_ref.* = try sema.resolveInst(zir_ref); |
| 25405 | try sema.checkNumericType(block, op_src.*, sema.typeOf(air_ref.*)); | | |
| 25406 | } | 25403 | } |
| 25407 | | 25404 | |
| 25408 | return sema.analyzeMinMax(block, src, air_tag, air_refs, operand_srcs); | 25405 | return sema.analyzeMinMax(block, src, air_tag, air_refs, operand_srcs); |
| ... | @@ -25418,227 +25415,313 @@ fn analyzeMinMax( | ... | @@ -25418,227 +25415,313 @@ fn analyzeMinMax( |
| 25418 | ) CompileError!Air.Inst.Ref { | 25415 | ) CompileError!Air.Inst.Ref { |
| 25419 | assert(operands.len == operand_srcs.len); | 25416 | assert(operands.len == operand_srcs.len); |
| 25420 | assert(operands.len > 0); | 25417 | assert(operands.len > 0); |
| | 25418 | |
| 25421 | const pt = sema.pt; | 25419 | const pt = sema.pt; |
| 25422 | const zcu = pt.zcu; | 25420 | const zcu = pt.zcu; |
| 25423 | | 25421 | |
| 25424 | if (operands.len == 1) return operands[0]; | 25422 | // This function has the signature `fn (Value, Value, *Zcu) Value`. |
| 25425 | | 25423 | // It is only used on scalar values, although the values may have different types. |
| | 25424 | // If either operand is undef, it returns undef. |
| 25426 | const opFunc = switch (air_tag) { | 25425 | const opFunc = switch (air_tag) { |
| 25427 | .min => Value.numberMin, | 25426 | .min => Value.numberMin, |
| 25428 | .max => Value.numberMax, | 25427 | .max => Value.numberMax, |
| 25429 | else => @compileError("unreachable"), | 25428 | else => comptime unreachable, |
| 25430 | }; | 25429 | }; |
| 25431 | | 25430 | |
| 25432 | // The set of runtime-known operands. Set up in the loop below. | 25431 | if (operands.len == 1) { |
| 25433 | var runtime_known = try std.DynamicBitSet.initFull(sema.arena, operands.len); | 25432 | try sema.checkNumericType(block, operand_srcs[0], sema.typeOf(operands[0])); |
| 25434 | // The current minmax value - initially this will always be comptime-known, then we'll add | 25433 | return operands[0]; |
| 25435 | // runtime values into the mix later. | 25434 | } |
| 25436 | var cur_minmax: ?Air.Inst.Ref = null; | 25435 | |
| 25437 | var cur_minmax_src: LazySrcLoc = undefined; // defined if cur_minmax not null | 25436 | // First, basic type validation; we'll make sure all the operands are numeric and agree on vector length. |
| 25438 | // The current known scalar bounds of the value. | 25437 | // This value will be `null` for a scalar type, otherwise the length of the vector type. |
| 25439 | var bounds_status: enum { | 25438 | const vector_len: ?u64 = vec_len: { |
| 25440 | unknown, // We've only seen undef comptime_ints so far, so do not know the bounds. | 25439 | const first_operand_ty = sema.typeOf(operands[0]); |
| 25441 | defined, // We've seen only integers, so the bounds are defined. | 25440 | try sema.checkNumericType(block, operand_srcs[0], first_operand_ty); |
| 25442 | non_integral, // There are floats in the mix, so the bounds aren't defined. | 25441 | if (first_operand_ty.zigTypeTag(zcu) == .vector) { |
| 25443 | } = .unknown; | 25442 | const vec_len = first_operand_ty.vectorLen(zcu); |
| 25444 | var cur_min_scalar: Value = undefined; | 25443 | for (operands[1..], operand_srcs[1..]) |operand, operand_src| { |
| 25445 | var cur_max_scalar: Value = undefined; | 25444 | const operand_ty = sema.typeOf(operand); |
| 25446 | | 25445 | try sema.checkNumericType(block, operand_src, operand_ty); |
| 25447 | // First, find all comptime-known arguments, and get their min/max | 25446 | if (operand_ty.zigTypeTag(zcu) != .vector) { |
| 25448 | | 25447 | return sema.failWithOwnedErrorMsg(block, msg: { |
| 25449 | for (operands, operand_srcs, 0..) |operand, operand_src, operand_idx| { | 25448 | const msg = try sema.errMsg(operand_src, "expected vector, found '{}'", .{operand_ty.fmt(pt)}); |
| 25450 | // Resolve the value now to avoid redundant calls to `checkSimdBinOp` - we'll have to call | 25449 | errdefer msg.destroy(zcu.gpa); |
| 25451 | // it in the runtime path anyway since the result type may have been refined | 25450 | try sema.errNote(operand_srcs[0], msg, "vector operand here", .{}); |
| 25452 | const unresolved_uncoerced_val = try sema.resolveValue(operand) orelse continue; | 25451 | break :msg msg; |
| 25453 | const uncoerced_val = try sema.resolveLazyValue(unresolved_uncoerced_val); | 25452 | }); |
| 25454 | | 25453 | } |
| 25455 | runtime_known.unset(operand_idx); | 25454 | if (operand_ty.vectorLen(zcu) != vec_len) { |
| 25456 | | 25455 | return sema.failWithOwnedErrorMsg(block, msg: { |
| 25457 | switch (bounds_status) { | 25456 | const msg = try sema.errMsg(operand_src, "expected vector of length '{d}', found '{}'", .{ vec_len, operand_ty.fmt(pt) }); |
| 25458 | .unknown, .defined => refine_bounds: { | 25457 | errdefer msg.destroy(zcu.gpa); |
| 25459 | const ty = sema.typeOf(operand); | 25458 | try sema.errNote(operand_srcs[0], msg, "vector of length '{d}' here", .{vec_len}); |
| 25460 | if (!ty.scalarType(zcu).isInt(zcu) and !ty.scalarType(zcu).eql(Type.comptime_int, zcu)) { | 25459 | break :msg msg; |
| 25461 | bounds_status = .non_integral; | 25460 | }); |
| 25462 | break :refine_bounds; | 25461 | } |
| 25463 | } | 25462 | } |
| 25464 | const scalar_bounds: ?[2]Value = bounds: { | 25463 | break :vec_len vec_len; |
| 25465 | if (!ty.isVector(zcu)) break :bounds try uncoerced_val.intValueBounds(pt); | 25464 | } else { |
| 25466 | var cur_bounds: [2]Value = try Value.intValueBounds(try uncoerced_val.elemValue(pt, 0), pt) orelse break :bounds null; | 25465 | for (operands[1..], operand_srcs[1..]) |operand, operand_src| { |
| 25467 | const len = try sema.usizeCast(block, src, ty.vectorLen(zcu)); | 25466 | const operand_ty = sema.typeOf(operand); |
| 25468 | for (1..len) |i| { | 25467 | if (operand_ty.zigTypeTag(zcu) == .vector) { |
| 25469 | const elem = try uncoerced_val.elemValue(pt, i); | 25468 | return sema.failWithOwnedErrorMsg(block, msg: { |
| 25470 | const elem_bounds = try elem.intValueBounds(pt) orelse break :bounds null; | 25469 | const msg = try sema.errMsg(operand_srcs[0], "expected vector, found '{}'", .{first_operand_ty.fmt(pt)}); |
| 25471 | cur_bounds = .{ | 25470 | errdefer msg.destroy(zcu.gpa); |
| 25472 | Value.numberMin(elem_bounds[0], cur_bounds[0], zcu), | 25471 | try sema.errNote(operand_src, msg, "vector operand here", .{}); |
| 25473 | Value.numberMax(elem_bounds[1], cur_bounds[1], zcu), | 25472 | break :msg msg; |
| 25474 | }; | 25473 | }); |
| | 25474 | } |
| | 25475 | } |
| | 25476 | break :vec_len null; |
| | 25477 | } |
| | 25478 | }; |
| | 25479 | |
| | 25480 | // Now we want to look at the scalar types. If any is a float, our result will be a float. This |
| | 25481 | // union is in "priority" order: `float` overrides `comptime_float` overrides `int`. |
| | 25482 | const TypeStrat = union(enum) { |
| | 25483 | float: Type, |
| | 25484 | comptime_float, |
| | 25485 | int: struct { |
| | 25486 | /// If this is still `true` at the end, we will just use a `comptime_int`. |
| | 25487 | all_comptime_int: bool, |
| | 25488 | // These two fields tells us about the *result* type, which is refined based on operand types. |
| | 25489 | // e.g. `@max(u32, i64)` results in a `u63`, because the result is >=0 and <=maxInt(i64). |
| | 25490 | result_min: Value, |
| | 25491 | result_max: Value, |
| | 25492 | // These two fields tell us the *intermediate* type to use for actually computing the min/max. |
| | 25493 | // e.g. `@max(u32, i64)` uses an intermediate `i64`, because it can fit all our operands. |
| | 25494 | operand_min: Value, |
| | 25495 | operand_max: Value, |
| | 25496 | }, |
| | 25497 | none, |
| | 25498 | }; |
| | 25499 | var cur_strat: TypeStrat = .none; |
| | 25500 | for (operands) |operand| { |
| | 25501 | const operand_scalar_ty = sema.typeOf(operand).scalarType(zcu); |
| | 25502 | const want_strat: TypeStrat = switch (operand_scalar_ty.zigTypeTag(zcu)) { |
| | 25503 | .comptime_int => s: { |
| | 25504 | const val = (try sema.resolveValueResolveLazy(operand)).?; |
| | 25505 | if (val.isUndef(zcu)) break :s .none; |
| | 25506 | break :s .{ .int = .{ |
| | 25507 | .all_comptime_int = true, |
| | 25508 | .result_min = val, |
| | 25509 | .result_max = val, |
| | 25510 | .operand_min = val, |
| | 25511 | .operand_max = val, |
| | 25512 | } }; |
| | 25513 | }, |
| | 25514 | .comptime_float => .comptime_float, |
| | 25515 | .float => .{ .float = operand_scalar_ty }, |
| | 25516 | .int => s: { |
| | 25517 | // If the *value* is comptime-known, we will use that to get tighter bounds. If #3806 |
| | 25518 | // is accepted and implemented, so that integer literals have a tightly-bounded ranged |
| | 25519 | // integer type (and `comptime_int` ceases to exist), this block should probably go away |
| | 25520 | // (replaced with just the simple calls to `Type.minInt`/`Type.maxInt`) so that we only |
| | 25521 | // use the input *types* to determine the result type. |
| | 25522 | const min: Value, const max: Value = bounds: { |
| | 25523 | if (try sema.resolveValueResolveLazy(operand)) |operand_val| { |
| | 25524 | if (vector_len) |len| { |
| | 25525 | var min = try operand_val.elemValue(pt, 0); |
| | 25526 | var max = min; |
| | 25527 | for (1..@intCast(len)) |elem_idx| { |
| | 25528 | const elem_val = try operand_val.elemValue(pt, elem_idx); |
| | 25529 | min = Value.numberMin(min, elem_val, zcu); |
| | 25530 | max = Value.numberMax(max, elem_val, zcu); |
| | 25531 | } |
| | 25532 | if (!min.isUndef(zcu) and !max.isUndef(zcu)) { |
| | 25533 | break :bounds .{ min, max }; |
| | 25534 | } |
| | 25535 | } else { |
| | 25536 | if (!operand_val.isUndef(zcu)) { |
| | 25537 | break :bounds .{ operand_val, operand_val }; |
| | 25538 | } |
| | 25539 | } |
| 25475 | } | 25540 | } |
| 25476 | break :bounds cur_bounds; | 25541 | break :bounds .{ |
| | 25542 | try operand_scalar_ty.minInt(pt, operand_scalar_ty), |
| | 25543 | try operand_scalar_ty.maxInt(pt, operand_scalar_ty), |
| | 25544 | }; |
| 25477 | }; | 25545 | }; |
| 25478 | if (scalar_bounds) |bounds| { | 25546 | break :s .{ .int = .{ |
| 25479 | if (bounds_status == .unknown) { | 25547 | .all_comptime_int = false, |
| 25480 | cur_min_scalar = bounds[0]; | 25548 | .result_min = min, |
| 25481 | cur_max_scalar = bounds[1]; | 25549 | .result_max = max, |
| 25482 | bounds_status = .defined; | 25550 | .operand_min = min, |
| 25483 | } else { | 25551 | .operand_max = max, |
| 25484 | cur_min_scalar = opFunc(cur_min_scalar, bounds[0], zcu); | 25552 | } }; |
| 25485 | cur_max_scalar = opFunc(cur_max_scalar, bounds[1], zcu); | | |
| 25486 | } | | |
| 25487 | } | | |
| 25488 | }, | 25553 | }, |
| 25489 | .non_integral => {}, | 25554 | else => unreachable, |
| 25490 | } | | |
| 25491 | | | |
| 25492 | const cur = cur_minmax orelse { | | |
| 25493 | cur_minmax = operand; | | |
| 25494 | cur_minmax_src = operand_src; | | |
| 25495 | continue; | | |
| 25496 | }; | | |
| 25497 | | | |
| 25498 | const simd_op = try sema.checkSimdBinOp(block, src, cur, operand, cur_minmax_src, operand_src); | | |
| 25499 | const cur_val = try sema.resolveLazyValue(simd_op.lhs_val.?); // cur_minmax is comptime-known | | |
| 25500 | const operand_val = try sema.resolveLazyValue(simd_op.rhs_val.?); // we checked the operand was resolvable above | | |
| 25501 | | | |
| 25502 | const vec_len = simd_op.len orelse { | | |
| 25503 | const result_val = opFunc(cur_val, operand_val, zcu); | | |
| 25504 | cur_minmax = Air.internedToRef(result_val.toIntern()); | | |
| 25505 | continue; | | |
| 25506 | }; | 25555 | }; |
| 25507 | const elems = try sema.arena.alloc(InternPool.Index, vec_len); | 25556 | if (@intFromEnum(want_strat) < @intFromEnum(cur_strat)) { |
| 25508 | for (elems, 0..) |*elem, i| { | 25557 | // `want_strat` overrides `cur_strat`. |
| 25509 | const lhs_elem_val = try cur_val.elemValue(pt, i); | 25558 | cur_strat = want_strat; |
| 25510 | const rhs_elem_val = try operand_val.elemValue(pt, i); | 25559 | } else if (@intFromEnum(want_strat) == @intFromEnum(cur_strat)) { |
| 25511 | const uncoerced_elem = opFunc(lhs_elem_val, rhs_elem_val, zcu); | 25560 | // The behavior depends on the tag. |
| 25512 | elem.* = (try pt.getCoerced(uncoerced_elem, simd_op.scalar_ty)).toIntern(); | 25561 | switch (cur_strat) { |
| | 25562 | .none, .comptime_float => {}, // no payload, so nop |
| | 25563 | .float => |cur_float| { |
| | 25564 | const want_float = want_strat.float; |
| | 25565 | // Select the larger bit size. If the bit size is the same, select whichever is not c_longdouble. |
| | 25566 | const cur_bits = cur_float.floatBits(zcu.getTarget()); |
| | 25567 | const want_bits = want_float.floatBits(zcu.getTarget()); |
| | 25568 | if (want_bits > cur_bits or |
| | 25569 | (want_bits == cur_bits and |
| | 25570 | cur_float.toIntern() == .c_longdouble_type and |
| | 25571 | want_float.toIntern() != .c_longdouble_type)) |
| | 25572 | { |
| | 25573 | cur_strat = want_strat; |
| | 25574 | } |
| | 25575 | }, |
| | 25576 | .int => |*cur_int| { |
| | 25577 | const want_int = want_strat.int; |
| | 25578 | if (!want_int.all_comptime_int) cur_int.all_comptime_int = false; |
| | 25579 | cur_int.result_min = opFunc(cur_int.result_min, want_int.result_min, zcu); |
| | 25580 | cur_int.result_max = opFunc(cur_int.result_max, want_int.result_max, zcu); |
| | 25581 | cur_int.operand_min = Value.numberMin(cur_int.operand_min, want_int.operand_min, zcu); |
| | 25582 | cur_int.operand_max = Value.numberMax(cur_int.operand_max, want_int.operand_max, zcu); |
| | 25583 | }, |
| | 25584 | } |
| 25513 | } | 25585 | } |
| 25514 | cur_minmax = Air.internedToRef((try pt.intern(.{ .aggregate = .{ | | |
| 25515 | .ty = simd_op.result_ty.toIntern(), | | |
| 25516 | .storage = .{ .elems = elems }, | | |
| 25517 | } }))); | | |
| 25518 | } | 25586 | } |
| 25519 | | 25587 | |
| 25520 | const opt_runtime_idx = runtime_known.findFirstSet(); | 25588 | // Use `cur_strat` to actually resolve the result type (and intermediate type). |
| 25521 | | 25589 | const result_scalar_ty: Type, const intermediate_scalar_ty: Type = switch (cur_strat) { |
| 25522 | if (cur_minmax) |ct_minmax_ref| refine: { | 25590 | .float => |ty| .{ ty, ty }, |
| 25523 | // Refine the comptime-known result type based on the bounds. This isn't strictly necessary | 25591 | .comptime_float => .{ .comptime_float, .comptime_float }, |
| 25524 | // in the runtime case, since we'll refine the type again later, but keeping things as small | 25592 | .int => |int| if (int.all_comptime_int) .{ |
| 25525 | // as possible will allow us to emit more optimal AIR (if all the runtime operands have | 25593 | .comptime_int, |
| 25526 | // smaller types than the non-refined comptime type). | 25594 | .comptime_int, |
| 25527 | | 25595 | } else .{ |
| 25528 | const val = (try sema.resolveValue(ct_minmax_ref)).?; | 25596 | try pt.intFittingRange(int.result_min, int.result_max), |
| 25529 | const orig_ty = sema.typeOf(ct_minmax_ref); | 25597 | try pt.intFittingRange(int.operand_min, int.operand_max), |
| 25530 | | 25598 | }, |
| 25531 | if (opt_runtime_idx == null and orig_ty.scalarType(zcu).eql(Type.comptime_int, zcu)) { | 25599 | .none => .{ .comptime_int, .comptime_int }, // all undef comptime ints |
| 25532 | // If all arguments were `comptime_int`, and there are no runtime args, we'll preserve that type | 25600 | }; |
| 25533 | break :refine; | 25601 | const result_ty: Type = if (vector_len) |l| try pt.vectorType(.{ |
| | 25602 | .len = @intCast(l), |
| | 25603 | .child = result_scalar_ty.toIntern(), |
| | 25604 | }) else result_scalar_ty; |
| | 25605 | const intermediate_ty: Type = if (vector_len) |l| try pt.vectorType(.{ |
| | 25606 | .len = @intCast(l), |
| | 25607 | .child = intermediate_scalar_ty.toIntern(), |
| | 25608 | }) else intermediate_scalar_ty; |
| | 25609 | |
| | 25610 | // This value, if not `null`, will have type `intermediate_ty`. |
| | 25611 | const comptime_part: ?Value = ct: { |
| | 25612 | // Contains the comptime-known scalar result values. |
| | 25613 | // Values are scalars with no particular type. |
| | 25614 | // `elems.len` is `vector_len orelse 1`. |
| | 25615 | const elems: []InternPool.Index = try sema.arena.alloc( |
| | 25616 | InternPool.Index, |
| | 25617 | try sema.usizeCast(block, src, vector_len orelse 1), |
| | 25618 | ); |
| | 25619 | // If `false`, we've not seen any comptime-known operand yet, so `elems` contains `undefined`. |
| | 25620 | // Otherwise, `elems` is populated with the comptime-known results so far. |
| | 25621 | var elems_populated = false; |
| | 25622 | // Populated when we see a runtime-known operand. |
| | 25623 | var opt_runtime_src: ?LazySrcLoc = null; |
| | 25624 | |
| | 25625 | for (operands, operand_srcs) |operand, operand_src| { |
| | 25626 | const operand_val = try sema.resolveValueResolveLazy(operand) orelse { |
| | 25627 | if (opt_runtime_src == null) opt_runtime_src = operand_src; |
| | 25628 | continue; |
| | 25629 | }; |
| | 25630 | if (vector_len) |len| { |
| | 25631 | // Vector case; apply `opFunc` to each element. |
| | 25632 | if (elems_populated) { |
| | 25633 | for (elems, 0..@intCast(len)) |*elem, elem_idx| { |
| | 25634 | const new_elem = try operand_val.elemValue(pt, elem_idx); |
| | 25635 | elem.* = opFunc(.fromInterned(elem.*), new_elem, zcu).toIntern(); |
| | 25636 | } |
| | 25637 | } else { |
| | 25638 | elems_populated = true; |
| | 25639 | for (elems, 0..@intCast(len)) |*elem_out, elem_idx| { |
| | 25640 | elem_out.* = (try operand_val.elemValue(pt, elem_idx)).toIntern(); |
| | 25641 | } |
| | 25642 | } |
| | 25643 | } else { |
| | 25644 | // Scalar case; just apply `opFunc`. |
| | 25645 | if (elems_populated) { |
| | 25646 | elems[0] = opFunc(.fromInterned(elems[0]), operand_val, zcu).toIntern(); |
| | 25647 | } else { |
| | 25648 | elems_populated = true; |
| | 25649 | elems[0] = operand_val.toIntern(); |
| | 25650 | } |
| | 25651 | } |
| 25534 | } | 25652 | } |
| 25535 | | 25653 | const runtime_src = opt_runtime_src orelse { |
| 25536 | // We can't refine float types | 25654 | // The result is comptime-known. Coerce each element to its scalar type. |
| 25537 | if (orig_ty.scalarType(zcu).isAnyFloat()) break :refine; | 25655 | assert(elems_populated); |
| 25538 | | 25656 | for (elems) |*elem| { |
| 25539 | assert(bounds_status == .defined); // there was a non-comptime-int integral comptime-known arg | 25657 | if (Value.fromInterned(elem.*).isUndef(zcu)) { |
| 25540 | | 25658 | elem.* = (try pt.undefValue(result_scalar_ty)).toIntern(); |
| 25541 | const refined_scalar_ty = try pt.intFittingRange(cur_min_scalar, cur_max_scalar); | 25659 | } else { |
| 25542 | const refined_ty = if (orig_ty.isVector(zcu)) try pt.vectorType(.{ | 25660 | // This coercion will always succeed, because `result_scalar_ty` can definitely hold the result. |
| 25543 | .len = orig_ty.vectorLen(zcu), | 25661 | const coerced_ref = try sema.coerce(block, result_scalar_ty, Air.internedToRef(elem.*), .unneeded); |
| 25544 | .child = refined_scalar_ty.toIntern(), | 25662 | elem.* = coerced_ref.toInterned().?; |
| 25545 | }) else refined_scalar_ty; | 25663 | } |
| 25546 | | 25664 | } |
| 25547 | // Apply the refined type to the current value | 25665 | if (vector_len == null) return Air.internedToRef(elems[0]); |
| 25548 | if (std.debug.runtime_safety) { | 25666 | return Air.internedToRef(try pt.intern(.{ .aggregate = .{ |
| 25549 | assert(try sema.intFitsInType(val, refined_ty, null)); | 25667 | .ty = result_ty.toIntern(), |
| | 25668 | .storage = .{ .elems = elems }, |
| | 25669 | } })); |
| | 25670 | }; |
| | 25671 | _ = runtime_src; |
| | 25672 | // The result is runtime-known. |
| | 25673 | // Coerce each element to the intermediate scalar type, unless there were no comptime-known operands. |
| | 25674 | if (!elems_populated) break :ct null; |
| | 25675 | for (elems) |*elem| { |
| | 25676 | if (Value.fromInterned(elem.*).isUndef(zcu)) { |
| | 25677 | elem.* = (try pt.undefValue(intermediate_scalar_ty)).toIntern(); |
| | 25678 | } else { |
| | 25679 | // This coercion will always succeed, because `intermediate_scalar_ty` can definitely hold all operands. |
| | 25680 | const coerced_ref = try sema.coerce(block, intermediate_scalar_ty, Air.internedToRef(elem.*), .unneeded); |
| | 25681 | elem.* = coerced_ref.toInterned().?; |
| | 25682 | } |
| 25550 | } | 25683 | } |
| 25551 | cur_minmax = try sema.coerceInMemory(val, refined_ty); | 25684 | break :ct .fromInterned(if (vector_len != null) try pt.intern(.{ .aggregate = .{ |
| 25552 | } | 25685 | .ty = intermediate_ty.toIntern(), |
| 25553 | | 25686 | .storage = .{ .elems = elems }, |
| 25554 | const runtime_idx = opt_runtime_idx orelse return cur_minmax.?; | 25687 | } }) else elems[0]); |
| 25555 | const runtime_src = operand_srcs[runtime_idx]; | 25688 | }; |
| 25556 | try sema.requireRuntimeBlock(block, src, runtime_src); | | |
| 25557 | | | |
| 25558 | // Now, iterate over runtime operands, emitting a min/max instruction for each. We'll refine the | | |
| 25559 | // type again at the end, based on the comptime-known bound. | | |
| 25560 | | | |
| 25561 | // If the comptime-known part is undef we can avoid emitting actual instructions later | | |
| 25562 | const known_undef = if (cur_minmax) |operand| blk: { | | |
| 25563 | const val = (try sema.resolveValue(operand)).?; | | |
| 25564 | break :blk val.isUndef(zcu); | | |
| 25565 | } else false; | | |
| 25566 | | 25689 | |
| 25567 | if (cur_minmax == null) { | 25690 | // Time to emit the runtime operations. All runtime-known peers are coerced to `intermediate_ty`, and we cast down to `result_ty` at the end. |
| 25568 | // No comptime operands - use the first operand as the starting value | | |
| 25569 | assert(bounds_status == .unknown); | | |
| 25570 | assert(runtime_idx == 0); | | |
| 25571 | cur_minmax = operands[0]; | | |
| 25572 | cur_minmax_src = runtime_src; | | |
| 25573 | runtime_known.unset(0); // don't look at this operand in the loop below | | |
| 25574 | const scalar_ty = sema.typeOf(cur_minmax.?).scalarType(zcu); | | |
| 25575 | if (scalar_ty.isInt(zcu)) { | | |
| 25576 | cur_min_scalar = try scalar_ty.minInt(pt, scalar_ty); | | |
| 25577 | cur_max_scalar = try scalar_ty.maxInt(pt, scalar_ty); | | |
| 25578 | bounds_status = .defined; | | |
| 25579 | } else { | | |
| 25580 | bounds_status = .non_integral; | | |
| 25581 | } | | |
| 25582 | } | | |
| 25583 | | 25691 | |
| 25584 | var it = runtime_known.iterator(.{}); | 25692 | // `.none` indicates no result so far. |
| 25585 | while (it.next()) |idx| { | 25693 | var cur_result: Air.Inst.Ref = if (comptime_part) |val| Air.internedToRef(val.toIntern()) else .none; |
| 25586 | const lhs = cur_minmax.?; | 25694 | for (operands, operand_srcs) |operand, operand_src| { |
| 25587 | const lhs_src = cur_minmax_src; | 25695 | if (try sema.isComptimeKnown(operand)) continue; // already in `comptime_part` |
| 25588 | const rhs = operands[idx]; | 25696 | // This coercion could fail; e.g. coercing a runtime integer peer to a `comptime_float` in a case like `@min(runtime_int, 1.5)`. |
| 25589 | const rhs_src = operand_srcs[idx]; | 25697 | const operand_coerced = try sema.coerce(block, intermediate_ty, operand, operand_src); |
| 25590 | const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src); | 25698 | if (cur_result == .none) { |
| 25591 | if (known_undef) { | 25699 | cur_result = operand_coerced; |
| 25592 | cur_minmax = try pt.undefRef(simd_op.result_ty); | | |
| 25593 | } else { | 25700 | } else { |
| 25594 | cur_minmax = try block.addBinOp(air_tag, simd_op.lhs, simd_op.rhs); | 25701 | cur_result = try block.addBinOp(air_tag, cur_result, operand_coerced); |
| 25595 | } | | |
| 25596 | // Compute the bounds of this type | | |
| 25597 | switch (bounds_status) { | | |
| 25598 | .unknown, .defined => refine_bounds: { | | |
| 25599 | const scalar_ty = sema.typeOf(rhs).scalarType(zcu); | | |
| 25600 | if (scalar_ty.isAnyFloat()) { | | |
| 25601 | bounds_status = .non_integral; | | |
| 25602 | break :refine_bounds; | | |
| 25603 | } | | |
| 25604 | const scalar_min = try scalar_ty.minInt(pt, scalar_ty); | | |
| 25605 | const scalar_max = try scalar_ty.maxInt(pt, scalar_ty); | | |
| 25606 | if (bounds_status == .unknown) { | | |
| 25607 | cur_min_scalar = scalar_min; | | |
| 25608 | cur_max_scalar = scalar_max; | | |
| 25609 | bounds_status = .defined; | | |
| 25610 | } else { | | |
| 25611 | cur_min_scalar = opFunc(cur_min_scalar, scalar_min, zcu); | | |
| 25612 | cur_max_scalar = opFunc(cur_max_scalar, scalar_max, zcu); | | |
| 25613 | } | | |
| 25614 | }, | | |
| 25615 | .non_integral => {}, | | |
| 25616 | } | 25702 | } |
| 25617 | } | 25703 | } |
| 25618 | | 25704 | |
| 25619 | // Finally, refine the type based on the known bounds. | 25705 | assert(cur_result != .none); |
| 25620 | const unrefined_ty = sema.typeOf(cur_minmax.?); | 25706 | assert(sema.typeOf(cur_result).toIntern() == intermediate_ty.toIntern()); |
| 25621 | if (unrefined_ty.scalarType(zcu).isAnyFloat()) { | | |
| 25622 | // We can't refine floats, so we're done. | | |
| 25623 | return cur_minmax.?; | | |
| 25624 | } | | |
| 25625 | assert(bounds_status == .defined); // there were integral runtime operands | | |
| 25626 | const refined_scalar_ty = try pt.intFittingRange(cur_min_scalar, cur_max_scalar); | | |
| 25627 | const refined_ty = if (unrefined_ty.isVector(zcu)) try pt.vectorType(.{ | | |
| 25628 | .len = unrefined_ty.vectorLen(zcu), | | |
| 25629 | .child = refined_scalar_ty.toIntern(), | | |
| 25630 | }) else refined_scalar_ty; | | |
| 25631 | | 25707 | |
| 25632 | if (try sema.typeHasOnePossibleValue(refined_ty)) |opv| { | 25708 | // If there is a comptime-known undef operand, we actually return comptime-known undef -- but we had to do the runtime stuff to check for coercion errors. |
| 25633 | return Air.internedToRef(opv.toIntern()); | 25709 | if (comptime_part) |val| { |
| | 25710 | if (val.isUndefDeep(zcu)) { |
| | 25711 | return pt.undefRef(result_ty); |
| | 25712 | } |
| 25634 | } | 25713 | } |
| 25635 | | 25714 | |
| 25636 | if (!refined_ty.eql(unrefined_ty, zcu)) { | 25715 | if (result_ty.toIntern() == intermediate_ty.toIntern()) { |
| 25637 | // We've reduced the type - cast the result down | 25716 | // No final cast needed; we're all done. |
| 25638 | return block.addTyOp(.intcast, refined_ty, cur_minmax.?); | 25717 | return cur_result; |
| 25639 | } | 25718 | } |
| 25640 | | 25719 | |
| 25641 | return cur_minmax.?; | 25720 | // A final cast is needed. The only case where `intermediate_ty` is different is for integers, |
| | 25721 | // where we have refined the range, so we should be doing an intcast. |
| | 25722 | assert(intermediate_scalar_ty.zigTypeTag(zcu) == .int); |
| | 25723 | assert(result_scalar_ty.zigTypeTag(zcu) == .int); |
| | 25724 | return block.addTyOp(.intcast, result_ty, cur_result); |
| 25642 | } | 25725 | } |
| 25643 | | 25726 | |
| 25644 | fn upgradeToArrayPtr(sema: *Sema, block: *Block, ptr: Air.Inst.Ref, len: u64) !Air.Inst.Ref { | 25727 | fn upgradeToArrayPtr(sema: *Sema, block: *Block, ptr: Air.Inst.Ref, len: u64) !Air.Inst.Ref { |