blob: b6401f1bfaf5c5c005bb17fab29fcdd1b58c7fc5 [file]
// Copyright 2024 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/core/ir/analysis/integer_range_analysis.h"
#include <limits>
#include "src/tint/lang/core/constant/scalar.h"
#include "src/tint/lang/core/ir/access.h"
#include "src/tint/lang/core/ir/binary.h"
#include "src/tint/lang/core/ir/exit_if.h"
#include "src/tint/lang/core/ir/exit_loop.h"
#include "src/tint/lang/core/ir/function.h"
#include "src/tint/lang/core/ir/if.h"
#include "src/tint/lang/core/ir/load.h"
#include "src/tint/lang/core/ir/loop.h"
#include "src/tint/lang/core/ir/multi_in_block.h"
#include "src/tint/lang/core/ir/next_iteration.h"
#include "src/tint/lang/core/ir/store.h"
#include "src/tint/lang/core/ir/traverse.h"
#include "src/tint/lang/core/ir/var.h"
#include "src/tint/lang/core/type/i32.h"
#include "src/tint/lang/core/type/pointer.h"
#include "src/tint/lang/core/type/u32.h"
#include "src/tint/utils/rtti/switch.h"
namespace tint::core::ir::analysis {
namespace {
/// Returns true if v is the integer constant 1.
bool IsOne(const Value* v) {
if (auto* cv = v->As<Constant>()) {
return Switch(
cv->Type(),
[&](const core::type::I32*) { return cv->Value()->ValueAs<int32_t>() == 1; },
[&](const core::type::U32*) { return cv->Value()->ValueAs<uint32_t>() == 1; },
[&](const Default) -> bool { return false; });
}
return false;
}
bool IsConstantInteger(const Value* v) {
if (auto* cv = v->As<Constant>()) {
return cv->Type()->IsIntegerScalar();
}
return false;
}
int64_t GetValueFromConstant(const Constant* value) {
// Return an int64_t is enough as the type of `value` can only be either i32 or u32.
[[maybe_unused]] bool is_i32_or_u32 = value->Type()->IsAnyOf<type::I32, type::U32>();
TINT_ASSERT(is_i32_or_u32);
return value->Value()->ValueAs<int64_t>();
}
struct CompareOpAndConstRHS {
BinaryOp op;
int64_t const_rhs = 0;
const Binary* binary = nullptr;
};
} // namespace
IntegerRangeInfo::IntegerRangeInfo(int64_t min_bound, int64_t max_bound) {
TINT_ASSERT(min_bound <= max_bound);
range = SignedIntegerRange{min_bound, max_bound};
}
IntegerRangeInfo::IntegerRangeInfo(uint64_t min_bound, uint64_t max_bound) {
TINT_ASSERT(min_bound <= max_bound);
range = UnsignedIntegerRange{min_bound, max_bound};
}
struct IntegerRangeAnalysisImpl {
explicit IntegerRangeAnalysisImpl(Function* func) : function_(func) {
// Analyze all of the loops in the function.
Traverse(func->Block(), [&](Loop* l) { AnalyzeLoop(l); });
}
const IntegerRangeInfo* GetInfo(const FunctionParam* param, uint32_t index) {
if (!param->Type()->IsIntegerScalarOrVector()) {
return nullptr;
}
// Currently we only support the query of ranges of `local_invocation_id` or
// `local_invocation_index`.
if (!param->Builtin()) {
return nullptr;
}
switch (*param->Builtin()) {
case BuiltinValue::kLocalInvocationIndex:
case BuiltinValue::kLocalInvocationId:
break;
default:
return nullptr;
}
const auto& info = integer_function_param_range_info_map_.GetOrAdd(
param, [&]() -> Vector<IntegerRangeInfo, 3> {
switch (*param->Builtin()) {
case BuiltinValue::kLocalInvocationIndex: {
// We shouldn't be trying to use range analysis on a module that has
// non-constant workgroup sizes, since we will always have replaced pipeline
// overrides with constant values early in the pipeline.
TINT_ASSERT(function_->WorkgroupSizeAsConst().has_value());
std::array<uint32_t, 3> workgroup_size =
function_->WorkgroupSizeAsConst().value();
uint64_t max_bound =
workgroup_size[0] * workgroup_size[1] * workgroup_size[2] - 1u;
constexpr uint64_t kMinBound = 0;
return {IntegerRangeInfo(kMinBound, max_bound)};
}
case BuiltinValue::kLocalInvocationId: {
TINT_ASSERT(function_->WorkgroupSizeAsConst().has_value());
std::array<uint32_t, 3> workgroup_size =
function_->WorkgroupSizeAsConst().value();
constexpr uint64_t kMinBound = 0;
Vector<IntegerRangeInfo, 3> integerRanges;
for (uint32_t size_x_y_z : workgroup_size) {
integerRanges.Push({kMinBound, size_x_y_z - 1u});
}
return integerRanges;
}
default:
TINT_UNREACHABLE();
}
});
TINT_ASSERT(info.Length() > index);
return &info[index];
}
const IntegerRangeInfo* GetInfo(const Var* var) {
return integer_var_range_info_map_.Get(var).value;
}
const IntegerRangeInfo* GetInfo(const Load* load) {
const InstructionResult* instruction = load->From()->As<InstructionResult>();
if (!instruction) {
return nullptr;
}
const Var* load_from_var = instruction->Instruction()->As<Var>();
if (!load_from_var) {
return nullptr;
}
return GetInfo(load_from_var);
}
const IntegerRangeInfo* GetInfo(const Access* access) {
const Value* obj = access->Object();
// Currently we only support the access to `local_invocation_id` or `local_invocation_index`
// as a function parameter.
const FunctionParam* function_param = obj->As<FunctionParam>();
if (!function_param) {
return nullptr;
}
if (access->Indices().Length() > 1) {
return nullptr;
}
if (!access->Indices()[0]->As<Constant>()) {
return nullptr;
}
uint32_t index =
static_cast<uint32_t>(GetValueFromConstant(access->Indices()[0]->As<Constant>()));
return GetInfo(function_param, index);
}
/// Analyze a loop to compute the range of the loop control variable if possible.
void AnalyzeLoop(const Loop* loop) {
const Var* index = GetLoopControlVariableFromConstantInitializer(loop);
if (!index) {
return;
}
const Binary* update = GetBinaryToUpdateLoopControlVariableInContinuingBlock(loop, index);
if (!update) {
return;
}
CompareOpAndConstRHS compare_info =
GetCompareInfoOfLoopControlVariableInLoopBody(loop, index);
if (!compare_info.binary) {
return;
}
TINT_ASSERT(index->Initializer());
TINT_ASSERT(index->Initializer()->As<Constant>());
// for (var i = const_init; ...)
int64_t const_init = GetValueFromConstant(index->Initializer()->As<Constant>());
// for (...; i++) or for(...; i--)
bool index_is_increasing = update->Op() == BinaryOp::kAdd;
auto to_integer_range_info = [&](int64_t min_value, int64_t max_value) {
if (index->Initializer()->As<Constant>()->Type()->IsSignedIntegerScalar()) {
return IntegerRangeInfo(min_value, max_value);
} else {
return IntegerRangeInfo(static_cast<uint64_t>(min_value),
static_cast<uint64_t>(max_value));
}
};
switch (compare_info.op) {
case BinaryOp::kLessThanEqual: {
// for (var index = const_init; index <= const_rhs; index++)
// Only `const_init <= const_rhs` is valid. The range of `index` is:
// [const_init, const_rhs]
// Note that in `GetBinaryToCompareLoopControlVariableInLoopBody()` we disallow
// `const_rhs` to be the maximum value of `i32` or `u32`, so:
// - `index + 1` won't be overflow as `const_init` cannot be greater than
// `const_rhs`.
// - `index <= const_rhs` can correctly exit when `const_init + 1` is the maximum
// value of `i32` or `u32`.
if (index_is_increasing && const_init <= compare_info.const_rhs) {
IntegerRangeInfo range_info =
to_integer_range_info(const_init, compare_info.const_rhs);
integer_var_range_info_map_.Add(index, range_info);
}
break;
}
case BinaryOp::kGreaterThanEqual: {
// for (var index = const_init; index >= const_rhs; index--)
// Only `const_init >= const_rhs` is valid. The range of `index` is:
// [const_rhs, const_init]
// Note that in `GetBinaryToCompareLoopControlVariableInLoopBody()` we disallow
// `const_rhs` to be the minimum value of `i32` or `u32`, so:
// - `index - 1` won't be underflow as `const_init` cannot be less than `const_rhs`.
// - `index >= const_rhs` can correctly exit when `const_init - 1` is the minimum
// value of `i32` or `u32`.
if (!index_is_increasing && const_init >= compare_info.const_rhs) {
IntegerRangeInfo range_info =
to_integer_range_info(compare_info.const_rhs, const_init);
integer_var_range_info_map_.Add(index, range_info);
}
break;
}
default:
break;
}
}
const Var* GetLoopControlVariableFromConstantInitializer(const Loop* loop) {
TINT_ASSERT(loop);
auto* init_block = loop->Initializer();
if (!init_block) {
return nullptr;
}
// Currently we only support the loop initializer of a simple for-loop, which only has two
// instructions
// - The first instruction is to initialize the loop control variable
// with a constant integer (signed or unsigned) value.
// - The second instruction is `next_iteration`
// e.g. for (var i = 0; ...)
if (init_block->Length() != 2u) {
return nullptr;
}
auto* var = init_block->Front()->As<Var>();
if (!var) {
return nullptr;
}
if (!init_block->Back()->As<NextIteration>()) {
return nullptr;
}
const auto* pointer = var->Result()->Type()->As<core::type::Pointer>();
if (!pointer->StoreType()->IsIntegerScalar()) {
return nullptr;
}
const auto* initializer = var->Initializer();
if (!initializer) {
return nullptr;
}
if (!initializer->As<Constant>()) {
return nullptr;
}
return var;
}
// Currently we only support the loop continuing of a simple for-loop, which only has 4
// instructions
/// - The first instruction is to load the loop control variable into a temporary variable.
/// - The second instruction is to add one or minus one to the temporary variable.
/// - The third instruction is to store the value of the temporary variable into the loop
/// control variable.
/// - The fourth instruction is `next_iteration`.
const Binary* GetBinaryToUpdateLoopControlVariableInContinuingBlock(
const Loop* loop,
const Var* loop_control_variable) {
TINT_ASSERT(loop);
TINT_ASSERT(loop_control_variable);
auto* continuing_block = loop->Continuing();
if (!continuing_block) {
return nullptr;
}
if (continuing_block->Length() != 4u) {
return nullptr;
}
// 1st instruction:
// %src = load %loop_control_variable
const auto* load_from_loop_control_variable = continuing_block->Instructions()->As<Load>();
if (!load_from_loop_control_variable) {
return nullptr;
}
if (load_from_loop_control_variable->From() != loop_control_variable->Result()) {
return nullptr;
}
// 2nd instruction:
// %dst = add %src, 1
// or %dst = add 1, %src
// or %dst = sub %src, 1
const auto* add_or_sub_from_loop_control_variable =
load_from_loop_control_variable->next->As<Binary>();
if (!add_or_sub_from_loop_control_variable) {
return nullptr;
}
const auto* src = load_from_loop_control_variable->Result();
const auto* lhs = add_or_sub_from_loop_control_variable->LHS();
const auto* rhs = add_or_sub_from_loop_control_variable->RHS();
switch (add_or_sub_from_loop_control_variable->Op()) {
case BinaryOp::kAdd: {
// %dst = add %src, 1
if (lhs == src && IsOne(rhs)) {
break;
}
// %dst = add 1, %src
if (rhs == src && IsOne(lhs)) {
break;
}
return nullptr;
}
case BinaryOp::kSubtract: {
// %dst = sub %src, 1
if (lhs == src && IsOne(rhs)) {
break;
}
return nullptr;
}
default:
return nullptr;
}
// 3rd instruction:
// store %loop_control_variable, %dst
const auto* store_into_loop_control_variable =
add_or_sub_from_loop_control_variable->next->As<Store>();
if (!store_into_loop_control_variable) {
return nullptr;
}
const auto* dst = add_or_sub_from_loop_control_variable->Result();
if (store_into_loop_control_variable->From() != dst) {
return nullptr;
}
if (store_into_loop_control_variable->To() != loop_control_variable->Result()) {
return nullptr;
}
// 4th instruction:
// next_iteration
if (!store_into_loop_control_variable->next->As<NextIteration>()) {
return nullptr;
}
return add_or_sub_from_loop_control_variable;
}
// Currently we only support the loop continuing of a simple for-loop which meets all the below
// requirements:
// - The loop control variable is only used as the parameter of the load instruction.
// - The first instruction is to load the loop control variable into a temporary variable.
// - The second instruction is a `compare` binary to compare the temporary variable with a
// constant value and save the result to a boolean variable.
// - The second instruction cannot be a comparison that will never return true.
// - The third instruction is an `ifelse` expression that uses the boolean variable got in the
// second instruction as the condition.
// - The true block of the above `ifelse` expression doesn't contain `exit_loop`.
// - The false block of the above `ifelse` expression only contains `exit_loop`.
// A valid `compare` binary instruction (the second instruction) can be in one of the below
// formats:
// 1. variable >= constant variable <= constant
// 2. variable > constant variable < constant
// 3. constant >= variable constant <= variable
// 4. constant > variable constant < variable
// This function analyzes the `compare` binary and returns both `variable` and `constant` in the
// equivalent format of format 1 and a pointer to the `compare` binary in a
// `CompareOpAndConstRHS`struct when the input `loop` meets all the above requirements.
// `CompareOpAndConstRHS.binary` will be set to nullptr otherwise.
CompareOpAndConstRHS GetCompareInfoOfLoopControlVariableInLoopBody(
const Loop* loop,
const Var* loop_control_variable) {
TINT_ASSERT(loop);
TINT_ASSERT(loop_control_variable);
auto* body_block = loop->Body();
CompareOpAndConstRHS compare_info = {};
// Reject any non-load instructions unless it is a store in the continuing block
const auto& uses = loop_control_variable->Result(0)->UsagesUnsorted();
for (auto& use : uses) {
if (use->instruction->Is<Load>()) {
continue;
}
if (use->instruction->Is<Store>() && use->instruction->Block() == loop->Continuing()) {
continue;
}
return compare_info;
}
// 1st instruction:
// %src = load %loop_control_variable
const auto* load_from_loop_control_variable = body_block->Instructions()->As<Load>();
if (!load_from_loop_control_variable) {
return compare_info;
}
if (load_from_loop_control_variable->From() != loop_control_variable->Result(0)) {
return compare_info;
}
// 2nd instruction:
// %condition:bool = lt(gt, lte, gte) %src, constant_value
// or %condition:bool = lt(gt, lte, gte) constant_value, %src
const auto* exit_condition_on_loop_control_variable =
load_from_loop_control_variable->next->As<Binary>();
if (!exit_condition_on_loop_control_variable) {
return compare_info;
}
BinaryOp op = exit_condition_on_loop_control_variable->Op();
auto* lhs = exit_condition_on_loop_control_variable->LHS();
auto* rhs = exit_condition_on_loop_control_variable->RHS();
switch (op) {
case BinaryOp::kLessThan:
case BinaryOp::kGreaterThan:
case BinaryOp::kLessThanEqual:
case BinaryOp::kGreaterThanEqual: {
if (IsConstantInteger(rhs) && lhs == load_from_loop_control_variable->Result(0)) {
break;
}
if (IsConstantInteger(lhs) && rhs == load_from_loop_control_variable->Result(0)) {
break;
}
return compare_info;
}
default:
return compare_info;
}
const bool is_signed = lhs->Type()->IsSignedIntegerScalar();
auto const_i32 = [](const Value* val) {
return val->As<Constant>()->Value()->ValueAs<int32_t>();
};
auto const_u32 = [](const Value* val) {
return val->As<Constant>()->Value()->ValueAs<uint32_t>();
};
// Check and extract necessary information from `binary`.
// Note that we should early return when the comparison will never return true.
// e.g. for (...; i < 0u; ...) // The loop body will never be executed.
// We should also early return when the comparison will always return true, which means the
// loop exit condition takes no effect and the loop control variable can actually take all
// the numbers.
// e.g. for (i = 100u; i >= 0u; i--) // `i` can be any u32 value instead of [0u, 100u]
if (IsConstantInteger(rhs)) {
// Handle `binary` in the format of `variable op const_rhs`
int64_t const_rhs = GetValueFromConstant(rhs->As<Constant>());
switch (op) {
case BinaryOp::kLessThan: {
// variable < std::numeric_limits<uint32_t>::min()
if (!is_signed && const_u32(rhs) == u32::kLowestValue) {
return compare_info;
}
// variable < std::numeric_limits<int32_t>::min()
if (is_signed && const_i32(rhs) == i32::kLowestValue) {
return compare_info;
}
// variable < constant => variable <= constant - 1
// `const_rhs - 1` will always be safe after the above checks.
compare_info.op = BinaryOp::kLessThanEqual;
compare_info.const_rhs = const_rhs - 1;
break;
}
case BinaryOp::kGreaterThan: {
// variable > std::numeric_limits<uint32_t>::max()
if (!is_signed && const_u32(rhs) == u32::kHighestValue) {
return compare_info;
}
// variable > std::numeric_limits<int32_t>::max()
if (is_signed && const_i32(rhs) == i32::kHighestValue) {
return compare_info;
}
// variable > constant => variable >= constant + 1
// `const_rhs` will always be safe after the above checks.
compare_info.op = BinaryOp::kGreaterThanEqual;
compare_info.const_rhs = const_rhs + 1;
break;
}
case BinaryOp::kLessThanEqual: {
// variable <= std::numeric_limits<uint32_t>::max()
if (!is_signed && const_u32(rhs) == u32::kHighestValue) {
return compare_info;
}
// variable <= std::numeric_limits<int32_t>::max()
if (is_signed && const_i32(rhs) == i32::kHighestValue) {
return compare_info;
}
compare_info.op = op;
compare_info.const_rhs = const_rhs;
break;
}
case BinaryOp::kGreaterThanEqual: {
// variable >= std::numeric_limits<uint32_t>::min()
if (!is_signed && const_u32(rhs) == u32::kLowestValue) {
return compare_info;
}
// variable >= std::numeric_limits<int32_t>::min()
if (is_signed && const_i32(rhs) == i32::kLowestValue) {
return compare_info;
}
compare_info.op = op;
compare_info.const_rhs = const_rhs;
break;
}
default:
TINT_UNREACHABLE();
}
} else {
// Handle `binary` in the format of `const_lhs op variable`
TINT_ASSERT(IsConstantInteger(lhs));
int64_t const_lhs = GetValueFromConstant(lhs->As<Constant>());
switch (op) {
case BinaryOp::kLessThan: {
// std::numeric_limits<uint32_t>::max() < variable
if (!is_signed && const_u32(lhs) == u32::kHighestValue) {
return compare_info;
}
// std::numeric_limits<int32_t>::max() < variable
if (is_signed && const_i32(lhs) == i32::kHighestValue) {
return compare_info;
}
// constant < variable => variable > constant => variable >= constant + 1
// `const_lhs + 1` will always be safe after the above checks.
compare_info.op = BinaryOp::kGreaterThanEqual;
compare_info.const_rhs = const_lhs + 1;
break;
}
case BinaryOp::kGreaterThan: {
// std::numeric_limits<uint32_t>::min() > variable
if (!is_signed && const_u32(lhs) == u32::kLowestValue) {
return compare_info;
}
// std::numeric_limits<int32_t>::min() > variable
if (is_signed && const_i32(lhs) == i32::kLowestValue) {
return compare_info;
}
// constant > variable => variable < constant => variable <= constant - 1
// `const_lhs - 1` will always be safe after the above checks.
compare_info.op = BinaryOp::kLessThanEqual;
compare_info.const_rhs = const_lhs - 1;
break;
}
case BinaryOp::kLessThanEqual: {
// std::numeric_limits<uint32_t>::min() <= variable
if (!is_signed && const_u32(lhs) == u32::kLowestValue) {
return compare_info;
}
// std::numeric_limits<int32_t>::min() <= variable
if (is_signed && const_i32(lhs) == i32::kLowestValue) {
return compare_info;
}
// constant <= variable => variable >= constant
compare_info.op = BinaryOp::kGreaterThanEqual;
compare_info.const_rhs = const_lhs;
break;
}
case BinaryOp::kGreaterThanEqual: {
// std::numeric_limits<uint32_t>::max() >= variable
if (!is_signed && const_u32(lhs) == u32::kHighestValue) {
return compare_info;
}
// std::numeric_limits<int32_t>::max() >= variable
if (is_signed && const_i32(lhs) == i32::kHighestValue) {
return compare_info;
}
// constant >= variable => variable <= constant
compare_info.op = BinaryOp::kLessThanEqual;
compare_info.const_rhs = const_lhs;
break;
}
default:
TINT_UNREACHABLE();
}
}
// 3rd instruction:
// if %condition [t: $true, f: $false] {
// $true: {
// // Maybe some other instructions
// exit_if
// }
// $false: { exit_loop }
// }
const auto* if_on_exit_condition = exit_condition_on_loop_control_variable->next->As<If>();
if (!if_on_exit_condition) {
return compare_info;
}
if (if_on_exit_condition->Condition() !=
exit_condition_on_loop_control_variable->Result(0)) {
return compare_info;
}
if (!if_on_exit_condition->True()->Terminator()->As<ExitIf>()) {
return compare_info;
}
if (!if_on_exit_condition->False()->Front()->As<ExitLoop>()) {
return compare_info;
}
// Set `compare_info.binary` after the loop passes all the above checks.
compare_info.binary = exit_condition_on_loop_control_variable;
return compare_info;
}
private:
Function* function_;
Hashmap<const FunctionParam*, Vector<IntegerRangeInfo, 3>, 4>
integer_function_param_range_info_map_;
Hashmap<const Var*, IntegerRangeInfo, 8> integer_var_range_info_map_;
};
IntegerRangeAnalysis::IntegerRangeAnalysis(Function* func)
: impl_(new IntegerRangeAnalysisImpl(func)) {}
IntegerRangeAnalysis::~IntegerRangeAnalysis() = default;
const IntegerRangeInfo* IntegerRangeAnalysis::GetInfo(const FunctionParam* param, uint32_t index) {
return impl_->GetInfo(param, index);
}
const Var* IntegerRangeAnalysis::GetLoopControlVariableFromConstantInitializerForTest(
const Loop* loop) {
return impl_->GetLoopControlVariableFromConstantInitializer(loop);
}
const Binary* IntegerRangeAnalysis::GetBinaryToUpdateLoopControlVariableInContinuingBlockForTest(
const Loop* loop,
const Var* loop_control_variable) {
return impl_->GetBinaryToUpdateLoopControlVariableInContinuingBlock(loop,
loop_control_variable);
}
const Binary* IntegerRangeAnalysis::GetBinaryToCompareLoopControlVariableInLoopBodyForTest(
const Loop* loop,
const Var* loop_control_variable) {
return impl_->GetCompareInfoOfLoopControlVariableInLoopBody(loop, loop_control_variable).binary;
}
const IntegerRangeInfo* IntegerRangeAnalysis::GetInfo(const Var* var) {
return impl_->GetInfo(var);
}
const IntegerRangeInfo* IntegerRangeAnalysis::GetInfo(const Load* load) {
return impl_->GetInfo(load);
}
const IntegerRangeInfo* IntegerRangeAnalysis::GetInfo(const Access* access) {
return impl_->GetInfo(access);
}
} // namespace tint::core::ir::analysis