blob: f07d30ee587a6339e5133b5291140c27ab3f931c [file] [edit]
// Copyright 2023 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/transform/robustness.h"
#include <utility>
#include "src/tint/lang/core/ir/transform/helper_test.h"
#include "src/tint/lang/core/type/array.h"
#include "src/tint/lang/core/type/depth_multisampled_texture.h"
#include "src/tint/lang/core/type/depth_texture.h"
#include "src/tint/lang/core/type/external_texture.h"
#include "src/tint/lang/core/type/matrix.h"
#include "src/tint/lang/core/type/multisampled_texture.h"
#include "src/tint/lang/core/type/pointer.h"
#include "src/tint/lang/core/type/sampled_texture.h"
#include "src/tint/lang/core/type/storage_texture.h"
#include "src/tint/lang/core/type/struct.h"
#include "src/tint/lang/core/type/vector.h"
namespace tint::core::ir::transform {
namespace {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
// Tests for non-binding variables
using IR_RobustnessTest = TransformTestWithParam<bool>;
// Tests for binding variables
struct BindingVariableCase {
bool enabled;
bool ignore_bindings;
};
inline std::ostream& operator<<(std::ostream& out, BindingVariableCase c) {
return out << "enabled: " << c.enabled << ", ignore_bindings: " << c.ignore_bindings;
}
using IR_BindingVariableRobustnessTest = TransformTestWithParam<BindingVariableCase>;
using IR_RobustnessWithIntegerRangeAnalysisTest = TransformTest;
////////////////////////////////////////////////////////////////
// These tests use the function address space.
// Test clamping of vectors, matrices, and fixed-size arrays.
// Test indices that are const, const-via-let, and dynamic.
// Test signed vs unsigned indices.
////////////////////////////////////////////////////////////////
TEST_P(IR_RobustnessTest, VectorLoad_ConstIndex) {
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* vec = b.Var("vec", ty.ptr(function, ty.vec4<u32>()));
auto* load = b.LoadVectorElement(vec, b.Constant(5_u));
b.Return(func, load);
});
auto* src = R"(
%foo = func():u32 {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%3:u32 = load_vector_element %vec, 5u
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():u32 {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%3:u32 = load_vector_element %vec, 3u
ret %3
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, VectorLoad_ConstIndexViaLet) {
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* vec = b.Var("vec", ty.ptr(function, ty.vec4<u32>()));
auto* idx = b.Let("idx", b.Constant(5_u));
auto* load = b.LoadVectorElement(vec, idx);
b.Return(func, load);
});
auto* src = R"(
%foo = func():u32 {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%idx:u32 = let 5u
%4:u32 = load_vector_element %vec, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():u32 {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%idx:u32 = let 5u
%4:u32 = min %idx, 3u
%5:u32 = load_vector_element %vec, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, VectorLoad_DynamicIndex) {
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* vec = b.Var("vec", ty.ptr(function, ty.vec4<u32>()));
auto* load = b.LoadVectorElement(vec, idx);
b.Return(func, load);
});
auto* src = R"(
%foo = func(%idx:u32):u32 {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%4:u32 = load_vector_element %vec, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:u32):u32 {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%4:u32 = min %idx, 3u
%5:u32 = load_vector_element %vec, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, VectorLoad_DynamicIndex_Signed) {
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.i32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* vec = b.Var("vec", ty.ptr(function, ty.vec4<u32>()));
auto* load = b.LoadVectorElement(vec, idx);
b.Return(func, load);
});
auto* src = R"(
%foo = func(%idx:i32):u32 {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%4:u32 = load_vector_element %vec, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:i32):u32 {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%4:u32 = convert %idx
%5:u32 = min %4, 3u
%6:u32 = load_vector_element %vec, %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, VectorStore_ConstIndex) {
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
auto* vec = b.Var("vec", ty.ptr(function, ty.vec4<u32>()));
b.StoreVectorElement(vec, b.Constant(5_u), b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
%foo = func():void {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
store_vector_element %vec, 5u, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():void {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
store_vector_element %vec, 3u, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, VectorStore_ConstIndexViaLet) {
auto* func = b.Function("foo", ty.void_());
b.Append(func->Block(), [&] {
auto* vec = b.Var("vec", ty.ptr(function, ty.vec4<u32>()));
auto* idx = b.Let("idx", b.Constant(5_u));
b.StoreVectorElement(vec, idx, b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
%foo = func():void {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%idx:u32 = let 5u
store_vector_element %vec, %idx, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():void {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%idx:u32 = let 5u
%4:u32 = min %idx, 3u
store_vector_element %vec, %4, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, VectorStore_DynamicIndex) {
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* vec = b.Var("vec", ty.ptr(function, ty.vec4<u32>()));
b.StoreVectorElement(vec, idx, b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
%foo = func(%idx:u32):void {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
store_vector_element %vec, %idx, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:u32):void {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%4:u32 = min %idx, 3u
store_vector_element %vec, %4, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, VectorStore_DynamicIndex_Signed) {
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.i32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* vec = b.Var("vec", ty.ptr(function, ty.vec4<u32>()));
b.StoreVectorElement(vec, idx, b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
%foo = func(%idx:i32):void {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
store_vector_element %vec, %idx, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:i32):void {
$B1: {
%vec:ptr<function, vec4<u32>, read_write> = var undef
%4:u32 = convert %idx
%5:u32 = min %4, 3u
store_vector_element %vec, %5, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Matrix_ConstIndex) {
auto* func = b.Function("foo", ty.vec4<f32>());
b.Append(func->Block(), [&] {
auto* mat = b.Var("mat", ty.ptr(function, ty.mat4x4<f32>()));
auto* access = b.Access(ty.ptr(function, ty.vec4<f32>()), mat, b.Constant(2_u));
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func():vec4<f32> {
$B1: {
%mat:ptr<function, mat4x4<f32>, read_write> = var undef
%3:ptr<function, vec4<f32>, read_write> = access %mat, 2u
%4:vec4<f32> = load %3
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_P(IR_RobustnessTest, Matrix_ConstIndexViaLet) {
auto* func = b.Function("foo", ty.vec4<f32>());
b.Append(func->Block(), [&] {
auto* mat = b.Var("mat", ty.ptr(function, ty.mat4x4<f32>()));
auto* idx = b.Let("idx", b.Constant(2_u));
auto* access = b.Access(ty.ptr(function, ty.vec4<f32>()), mat, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func():vec4<f32> {
$B1: {
%mat:ptr<function, mat4x4<f32>, read_write> = var undef
%idx:u32 = let 2u
%4:ptr<function, vec4<f32>, read_write> = access %mat, %idx
%5:vec4<f32> = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():vec4<f32> {
$B1: {
%mat:ptr<function, mat4x4<f32>, read_write> = var undef
%idx:u32 = let 2u
%4:u32 = min %idx, 3u
%5:ptr<function, vec4<f32>, read_write> = access %mat, %4
%6:vec4<f32> = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Matrix_DynamicIndex) {
auto* func = b.Function("foo", ty.vec4<f32>());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* mat = b.Var("mat", ty.ptr(function, ty.mat4x4<f32>()));
auto* access = b.Access(ty.ptr(function, ty.vec4<f32>()), mat, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func(%idx:u32):vec4<f32> {
$B1: {
%mat:ptr<function, mat4x4<f32>, read_write> = var undef
%4:ptr<function, vec4<f32>, read_write> = access %mat, %idx
%5:vec4<f32> = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:u32):vec4<f32> {
$B1: {
%mat:ptr<function, mat4x4<f32>, read_write> = var undef
%4:u32 = min %idx, 3u
%5:ptr<function, vec4<f32>, read_write> = access %mat, %4
%6:vec4<f32> = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Matrix_DynamicIndex_Signed) {
auto* func = b.Function("foo", ty.vec4<f32>());
auto* idx = b.FunctionParam("idx", ty.i32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* mat = b.Var("mat", ty.ptr(function, ty.mat4x4<f32>()));
auto* access = b.Access(ty.ptr(function, ty.vec4<f32>()), mat, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func(%idx:i32):vec4<f32> {
$B1: {
%mat:ptr<function, mat4x4<f32>, read_write> = var undef
%4:ptr<function, vec4<f32>, read_write> = access %mat, %idx
%5:vec4<f32> = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:i32):vec4<f32> {
$B1: {
%mat:ptr<function, mat4x4<f32>, read_write> = var undef
%4:u32 = convert %idx
%5:u32 = min %4, 3u
%6:ptr<function, vec4<f32>, read_write> = access %mat, %5
%7:vec4<f32> = load %6
ret %7
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Array_ConstSize_ConstIndex) {
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 4>()));
auto* access = b.Access(ty.ptr<function, u32>(), arr, b.Constant(2_u));
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func():u32 {
$B1: {
%arr:ptr<function, array<u32, 4>, read_write> = var undef
%3:ptr<function, u32, read_write> = access %arr, 2u
%4:u32 = load %3
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_P(IR_RobustnessTest, Array_ConstSize_ConstIndexViaLet) {
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 4>()));
auto* idx = b.Let("idx", b.Constant(2_u));
auto* access = b.Access(ty.ptr<function, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func():u32 {
$B1: {
%arr:ptr<function, array<u32, 4>, read_write> = var undef
%idx:u32 = let 2u
%4:ptr<function, u32, read_write> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func():u32 {
$B1: {
%arr:ptr<function, array<u32, 4>, read_write> = var undef
%idx:u32 = let 2u
%4:u32 = min %idx, 3u
%5:ptr<function, u32, read_write> = access %arr, %4
%6:u32 = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Array_ConstSize_DynamicIndex) {
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 4>()));
auto* access = b.Access(ty.ptr<function, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func(%idx:u32):u32 {
$B1: {
%arr:ptr<function, array<u32, 4>, read_write> = var undef
%4:ptr<function, u32, read_write> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:u32):u32 {
$B1: {
%arr:ptr<function, array<u32, 4>, read_write> = var undef
%4:u32 = min %idx, 3u
%5:ptr<function, u32, read_write> = access %arr, %4
%6:u32 = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Array_ConstSize_DynamicIndex_Signed) {
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.i32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 4>()));
auto* access = b.Access(ty.ptr<function, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func(%idx:i32):u32 {
$B1: {
%arr:ptr<function, array<u32, 4>, read_write> = var undef
%4:ptr<function, u32, read_write> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:i32):u32 {
$B1: {
%arr:ptr<function, array<u32, 4>, read_write> = var undef
%4:u32 = convert %idx
%5:u32 = min %4, 3u
%6:ptr<function, u32, read_write> = access %arr, %5
%7:u32 = load %6
ret %7
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, NestedArrays) {
auto* func = b.Function("foo", ty.u32());
auto* idx1 = b.FunctionParam("idx1", ty.u32());
auto* idx2 = b.FunctionParam("idx2", ty.u32());
auto* idx3 = b.FunctionParam("idx3", ty.u32());
auto* idx4 = b.FunctionParam("idx4", ty.u32());
func->SetParams({idx1, idx2, idx3, idx4});
b.Append(func->Block(), [&] {
auto* arr = b.Var(
"arr", ty.ptr(function, ty.array(ty.array(ty.array(ty.array(ty.u32(), 4), 5), 6), 7)));
auto* access = b.Access(ty.ptr<function, u32>(), arr, idx1, idx2, idx3, idx4);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
%foo = func(%idx1:u32, %idx2:u32, %idx3:u32, %idx4:u32):u32 {
$B1: {
%arr:ptr<function, array<array<array<array<u32, 4>, 5>, 6>, 7>, read_write> = var undef
%7:ptr<function, u32, read_write> = access %arr, %idx1, %idx2, %idx3, %idx4
%8:u32 = load %7
ret %8
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx1:u32, %idx2:u32, %idx3:u32, %idx4:u32):u32 {
$B1: {
%arr:ptr<function, array<array<array<array<u32, 4>, 5>, 6>, 7>, read_write> = var undef
%7:u32 = min %idx1, 6u
%8:u32 = min %idx2, 5u
%9:u32 = min %idx3, 4u
%10:u32 = min %idx4, 3u
%11:ptr<function, u32, read_write> = access %arr, %7, %8, %9, %10
%12:u32 = load %11
ret %12
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, NestedMixedTypes) {
auto* structure = ty.Struct(mod.symbols.Register("structure"),
{
{mod.symbols.Register("arr"), ty.array(ty.mat3x4<f32>(), 4)},
});
auto* func = b.Function("foo", ty.vec4<f32>());
auto* idx1 = b.FunctionParam("idx1", ty.u32());
auto* idx2 = b.FunctionParam("idx2", ty.u32());
auto* idx3 = b.FunctionParam("idx3", ty.u32());
func->SetParams({idx1, idx2, idx3});
b.Append(func->Block(), [&] {
auto* arr = b.Var("arr", ty.ptr(function, ty.array(structure, 8)));
auto* access =
b.Access(ty.ptr<function, vec4<f32>>(), arr, idx1, b.Constant(0_u), idx2, idx3);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
structure = struct @align(16) {
arr:array<mat3x4<f32>, 4> @offset(0)
}
%foo = func(%idx1:u32, %idx2:u32, %idx3:u32):vec4<f32> {
$B1: {
%arr:ptr<function, array<structure, 8>, read_write> = var undef
%6:ptr<function, vec4<f32>, read_write> = access %arr, %idx1, 0u, %idx2, %idx3
%7:vec4<f32> = load %6
ret %7
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
structure = struct @align(16) {
arr:array<mat3x4<f32>, 4> @offset(0)
}
%foo = func(%idx1:u32, %idx2:u32, %idx3:u32):vec4<f32> {
$B1: {
%arr:ptr<function, array<structure, 8>, read_write> = var undef
%6:u32 = min %idx1, 7u
%7:u32 = min %idx2, 3u
%8:u32 = min %idx3, 2u
%9:ptr<function, vec4<f32>, read_write> = access %arr, %6, 0u, %7, %8
%10:vec4<f32> = load %9
ret %10
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
////////////////////////////////////////////////////////////////
// Test the clamp toggles for every other address space.
////////////////////////////////////////////////////////////////
TEST_P(IR_RobustnessTest, Private_LoadVectorElement) {
auto* vec = b.Var("vec", ty.ptr(private_, ty.vec4<u32>()));
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* load = b.LoadVectorElement(vec, idx);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%vec:ptr<private, vec4<u32>, read_write> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = load_vector_element %vec, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<private, vec4<u32>, read_write> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:u32 = load_vector_element %vec, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.clamp_private = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Private_StoreVectorElement) {
auto* vec = b.Var("vec", ty.ptr(private_, ty.vec4<u32>()));
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
b.StoreVectorElement(vec, idx, b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%vec:ptr<private, vec4<u32>, read_write> = var undef
}
%foo = func(%idx:u32):void {
$B2: {
store_vector_element %vec, %idx, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<private, vec4<u32>, read_write> = var undef
}
%foo = func(%idx:u32):void {
$B2: {
%4:u32 = min %idx, 3u
store_vector_element %vec, %4, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_private = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Private_Access) {
auto* arr = b.Var("arr", ty.ptr(private_, ty.array<u32, 4>()));
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<private_, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<private, array<u32, 4>, read_write> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<private, u32, read_write> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%arr:ptr<private, array<u32, 4>, read_write> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:ptr<private, u32, read_write> = access %arr, %4
%6:u32 = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_private = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, ImmediateData_LoadVectorElement) {
auto* vec = b.Var("vec", ty.ptr(immediate, ty.vec4<u32>()));
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* load = b.LoadVectorElement(vec, idx);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%vec:ptr<immediate, vec4<u32>, read> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = load_vector_element %vec, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<immediate, vec4<u32>, read> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:u32 = load_vector_element %vec, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.clamp_immediate_data = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, ImmediateData_StoreVectorElement) {
auto* vec = b.Var("vec", ty.ptr(immediate, ty.vec4<u32>()));
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
b.StoreVectorElement(vec, idx, b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%vec:ptr<immediate, vec4<u32>, read> = var undef
}
%foo = func(%idx:u32):void {
$B2: {
store_vector_element %vec, %idx, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<immediate, vec4<u32>, read> = var undef
}
%foo = func(%idx:u32):void {
$B2: {
%4:u32 = min %idx, 3u
store_vector_element %vec, %4, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_immediate_data = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, ImmediateData_Access) {
auto* arr = b.Var("arr", ty.ptr(immediate, ty.array<u32, 4>()));
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<immediate, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<immediate, array<u32, 4>, read> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<immediate, u32, read> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%arr:ptr<immediate, array<u32, 4>, read> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:ptr<immediate, u32, read> = access %arr, %4
%6:u32 = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_immediate_data = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, Storage_LoadVectorElement) {
auto* vec = b.Var("vec", ty.ptr(storage, ty.vec4<u32>()));
vec->SetBindingPoint(0, 0);
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* load = b.LoadVectorElement(vec, idx);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%vec:ptr<storage, vec4<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = load_vector_element %vec, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<storage, vec4<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:u32 = load_vector_element %vec, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, Storage_StoreVectorElement) {
auto* vec = b.Var("vec", ty.ptr(storage, ty.vec4<u32>()));
vec->SetBindingPoint(0, 0);
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
b.StoreVectorElement(vec, idx, b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%vec:ptr<storage, vec4<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):void {
$B2: {
store_vector_element %vec, %idx, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<storage, vec4<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):void {
$B2: {
%4:u32 = min %idx, 3u
store_vector_element %vec, %4, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, Storage_Access) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<u32, 4>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<u32, 4>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%arr:ptr<storage, array<u32, 4>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:ptr<storage, u32, read_write> = access %arr, %4
%6:u32 = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, Unifom_LoadVectorElement) {
auto* vec = b.Var("vec", ty.ptr(uniform, ty.vec4<u32>()));
vec->SetBindingPoint(0, 0);
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* load = b.LoadVectorElement(vec, idx);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%vec:ptr<uniform, vec4<u32>, read> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = load_vector_element %vec, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<uniform, vec4<u32>, read> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:u32 = load_vector_element %vec, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.clamp_uniform = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, Unifom_StoreVectorElement) {
auto* vec = b.Var("vec", ty.ptr(uniform, ty.vec4<u32>()));
vec->SetBindingPoint(0, 0);
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
b.StoreVectorElement(vec, idx, b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%vec:ptr<uniform, vec4<u32>, read> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):void {
$B2: {
store_vector_element %vec, %idx, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<uniform, vec4<u32>, read> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):void {
$B2: {
%4:u32 = min %idx, 3u
store_vector_element %vec, %4, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_uniform = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, Uniform_Access) {
auto* arr = b.Var("arr", ty.ptr(uniform, ty.array<u32, 4>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<uniform, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<uniform, array<u32, 4>, read> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<uniform, u32, read> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%arr:ptr<uniform, array<u32, 4>, read> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:ptr<uniform, u32, read> = access %arr, %4
%6:u32 = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_uniform = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Workgroup_LoadVectorElement) {
auto* vec = b.Var("vec", ty.ptr(workgroup, ty.vec4<u32>()));
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* load = b.LoadVectorElement(vec, idx);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%vec:ptr<workgroup, vec4<u32>, read_write> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = load_vector_element %vec, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<workgroup, vec4<u32>, read_write> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:u32 = load_vector_element %vec, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.clamp_workgroup = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Workgroup_StoreVectorElement) {
auto* vec = b.Var("vec", ty.ptr(workgroup, ty.vec4<u32>()));
mod.root_block->Append(vec);
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
b.StoreVectorElement(vec, idx, b.Constant(0_u));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%vec:ptr<workgroup, vec4<u32>, read_write> = var undef
}
%foo = func(%idx:u32):void {
$B2: {
store_vector_element %vec, %idx, 0u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec:ptr<workgroup, vec4<u32>, read_write> = var undef
}
%foo = func(%idx:u32):void {
$B2: {
%4:u32 = min %idx, 3u
store_vector_element %vec, %4, 0u
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_workgroup = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, Workgroup_Access) {
auto* arr = b.Var("arr", ty.ptr(workgroup, ty.array<u32, 4>()));
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<workgroup, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<workgroup, array<u32, 4>, read_write> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<workgroup, u32, read_write> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%arr:ptr<workgroup, array<u32, 4>, read_write> = var undef
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = min %idx, 3u
%5:ptr<workgroup, u32, read_write> = access %arr, %4
%6:u32 = load %5
ret %6
}
}
)";
RobustnessConfig cfg;
cfg.clamp_workgroup = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
////////////////////////////////////////////////////////////////
// Test clamping non-pointer values.
////////////////////////////////////////////////////////////////
TEST_P(IR_RobustnessTest, ConstantVector_DynamicIndex) {
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* vec = mod.constant_values.Composite(ty.vec4<u32>(), Vector{
mod.constant_values.Get(1_u),
mod.constant_values.Get(2_u),
mod.constant_values.Get(3_u),
mod.constant_values.Get(4_u),
});
auto* element = b.Access(ty.u32(), b.Constant(vec), idx);
b.Return(func, element);
});
auto* src = R"(
%foo = func(%idx:u32):u32 {
$B1: {
%3:u32 = access vec4<u32>(1u, 2u, 3u, 4u), %idx
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:u32):u32 {
$B1: {
%3:u32 = min %idx, 3u
%4:u32 = access vec4<u32>(1u, 2u, 3u, 4u), %3
ret %4
}
}
)";
RobustnessConfig cfg;
cfg.clamp_value = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, ConstantArray_DynamicIndex) {
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* arr =
mod.constant_values.Composite(ty.array<u32, 4>(), Vector{
mod.constant_values.Get(1_u),
mod.constant_values.Get(2_u),
mod.constant_values.Get(3_u),
mod.constant_values.Get(4_u),
});
auto* element = b.Access(ty.u32(), b.Constant(arr), idx);
b.Return(func, element);
});
auto* src = R"(
%foo = func(%idx:u32):u32 {
$B1: {
%3:u32 = access array<u32, 4>(1u, 2u, 3u, 4u), %idx
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%idx:u32):u32 {
$B1: {
%3:u32 = min %idx, 3u
%4:u32 = access array<u32, 4>(1u, 2u, 3u, 4u), %3
ret %4
}
}
)";
RobustnessConfig cfg;
cfg.clamp_value = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
TEST_P(IR_RobustnessTest, ParamValueArray_DynamicIndex) {
auto* func = b.Function("foo", ty.u32());
auto* arr = b.FunctionParam("arr", ty.array<u32, 4>());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({arr, idx});
b.Append(func->Block(), [&] {
auto* element = b.Access(ty.u32(), arr, idx);
b.Return(func, element);
});
auto* src = R"(
%foo = func(%arr:array<u32, 4>, %idx:u32):u32 {
$B1: {
%4:u32 = access %arr, %idx
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
%foo = func(%arr:array<u32, 4>, %idx:u32):u32 {
$B1: {
%4:u32 = min %idx, 3u
%5:u32 = access %arr, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.clamp_value = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
////////////////////////////////////////////////////////////////
// Test clamping runtime-sized arrays.
////////////////////////////////////////////////////////////////
TEST_P(IR_BindingVariableRobustnessTest, RuntimeSizedArray_ConstIndex) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<u32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage, u32>(), arr, b.Constant(42_u));
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func():u32 {
$B2: {
%3:ptr<storage, u32, read_write> = access %arr, 42u
%4:u32 = load %3
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%arr:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func():u32 {
$B2: {
%3:u32 = arrayLength %arr
%4:u32 = sub %3, 1u
%5:u32 = min 42u, %4
%6:ptr<storage, u32, read_write> = access %arr, %5
%7:u32 = load %6
ret %7
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, RuntimeSizedArray_DynamicIndex) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<u32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%arr:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = arrayLength %arr
%5:u32 = sub %4, 1u
%6:u32 = min %idx, %5
%7:ptr<storage, u32, read_write> = access %arr, %6
%8:u32 = load %7
ret %8
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, RuntimeSizedArray_InStruct_ConstIndex) {
auto* structure = ty.Struct(mod.symbols.Register("structure"),
{
{mod.symbols.Register("arr"), ty.array<u32>()},
});
auto* buffer = b.Var("buffer", ty.ptr(storage, structure));
buffer->SetBindingPoint(0, 0);
mod.root_block->Append(buffer);
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage, u32>(), buffer, b.Constant(0_u), b.Constant(42_u));
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
structure = struct @align(4) {
arr:array<u32> @offset(0)
}
$B1: { # root
%buffer:ptr<storage, structure, read_write> = var undef @binding_point(0, 0)
}
%foo = func():u32 {
$B2: {
%3:ptr<storage, u32, read_write> = access %buffer, 0u, 42u
%4:u32 = load %3
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
structure = struct @align(4) {
arr:array<u32> @offset(0)
}
$B1: { # root
%buffer:ptr<storage, structure, read_write> = var undef @binding_point(0, 0)
}
%foo = func():u32 {
$B2: {
%3:ptr<storage, array<u32>, read_write> = access %buffer, 0u
%4:u32 = arrayLength %3
%5:u32 = sub %4, 1u
%6:u32 = min 42u, %5
%7:ptr<storage, u32, read_write> = access %buffer, 0u, %6
%8:u32 = load %7
ret %8
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, RuntimeSizedArray_InStruct_DynamicIndex) {
auto* structure = ty.Struct(mod.symbols.Register("structure"),
{
{mod.symbols.Register("arr"), ty.array<u32>()},
});
auto* buffer = b.Var("buffer", ty.ptr(storage, structure));
buffer->SetBindingPoint(0, 0);
mod.root_block->Append(buffer);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage, u32>(), buffer, b.Constant(0_u), idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
structure = struct @align(4) {
arr:array<u32> @offset(0)
}
$B1: { # root
%buffer:ptr<storage, structure, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %buffer, 0u, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
structure = struct @align(4) {
arr:array<u32> @offset(0)
}
$B1: { # root
%buffer:ptr<storage, structure, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<storage, array<u32>, read_write> = access %buffer, 0u
%5:u32 = arrayLength %4
%6:u32 = sub %5, 1u
%7:u32 = min %idx, %6
%8:ptr<storage, u32, read_write> = access %buffer, 0u, %7
%9:u32 = load %8
ret %9
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, RuntimeSizedArray_DisableClamping) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<u32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.u32());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage, u32>(), arr, idx);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %arr, %idx
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%arr:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):u32 {
$B2: {
%4:u32 = arrayLength %arr
%5:u32 = sub %4, 1u
%6:u32 = min %idx, %5
%7:ptr<storage, u32, read_write> = access %arr, %6
%8:u32 = load %7
ret %8
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
////////////////////////////////////////////////////////////////
// Test clamping texture builtin calls.
////////////////////////////////////////////////////////////////
TEST_P(IR_BindingVariableRobustnessTest, TextureDimensions) {
auto* texture = b.Var(
"texture", ty.ptr(handle, ty.sampled_texture(type::TextureDimension::k2d, ty.f32()), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
auto* func = b.Function("foo", ty.vec2<u32>());
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* dims = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, handle);
b.Return(func, dims);
});
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(0, 0)
}
%foo = func():vec2<u32> {
$B2: {
%3:texture_2d<f32> = load %texture
%4:vec2<u32> = textureDimensions %3
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureDimensions_WithLevel) {
auto* texture = b.Var(
"texture", ty.ptr(handle, ty.sampled_texture(type::TextureDimension::k2d, ty.f32()), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
auto* func = b.Function("foo", ty.vec2<u32>());
auto* level = b.FunctionParam("level", ty.u32());
func->SetParams({level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* dims = b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, handle, level);
b.Return(func, dims);
});
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(0, 0)
}
%foo = func(%level:u32):vec2<u32> {
$B2: {
%4:texture_2d<f32> = load %texture
%5:vec2<u32> = textureDimensions %4, %level
ret %5
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(0, 0)
}
%foo = func(%level:u32):vec2<u32> {
$B2: {
%4:texture_2d<f32> = load %texture
%5:u32 = textureNumLevels %4
%6:u32 = sub %5, 1u
%7:u32 = min %level, %6
%8:vec2<u32> = textureDimensions %4, %7
ret %8
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Sampled1D) {
auto* texture = b.Var(
"texture", ty.ptr(handle, ty.sampled_texture(type::TextureDimension::k1d, ty.f32()), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.i32());
auto* level = b.FunctionParam("level", ty.i32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.u32());
auto* level = b.FunctionParam("level", ty.u32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_1d<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:i32, %level:i32):vec4<f32> {
$B2: {
%5:texture_1d<f32> = load %texture
%6:vec4<f32> = textureLoad %5, %coords, %level
ret %6
}
}
%load_unsigned = func(%coords_1:u32, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%10:texture_1d<f32> = load %texture
%11:vec4<f32> = textureLoad %10, %coords_1, %level_1
ret %11
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_1d<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:i32, %level:i32):vec4<f32> {
$B2: {
%5:texture_1d<f32> = load %texture
%6:u32 = textureNumLevels %5
%7:u32 = sub %6, 1u
%8:u32 = convert %level
%9:u32 = min %8, %7
%10:u32 = textureDimensions %5, %9
%11:u32 = sub %10, 1u
%12:u32 = convert %coords
%13:u32 = min %12, %11
%14:vec4<f32> = textureLoad %5, %13, %9
ret %14
}
}
%load_unsigned = func(%coords_1:u32, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%18:texture_1d<f32> = load %texture
%19:u32 = textureNumLevels %18
%20:u32 = sub %19, 1u
%21:u32 = min %level_1, %20
%22:u32 = textureDimensions %18, %21
%23:u32 = sub %22, 1u
%24:u32 = min %coords_1, %23
%25:vec4<f32> = textureLoad %18, %24, %21
ret %25
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Sampled2D) {
auto* texture = b.Var(
"texture", ty.ptr(handle, ty.sampled_texture(type::TextureDimension::k2d, ty.f32()), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
auto* level = b.FunctionParam("level", ty.i32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
auto* level = b.FunctionParam("level", ty.u32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %level:i32):vec4<f32> {
$B2: {
%5:texture_2d<f32> = load %texture
%6:vec4<f32> = textureLoad %5, %coords, %level
ret %6
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%10:texture_2d<f32> = load %texture
%11:vec4<f32> = textureLoad %10, %coords_1, %level_1
ret %11
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_2d<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %level:i32):vec4<f32> {
$B2: {
%5:texture_2d<f32> = load %texture
%6:u32 = textureNumLevels %5
%7:u32 = sub %6, 1u
%8:u32 = convert %level
%9:u32 = min %8, %7
%10:vec2<u32> = textureDimensions %5, %9
%11:vec2<u32> = sub %10, vec2<u32>(1u)
%12:vec2<u32> = convert %coords
%13:vec2<u32> = min %12, %11
%14:vec4<f32> = textureLoad %5, %13, %9
ret %14
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%18:texture_2d<f32> = load %texture
%19:u32 = textureNumLevels %18
%20:u32 = sub %19, 1u
%21:u32 = min %level_1, %20
%22:vec2<u32> = textureDimensions %18, %21
%23:vec2<u32> = sub %22, vec2<u32>(1u)
%24:vec2<u32> = min %coords_1, %23
%25:vec4<f32> = textureLoad %18, %24, %21
ret %25
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Sampled2DArray) {
auto* texture =
b.Var("texture",
ty.ptr(handle, ty.sampled_texture(type::TextureDimension::k2dArray, ty.f32()), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
auto* layer = b.FunctionParam("layer", ty.i32());
auto* level = b.FunctionParam("level", ty.i32());
func->SetParams({coords, layer, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, layer, level);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
auto* layer = b.FunctionParam("layer", ty.u32());
auto* level = b.FunctionParam("level", ty.u32());
func->SetParams({coords, layer, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, layer, level);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_2d_array<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %layer:i32, %level:i32):vec4<f32> {
$B2: {
%6:texture_2d_array<f32> = load %texture
%7:vec4<f32> = textureLoad %6, %coords, %layer, %level
ret %7
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %layer_1: 'layer', %level_1: 'level'
$B3: {
%12:texture_2d_array<f32> = load %texture
%13:vec4<f32> = textureLoad %12, %coords_1, %layer_1, %level_1
ret %13
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_2d_array<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %layer:i32, %level:i32):vec4<f32> {
$B2: {
%6:texture_2d_array<f32> = load %texture
%7:u32 = textureNumLayers %6
%8:u32 = sub %7, 1u
%9:u32 = convert %layer
%10:u32 = min %9, %8
%11:u32 = textureNumLevels %6
%12:u32 = sub %11, 1u
%13:u32 = convert %level
%14:u32 = min %13, %12
%15:vec2<u32> = textureDimensions %6, %14
%16:vec2<u32> = sub %15, vec2<u32>(1u)
%17:vec2<u32> = convert %coords
%18:vec2<u32> = min %17, %16
%19:vec4<f32> = textureLoad %6, %18, %10, %14
ret %19
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %layer_1: 'layer', %level_1: 'level'
$B3: {
%24:texture_2d_array<f32> = load %texture
%25:u32 = textureNumLayers %24
%26:u32 = sub %25, 1u
%27:u32 = min %layer_1, %26
%28:u32 = textureNumLevels %24
%29:u32 = sub %28, 1u
%30:u32 = min %level_1, %29
%31:vec2<u32> = textureDimensions %24, %30
%32:vec2<u32> = sub %31, vec2<u32>(1u)
%33:vec2<u32> = min %coords_1, %32
%34:vec4<f32> = textureLoad %24, %33, %27, %30
ret %34
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Sampled3D) {
auto* texture = b.Var(
"texture", ty.ptr(handle, ty.sampled_texture(type::TextureDimension::k3d, ty.f32()), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec3<i32>());
auto* level = b.FunctionParam("level", ty.i32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec3<u32>());
auto* level = b.FunctionParam("level", ty.u32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_3d<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec3<i32>, %level:i32):vec4<f32> {
$B2: {
%5:texture_3d<f32> = load %texture
%6:vec4<f32> = textureLoad %5, %coords, %level
ret %6
}
}
%load_unsigned = func(%coords_1:vec3<u32>, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%10:texture_3d<f32> = load %texture
%11:vec4<f32> = textureLoad %10, %coords_1, %level_1
ret %11
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_3d<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec3<i32>, %level:i32):vec4<f32> {
$B2: {
%5:texture_3d<f32> = load %texture
%6:u32 = textureNumLevels %5
%7:u32 = sub %6, 1u
%8:u32 = convert %level
%9:u32 = min %8, %7
%10:vec3<u32> = textureDimensions %5, %9
%11:vec3<u32> = sub %10, vec3<u32>(1u)
%12:vec3<u32> = convert %coords
%13:vec3<u32> = min %12, %11
%14:vec4<f32> = textureLoad %5, %13, %9
ret %14
}
}
%load_unsigned = func(%coords_1:vec3<u32>, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%18:texture_3d<f32> = load %texture
%19:u32 = textureNumLevels %18
%20:u32 = sub %19, 1u
%21:u32 = min %level_1, %20
%22:vec3<u32> = textureDimensions %18, %21
%23:vec3<u32> = sub %22, vec3<u32>(1u)
%24:vec3<u32> = min %coords_1, %23
%25:vec4<f32> = textureLoad %18, %24, %21
ret %25
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Multisampled2D) {
auto* texture =
b.Var("texture",
ty.ptr(handle, ty.multisampled_texture(type::TextureDimension::k2d, ty.f32()), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
auto* level = b.FunctionParam("level", ty.i32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
auto* level = b.FunctionParam("level", ty.u32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_multisampled_2d<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %level:i32):vec4<f32> {
$B2: {
%5:texture_multisampled_2d<f32> = load %texture
%6:vec4<f32> = textureLoad %5, %coords, %level
ret %6
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%10:texture_multisampled_2d<f32> = load %texture
%11:vec4<f32> = textureLoad %10, %coords_1, %level_1
ret %11
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_multisampled_2d<f32>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %level:i32):vec4<f32> {
$B2: {
%5:texture_multisampled_2d<f32> = load %texture
%6:vec2<u32> = textureDimensions %5
%7:vec2<u32> = sub %6, vec2<u32>(1u)
%8:vec2<u32> = convert %coords
%9:vec2<u32> = min %8, %7
%10:vec4<f32> = textureLoad %5, %9, %level
ret %10
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):vec4<f32> { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%14:texture_multisampled_2d<f32> = load %texture
%15:vec2<u32> = textureDimensions %14
%16:vec2<u32> = sub %15, vec2<u32>(1u)
%17:vec2<u32> = min %coords_1, %16
%18:vec4<f32> = textureLoad %14, %17, %level_1
ret %18
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Depth2D) {
auto* texture =
b.Var("texture", ty.ptr(handle, ty.depth_texture(type::TextureDimension::k2d), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.f32());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
auto* level = b.FunctionParam("level", ty.i32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.f32());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
auto* level = b.FunctionParam("level", ty.u32());
func->SetParams({coords, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, level);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_depth_2d, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %level:i32):f32 {
$B2: {
%5:texture_depth_2d = load %texture
%6:f32 = textureLoad %5, %coords, %level
ret %6
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):f32 { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%10:texture_depth_2d = load %texture
%11:f32 = textureLoad %10, %coords_1, %level_1
ret %11
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_depth_2d, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %level:i32):f32 {
$B2: {
%5:texture_depth_2d = load %texture
%6:u32 = textureNumLevels %5
%7:u32 = sub %6, 1u
%8:u32 = convert %level
%9:u32 = min %8, %7
%10:vec2<u32> = textureDimensions %5, %9
%11:vec2<u32> = sub %10, vec2<u32>(1u)
%12:vec2<u32> = convert %coords
%13:vec2<u32> = min %12, %11
%14:f32 = textureLoad %5, %13, %9
ret %14
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %level_1:u32):f32 { # %coords_1: 'coords', %level_1: 'level'
$B3: {
%18:texture_depth_2d = load %texture
%19:u32 = textureNumLevels %18
%20:u32 = sub %19, 1u
%21:u32 = min %level_1, %20
%22:vec2<u32> = textureDimensions %18, %21
%23:vec2<u32> = sub %22, vec2<u32>(1u)
%24:vec2<u32> = min %coords_1, %23
%25:f32 = textureLoad %18, %24, %21
ret %25
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Depth2DArray) {
auto* texture =
b.Var("texture", ty.ptr(handle, ty.depth_texture(type::TextureDimension::k2dArray), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.f32());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
auto* layer = b.FunctionParam("layer", ty.i32());
auto* level = b.FunctionParam("level", ty.i32());
func->SetParams({coords, layer, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, layer, level);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.f32());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
auto* layer = b.FunctionParam("layer", ty.u32());
auto* level = b.FunctionParam("level", ty.u32());
func->SetParams({coords, layer, level});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, layer, level);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_depth_2d_array, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %layer:i32, %level:i32):f32 {
$B2: {
%6:texture_depth_2d_array = load %texture
%7:f32 = textureLoad %6, %coords, %layer, %level
ret %7
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %level_1:u32):f32 { # %coords_1: 'coords', %layer_1: 'layer', %level_1: 'level'
$B3: {
%12:texture_depth_2d_array = load %texture
%13:f32 = textureLoad %12, %coords_1, %layer_1, %level_1
ret %13
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_depth_2d_array, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %layer:i32, %level:i32):f32 {
$B2: {
%6:texture_depth_2d_array = load %texture
%7:u32 = textureNumLayers %6
%8:u32 = sub %7, 1u
%9:u32 = convert %layer
%10:u32 = min %9, %8
%11:u32 = textureNumLevels %6
%12:u32 = sub %11, 1u
%13:u32 = convert %level
%14:u32 = min %13, %12
%15:vec2<u32> = textureDimensions %6, %14
%16:vec2<u32> = sub %15, vec2<u32>(1u)
%17:vec2<u32> = convert %coords
%18:vec2<u32> = min %17, %16
%19:f32 = textureLoad %6, %18, %10, %14
ret %19
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32, %level_1:u32):f32 { # %coords_1: 'coords', %layer_1: 'layer', %level_1: 'level'
$B3: {
%24:texture_depth_2d_array = load %texture
%25:u32 = textureNumLayers %24
%26:u32 = sub %25, 1u
%27:u32 = min %layer_1, %26
%28:u32 = textureNumLevels %24
%29:u32 = sub %28, 1u
%30:u32 = min %level_1, %29
%31:vec2<u32> = textureDimensions %24, %30
%32:vec2<u32> = sub %31, vec2<u32>(1u)
%33:vec2<u32> = min %coords_1, %32
%34:f32 = textureLoad %24, %33, %27, %30
ret %34
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_DepthMultisampled2D) {
auto* texture =
b.Var("texture",
ty.ptr(handle, ty.depth_multisampled_texture(type::TextureDimension::k2d), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.f32());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
auto* index = b.FunctionParam("index", ty.i32());
func->SetParams({coords, index});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, index);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.f32());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
auto* index = b.FunctionParam("index", ty.u32());
func->SetParams({coords, index});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.f32(), core::BuiltinFn::kTextureLoad, handle, coords, index);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_depth_multisampled_2d, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %index:i32):f32 {
$B2: {
%5:texture_depth_multisampled_2d = load %texture
%6:f32 = textureLoad %5, %coords, %index
ret %6
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %index_1:u32):f32 { # %coords_1: 'coords', %index_1: 'index'
$B3: {
%10:texture_depth_multisampled_2d = load %texture
%11:f32 = textureLoad %10, %coords_1, %index_1
ret %11
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_depth_multisampled_2d, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %index:i32):f32 {
$B2: {
%5:texture_depth_multisampled_2d = load %texture
%6:vec2<u32> = textureDimensions %5
%7:vec2<u32> = sub %6, vec2<u32>(1u)
%8:vec2<u32> = convert %coords
%9:vec2<u32> = min %8, %7
%10:f32 = textureLoad %5, %9, %index
ret %10
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %index_1:u32):f32 { # %coords_1: 'coords', %index_1: 'index'
$B3: {
%14:texture_depth_multisampled_2d = load %texture
%15:vec2<u32> = textureDimensions %14
%16:vec2<u32> = sub %15, vec2<u32>(1u)
%17:vec2<u32> = min %coords_1, %16
%18:f32 = textureLoad %14, %17, %index_1
ret %18
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_External) {
auto* texture = b.Var("texture", ty.ptr(handle, ty.external_texture(), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
func->SetParams({coords});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
func->SetParams({coords});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_external, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>):vec4<f32> {
$B2: {
%4:texture_external = load %texture
%5:vec4<f32> = textureLoad %4, %coords
ret %5
}
}
%load_unsigned = func(%coords_1:vec2<u32>):vec4<f32> { # %coords_1: 'coords'
$B3: {
%8:texture_external = load %texture
%9:vec4<f32> = textureLoad %8, %coords_1
ret %9
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_external, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>):vec4<f32> {
$B2: {
%4:texture_external = load %texture
%5:vec2<u32> = textureDimensions %4
%6:vec2<u32> = sub %5, vec2<u32>(1u)
%7:vec2<u32> = convert %coords
%8:vec2<u32> = min %7, %6
%9:vec4<f32> = textureLoad %4, %8
ret %9
}
}
%load_unsigned = func(%coords_1:vec2<u32>):vec4<f32> { # %coords_1: 'coords'
$B3: {
%12:texture_external = load %texture
%13:vec2<u32> = textureDimensions %12
%14:vec2<u32> = sub %13, vec2<u32>(1u)
%15:vec2<u32> = min %coords_1, %14
%16:vec4<f32> = textureLoad %12, %15
ret %16
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Storage1D) {
auto format = core::TexelFormat::kRgba8Unorm;
auto* texture = b.Var(
"texture",
ty.ptr(handle, ty.storage_texture(type::TextureDimension::k1d, format, read_write), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.i32());
func->SetParams({coords});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.u32());
func->SetParams({coords});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_1d<rgba8unorm, read_write>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:i32):vec4<f32> {
$B2: {
%4:texture_storage_1d<rgba8unorm, read_write> = load %texture
%5:vec4<f32> = textureLoad %4, %coords
ret %5
}
}
%load_unsigned = func(%coords_1:u32):vec4<f32> { # %coords_1: 'coords'
$B3: {
%8:texture_storage_1d<rgba8unorm, read_write> = load %texture
%9:vec4<f32> = textureLoad %8, %coords_1
ret %9
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_1d<rgba8unorm, read_write>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:i32):vec4<f32> {
$B2: {
%4:texture_storage_1d<rgba8unorm, read_write> = load %texture
%5:u32 = textureDimensions %4
%6:u32 = sub %5, 1u
%7:u32 = convert %coords
%8:u32 = min %7, %6
%9:vec4<f32> = textureLoad %4, %8
ret %9
}
}
%load_unsigned = func(%coords_1:u32):vec4<f32> { # %coords_1: 'coords'
$B3: {
%12:texture_storage_1d<rgba8unorm, read_write> = load %texture
%13:u32 = textureDimensions %12
%14:u32 = sub %13, 1u
%15:u32 = min %coords_1, %14
%16:vec4<f32> = textureLoad %12, %15
ret %16
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Storage2D) {
auto format = core::TexelFormat::kRgba8Unorm;
auto* texture = b.Var(
"texture",
ty.ptr(handle, ty.storage_texture(type::TextureDimension::k2d, format, read_write), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
func->SetParams({coords});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
func->SetParams({coords});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_2d<rgba8unorm, read_write>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>):vec4<f32> {
$B2: {
%4:texture_storage_2d<rgba8unorm, read_write> = load %texture
%5:vec4<f32> = textureLoad %4, %coords
ret %5
}
}
%load_unsigned = func(%coords_1:vec2<u32>):vec4<f32> { # %coords_1: 'coords'
$B3: {
%8:texture_storage_2d<rgba8unorm, read_write> = load %texture
%9:vec4<f32> = textureLoad %8, %coords_1
ret %9
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_2d<rgba8unorm, read_write>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>):vec4<f32> {
$B2: {
%4:texture_storage_2d<rgba8unorm, read_write> = load %texture
%5:vec2<u32> = textureDimensions %4
%6:vec2<u32> = sub %5, vec2<u32>(1u)
%7:vec2<u32> = convert %coords
%8:vec2<u32> = min %7, %6
%9:vec4<f32> = textureLoad %4, %8
ret %9
}
}
%load_unsigned = func(%coords_1:vec2<u32>):vec4<f32> { # %coords_1: 'coords'
$B3: {
%12:texture_storage_2d<rgba8unorm, read_write> = load %texture
%13:vec2<u32> = textureDimensions %12
%14:vec2<u32> = sub %13, vec2<u32>(1u)
%15:vec2<u32> = min %coords_1, %14
%16:vec4<f32> = textureLoad %12, %15
ret %16
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Storage2DArray) {
auto format = core::TexelFormat::kRgba8Unorm;
auto* texture = b.Var(
"texture",
ty.ptr(handle, ty.storage_texture(type::TextureDimension::k2dArray, format, read_write),
read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
auto* layer = b.FunctionParam("layer", ty.i32());
func->SetParams({coords, layer});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, layer);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
auto* layer = b.FunctionParam("layer", ty.u32());
func->SetParams({coords, layer});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel =
b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords, layer);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_2d_array<rgba8unorm, read_write>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %layer:i32):vec4<f32> {
$B2: {
%5:texture_storage_2d_array<rgba8unorm, read_write> = load %texture
%6:vec4<f32> = textureLoad %5, %coords, %layer
ret %6
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32):vec4<f32> { # %coords_1: 'coords', %layer_1: 'layer'
$B3: {
%10:texture_storage_2d_array<rgba8unorm, read_write> = load %texture
%11:vec4<f32> = textureLoad %10, %coords_1, %layer_1
ret %11
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_2d_array<rgba8unorm, read_write>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec2<i32>, %layer:i32):vec4<f32> {
$B2: {
%5:texture_storage_2d_array<rgba8unorm, read_write> = load %texture
%6:u32 = textureNumLayers %5
%7:u32 = sub %6, 1u
%8:u32 = convert %layer
%9:u32 = min %8, %7
%10:vec2<u32> = textureDimensions %5
%11:vec2<u32> = sub %10, vec2<u32>(1u)
%12:vec2<u32> = convert %coords
%13:vec2<u32> = min %12, %11
%14:vec4<f32> = textureLoad %5, %13, %9
ret %14
}
}
%load_unsigned = func(%coords_1:vec2<u32>, %layer_1:u32):vec4<f32> { # %coords_1: 'coords', %layer_1: 'layer'
$B3: {
%18:texture_storage_2d_array<rgba8unorm, read_write> = load %texture
%19:u32 = textureNumLayers %18
%20:u32 = sub %19, 1u
%21:u32 = min %layer_1, %20
%22:vec2<u32> = textureDimensions %18
%23:vec2<u32> = sub %22, vec2<u32>(1u)
%24:vec2<u32> = min %coords_1, %23
%25:vec4<f32> = textureLoad %18, %24, %21
ret %25
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
TEST_P(IR_BindingVariableRobustnessTest, TextureLoad_Storage3D) {
auto format = core::TexelFormat::kRgba8Unorm;
auto* texture = b.Var(
"texture",
ty.ptr(handle, ty.storage_texture(type::TextureDimension::k3d, format, read_write), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
{
auto* func = b.Function("load_signed", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec3<i32>());
func->SetParams({coords});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
b.Return(func, texel);
});
}
{
auto* func = b.Function("load_unsigned", ty.vec4<f32>());
auto* coords = b.FunctionParam("coords", ty.vec3<u32>());
func->SetParams({coords});
b.Append(func->Block(), [&] {
auto* handle = b.Load(texture);
auto* texel = b.Call(ty.vec4<f32>(), core::BuiltinFn::kTextureLoad, handle, coords);
b.Return(func, texel);
});
}
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_3d<rgba8unorm, read_write>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec3<i32>):vec4<f32> {
$B2: {
%4:texture_storage_3d<rgba8unorm, read_write> = load %texture
%5:vec4<f32> = textureLoad %4, %coords
ret %5
}
}
%load_unsigned = func(%coords_1:vec3<u32>):vec4<f32> { # %coords_1: 'coords'
$B3: {
%8:texture_storage_3d<rgba8unorm, read_write> = load %texture
%9:vec4<f32> = textureLoad %8, %coords_1
ret %9
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_3d<rgba8unorm, read_write>, read> = var undef @binding_point(0, 0)
}
%load_signed = func(%coords:vec3<i32>):vec4<f32> {
$B2: {
%4:texture_storage_3d<rgba8unorm, read_write> = load %texture
%5:vec3<u32> = textureDimensions %4
%6:vec3<u32> = sub %5, vec3<u32>(1u)
%7:vec3<u32> = convert %coords
%8:vec3<u32> = min %7, %6
%9:vec4<f32> = textureLoad %4, %8
ret %9
}
}
%load_unsigned = func(%coords_1:vec3<u32>):vec4<f32> { # %coords_1: 'coords'
$B3: {
%12:texture_storage_3d<rgba8unorm, read_write> = load %texture
%13:vec3<u32> = textureDimensions %12
%14:vec3<u32> = sub %13, vec3<u32>(1u)
%15:vec3<u32> = min %coords_1, %14
%16:vec4<f32> = textureLoad %12, %15
ret %16
}
}
)";
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
Run(Robustness, cfg);
EXPECT_EQ(GetParam().enabled ? expect : src, str());
}
////////////////////////////////////////////////////////////////
// Test things that should not be clamped.
////////////////////////////////////////////////////////////////
TEST_P(IR_BindingVariableRobustnessTest, NoModify_TextureStore) {
auto format = core::TexelFormat::kRgba8Unorm;
auto* texture =
b.Var("texture",
ty.ptr(handle, ty.storage_texture(type::TextureDimension::k2d, format, write), read));
texture->SetBindingPoint(0, 0);
mod.root_block->Append(texture);
auto* foo = b.Function("foo", ty.void_());
auto* coords = b.FunctionParam("coords", ty.vec2<i32>());
auto* value = b.FunctionParam("value", ty.vec4<f32>());
foo->SetParams({coords, value});
b.Append(foo->Block(), [&] {
auto* handle = b.Load(texture);
b.Call(ty.void_(), core::BuiltinFn::kTextureStore, handle, coords, value);
b.Return(foo);
});
auto* src = R"(
$B1: { # root
%texture:ptr<handle, texture_storage_2d<rgba8unorm, write>, read> = var undef @binding_point(0, 0)
}
%foo = func(%coords:vec2<i32>, %value:vec4<f32>):void {
$B2: {
%5:texture_storage_2d<rgba8unorm, write> = load %texture
%6:void = textureStore %5, %coords, %value
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
RobustnessConfig cfg;
cfg.clamp_texture = GetParam().enabled;
if (GetParam().ignore_bindings) {
cfg.bindings_ignored = {{0, 0}};
}
Run(Robustness, cfg);
EXPECT_EQ((GetParam().enabled && !GetParam().ignore_bindings) ? expect : src, str());
}
// Test that ignoring a subset of bindings works
TEST_P(IR_RobustnessTest, BindingsIgnored_Subset) {
auto* vec1 = b.Var("vec1", ty.ptr(storage, ty.vec4<u32>()));
auto* vec2 = b.Var("vec2", ty.ptr(storage, ty.vec4<u32>()));
auto* arr1 = b.Var("arr1", ty.ptr(storage, ty.array<u32, 4>()));
auto* arr2 = b.Var("arr2", ty.ptr(storage, ty.array<u32, 4>()));
vec1->SetBindingPoint(1, 2);
vec2->SetBindingPoint(2, 3); // Ignored
arr1->SetBindingPoint(4, 5);
arr2->SetBindingPoint(6, 7); // Ignored
mod.root_block->Append(vec1);
mod.root_block->Append(vec2);
mod.root_block->Append(arr1);
mod.root_block->Append(arr2);
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
b.LoadVectorElement(vec1, idx);
b.LoadVectorElement(vec2, idx);
b.StoreVectorElement(vec1, idx, b.Constant(0_u));
b.StoreVectorElement(vec2, idx, b.Constant(0_u));
auto* access_arr1 = b.Access(ty.ptr<storage, u32>(), arr1, idx);
b.Load(access_arr1);
auto* access_arr2 = b.Access(ty.ptr<storage, u32>(), arr2, idx);
b.Load(access_arr2);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%vec1:ptr<storage, vec4<u32>, read_write> = var undef @binding_point(1, 2)
%vec2:ptr<storage, vec4<u32>, read_write> = var undef @binding_point(2, 3)
%arr1:ptr<storage, array<u32, 4>, read_write> = var undef @binding_point(4, 5)
%arr2:ptr<storage, array<u32, 4>, read_write> = var undef @binding_point(6, 7)
}
%foo = func(%idx:u32):void {
$B2: {
%7:u32 = load_vector_element %vec1, %idx
%8:u32 = load_vector_element %vec2, %idx
store_vector_element %vec1, %idx, 0u
store_vector_element %vec2, %idx, 0u
%9:ptr<storage, u32, read_write> = access %arr1, %idx
%10:u32 = load %9
%11:ptr<storage, u32, read_write> = access %arr2, %idx
%12:u32 = load %11
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%vec1:ptr<storage, vec4<u32>, read_write> = var undef @binding_point(1, 2)
%vec2:ptr<storage, vec4<u32>, read_write> = var undef @binding_point(2, 3)
%arr1:ptr<storage, array<u32, 4>, read_write> = var undef @binding_point(4, 5)
%arr2:ptr<storage, array<u32, 4>, read_write> = var undef @binding_point(6, 7)
}
%foo = func(%idx:u32):void {
$B2: {
%7:u32 = min %idx, 3u
%8:u32 = load_vector_element %vec1, %7
%9:u32 = load_vector_element %vec2, %idx
%10:u32 = min %idx, 3u
store_vector_element %vec1, %10, 0u
store_vector_element %vec2, %idx, 0u
%11:u32 = min %idx, 3u
%12:ptr<storage, u32, read_write> = access %arr1, %11
%13:u32 = load %12
%14:ptr<storage, u32, read_write> = access %arr2, %idx
%15:u32 = load %14
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_storage = GetParam();
cfg.bindings_ignored = {{2, 3}, {6, 7}};
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect : src, str());
}
// Test that bindings_ignored works via lets
TEST_P(IR_RobustnessTest, BindingsIgnored_ViaLets) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<u32, 4>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* func = b.Function("foo", ty.void_());
auto* idx = b.FunctionParam("idx", ty.u32());
func->SetParams({idx});
b.Append(func->Block(), [&] {
auto* p1 = b.Let("p1", arr);
auto* p2 = b.Let("p2", p1);
auto* access = b.Access(ty.ptr<storage, u32>(), p2, idx);
b.Load(access);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<u32, 4>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%idx:u32):void {
$B2: {
%p1:ptr<storage, array<u32, 4>, read_write> = let %arr
%p2:ptr<storage, array<u32, 4>, read_write> = let %p1
%6:ptr<storage, u32, read_write> = access %p2, %idx
%7:u32 = load %6
ret
}
}
)";
EXPECT_EQ(src, str());
RobustnessConfig cfg;
cfg.clamp_storage = GetParam();
cfg.bindings_ignored = {{0, 0}};
Run(Robustness, cfg);
EXPECT_EQ(src, str());
}
TEST_P(IR_RobustnessTest, SubgroupMatrixLoad_StorageRuntimeArray_ConstStride_ColMajor) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<f32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", mat);
b.Append(func->Block(), [&] {
// Constant stride of 1 should be clamped to 4 even when predication is disabled.
auto* load =
b.CallExplicit(mat, BuiltinFn::kSubgroupMatrixLoad, Vector{mat}, arr, 0_u, true, 1_u);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func():subgroup_matrix_result<f32, 8, 4> {
$B2: {
%3:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, 1u
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect_with_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func():subgroup_matrix_result<f32, 8, 4> {
$B2: {
%3:u32 = arrayLength %arr
%4:u32 = mul 4u, 7u
%5:u32 = add 0u, %4
%6:u32 = add %5, 4u
%7:bool = lte %6, %3
%8:ptr<function, subgroup_matrix_result<f32, 8, 4>, read_write> = var undef
if %7 [t: $B3] { # if_1
$B3: { # true
%9:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, 4u
store %8, %9
exit_if # if_1
}
}
%10:subgroup_matrix_result<f32, 8, 4> = load %8
ret %10
}
}
)";
auto* expect_without_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func():subgroup_matrix_result<f32, 8, 4> {
$B2: {
%3:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, 4u
ret %3
}
}
)";
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect_with_predication : expect_without_predication, str());
}
TEST_P(IR_RobustnessTest, SubgroupMatrixLoad_StorageRuntimeArray_DynamicStride_ColMajor) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<f32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", mat);
auto* stride = b.FunctionParam<u32>("stride");
func->AppendParam(stride);
b.Append(func->Block(), [&] {
// Dynamic stride should be clamped with `max` even when predication is disabled.
auto* load = b.CallExplicit(mat, BuiltinFn::kSubgroupMatrixLoad, Vector{mat}, arr, 0_u,
true, stride);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, %stride
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect_with_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:u32 = max %stride, 4u
%5:u32 = arrayLength %arr
%6:u32 = mul %4, 7u
%7:u32 = add 0u, %6
%8:u32 = add %7, 4u
%9:bool = lte %8, %5
%10:ptr<function, subgroup_matrix_result<f32, 8, 4>, read_write> = var undef
if %9 [t: $B3] { # if_1
$B3: { # true
%11:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, %4
store %10, %11
exit_if # if_1
}
}
%12:subgroup_matrix_result<f32, 8, 4> = load %10
ret %12
}
}
)";
auto* expect_without_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:u32 = max %stride, 4u
%5:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect_with_predication : expect_without_predication, str());
}
TEST_P(IR_RobustnessTest, SubgroupMatrixLoad_StorageRuntimeArray_DynamicStride_RowMajor) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<f32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", mat);
auto* stride = b.FunctionParam<u32>("stride");
func->AppendParam(stride);
b.Append(func->Block(), [&] {
// Dynamic stride should be clamped with `max` even when predication is disabled.
auto* load = b.CallExplicit(mat, BuiltinFn::kSubgroupMatrixLoad, Vector{mat}, arr, 0_u,
false, stride);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, false, %stride
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect_with_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:u32 = max %stride, 8u
%5:u32 = arrayLength %arr
%6:u32 = mul %4, 3u
%7:u32 = add 0u, %6
%8:u32 = add %7, 8u
%9:bool = lte %8, %5
%10:ptr<function, subgroup_matrix_result<f32, 8, 4>, read_write> = var undef
if %9 [t: $B3] { # if_1
$B3: { # true
%11:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, false, %4
store %10, %11
exit_if # if_1
}
}
%12:subgroup_matrix_result<f32, 8, 4> = load %10
ret %12
}
}
)";
auto* expect_without_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:u32 = max %stride, 8u
%5:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, false, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect_with_predication : expect_without_predication, str());
}
TEST_P(IR_RobustnessTest, SubgroupMatrixLoad_WorkgroupFixedArray_DynamicStride_ColMajor) {
auto* arr = b.Var("arr", ty.ptr(workgroup, ty.array<f32, 1024>()));
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", mat);
auto* stride = b.FunctionParam<u32>("stride");
func->AppendParam(stride);
b.Append(func->Block(), [&] {
// Dynamic stride should be clamped with `max` even when predication is disabled.
auto* load = b.CallExplicit(mat, BuiltinFn::kSubgroupMatrixLoad, Vector{mat}, arr, 0_u,
true, stride);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<workgroup, array<f32, 1024>, read_write> = var undef
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, %stride
ret %4
}
}
)";
EXPECT_EQ(src, str());
auto* expect_with_predication = R"(
$B1: { # root
%arr:ptr<workgroup, array<f32, 1024>, read_write> = var undef
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:u32 = max %stride, 4u
%5:u32 = mul %4, 7u
%6:u32 = add 0u, %5
%7:u32 = add %6, 4u
%8:bool = lte %7, 1024u
%9:ptr<function, subgroup_matrix_result<f32, 8, 4>, read_write> = var undef
if %8 [t: $B3] { # if_1
$B3: { # true
%10:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, %4
store %9, %10
exit_if # if_1
}
}
%11:subgroup_matrix_result<f32, 8, 4> = load %9
ret %11
}
}
)";
auto* expect_without_predication = R"(
$B1: { # root
%arr:ptr<workgroup, array<f32, 1024>, read_write> = var undef
}
%foo = func(%stride:u32):subgroup_matrix_result<f32, 8, 4> {
$B2: {
%4:u32 = max %stride, 4u
%5:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 0u, true, %4
ret %5
}
}
)";
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect_with_predication : expect_without_predication, str());
}
// Test that we avoid any predication and clamping for fixed size arrays when all parameters are
// constant and in-bounds.
TEST_P(IR_RobustnessTest, SubgroupMatrixLoad_WorkgroupFixedArray_ConstStrideAndOffset) {
auto* arr = b.Var("arr", ty.ptr(workgroup, ty.array<f32, 1024>()));
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", mat);
b.Append(func->Block(), [&] {
// The final row will start at 1016. Another full stride will take it past the 1024 limit,
// but the transform should understand that only 8 elements are accessed on that row.
auto* load = b.CallExplicit(mat, BuiltinFn::kSubgroupMatrixLoad, Vector{mat}, arr, 920_u,
false, 32_u);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%arr:ptr<workgroup, array<f32, 1024>, read_write> = var undef
}
%foo = func():subgroup_matrix_result<f32, 8, 4> {
$B2: {
%3:subgroup_matrix_result<f32, 8, 4> = subgroupMatrixLoad<subgroup_matrix_result<f32, 8, 4>> %arr, 920u, false, 32u
ret %3
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_P(IR_RobustnessTest, SubgroupMatrixStore_StorageRuntimeArray_ConstStride_ColMajor) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<f32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", ty.void_());
auto* value = b.FunctionParam("value", mat);
func->AppendParam(value);
b.Append(func->Block(), [&] {
// Constant stride of 1 should be clamped to 4 even when predication is disabled.
b.Call(ty.void_(), BuiltinFn::kSubgroupMatrixStore, arr, 0_u, value, true, 1_u);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>):void {
$B2: {
%4:void = subgroupMatrixStore %arr, 0u, %value, true, 1u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect_with_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>):void {
$B2: {
%4:u32 = arrayLength %arr
%5:u32 = mul 4u, 7u
%6:u32 = add 0u, %5
%7:u32 = add %6, 4u
%8:bool = lte %7, %4
if %8 [t: $B3] { # if_1
$B3: { # true
%9:void = subgroupMatrixStore %arr, 0u, %value, true, 4u
exit_if # if_1
}
}
ret
}
}
)";
auto* expect_without_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>):void {
$B2: {
%4:void = subgroupMatrixStore %arr, 0u, %value, true, 4u
ret
}
}
)";
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect_with_predication : expect_without_predication, str());
}
TEST_P(IR_RobustnessTest, SubgroupMatrixStore_StorageRuntimeArray_DynamicStride_ColMajor) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<f32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", ty.void_());
auto* value = b.FunctionParam("value", mat);
auto* stride = b.FunctionParam<u32>("stride");
func->AppendParam(value);
func->AppendParam(stride);
b.Append(func->Block(), [&] {
// Dynamic stride should be clamped with `max` even when predication is disabled.
b.Call(ty.void_(), BuiltinFn::kSubgroupMatrixStore, arr, 0_u, value, true, stride);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:void = subgroupMatrixStore %arr, 0u, %value, true, %stride
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect_with_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:u32 = max %stride, 4u
%6:u32 = arrayLength %arr
%7:u32 = mul %5, 7u
%8:u32 = add 0u, %7
%9:u32 = add %8, 4u
%10:bool = lte %9, %6
if %10 [t: $B3] { # if_1
$B3: { # true
%11:void = subgroupMatrixStore %arr, 0u, %value, true, %5
exit_if # if_1
}
}
ret
}
}
)";
auto* expect_without_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:u32 = max %stride, 4u
%6:void = subgroupMatrixStore %arr, 0u, %value, true, %5
ret
}
}
)";
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect_with_predication : expect_without_predication, str());
}
TEST_P(IR_RobustnessTest, SubgroupMatrixStore_StorageRuntimeArray_DynamicStride_RowMajor) {
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<f32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", ty.void_());
auto* value = b.FunctionParam("value", mat);
auto* stride = b.FunctionParam<u32>("stride");
func->AppendParam(value);
func->AppendParam(stride);
b.Append(func->Block(), [&] {
// Dynamic stride should be clamped with `max` even when predication is disabled.
b.Call(ty.void_(), BuiltinFn::kSubgroupMatrixStore, arr, 0_u, value, false, stride);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:void = subgroupMatrixStore %arr, 0u, %value, false, %stride
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect_with_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:u32 = max %stride, 8u
%6:u32 = arrayLength %arr
%7:u32 = mul %5, 3u
%8:u32 = add 0u, %7
%9:u32 = add %8, 8u
%10:bool = lte %9, %6
if %10 [t: $B3] { # if_1
$B3: { # true
%11:void = subgroupMatrixStore %arr, 0u, %value, false, %5
exit_if # if_1
}
}
ret
}
}
)";
auto* expect_without_predication = R"(
$B1: { # root
%arr:ptr<storage, array<f32>, read_write> = var undef @binding_point(0, 0)
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:u32 = max %stride, 8u
%6:void = subgroupMatrixStore %arr, 0u, %value, false, %5
ret
}
}
)";
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect_with_predication : expect_without_predication, str());
}
TEST_P(IR_RobustnessTest, SubgroupMatrixStore_WorkgroupFixedArray_DynamicStride_ColMajor) {
auto* arr = b.Var("arr", ty.ptr(workgroup, ty.array<f32, 1024>()));
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", ty.void_());
auto* value = b.FunctionParam("value", mat);
auto* stride = b.FunctionParam<u32>("stride");
func->AppendParam(value);
func->AppendParam(stride);
b.Append(func->Block(), [&] {
// Dynamic stride should be clamped with `max` even when predication is disabled.
b.Call(ty.void_(), BuiltinFn::kSubgroupMatrixStore, arr, 0_u, value, true, stride);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%arr:ptr<workgroup, array<f32, 1024>, read_write> = var undef
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:void = subgroupMatrixStore %arr, 0u, %value, true, %stride
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect_with_predication = R"(
$B1: { # root
%arr:ptr<workgroup, array<f32, 1024>, read_write> = var undef
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:u32 = max %stride, 4u
%6:u32 = mul %5, 7u
%7:u32 = add 0u, %6
%8:u32 = add %7, 4u
%9:bool = lte %8, 1024u
if %9 [t: $B3] { # if_1
$B3: { # true
%10:void = subgroupMatrixStore %arr, 0u, %value, true, %5
exit_if # if_1
}
}
ret
}
}
)";
auto* expect_without_predication = R"(
$B1: { # root
%arr:ptr<workgroup, array<f32, 1024>, read_write> = var undef
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>, %stride:u32):void {
$B2: {
%5:u32 = max %stride, 4u
%6:void = subgroupMatrixStore %arr, 0u, %value, true, %5
ret
}
}
)";
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(GetParam() ? expect_with_predication : expect_without_predication, str());
}
// Test that we avoid any predication and clamping for fixed size arrays when all parameters are
// constant and in-bounds.
TEST_P(IR_RobustnessTest, SubgroupMatrixStore_WorkgroupFixedArray_ConstStrideAndOffset) {
auto* arr = b.Var("arr", ty.ptr(workgroup, ty.array<f32, 1024>()));
mod.root_block->Append(arr);
auto* mat = ty.subgroup_matrix_result(ty.f32(), 8u, 4u);
auto* func = b.Function("foo", ty.void_());
auto* value = b.FunctionParam("value", mat);
func->AppendParam(value);
b.Append(func->Block(), [&] {
// The final row will start at 1016. Another full stride will take it past the 1024 limit,
// but the transform should understand that only 8 elements are accessed on that row.
b.Call(ty.void_(), BuiltinFn::kSubgroupMatrixStore, arr, 920_u, value, false, 32_u);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%arr:ptr<workgroup, array<f32, 1024>, read_write> = var undef
}
%foo = func(%value:subgroup_matrix_result<f32, 8, 4>):void {
$B2: {
%4:void = subgroupMatrixStore %arr, 920u, %value, false, 32u
ret
}
}
)";
EXPECT_EQ(src, str());
auto* expect = src;
RobustnessConfig cfg;
cfg.predicate_subgroup_matrix = GetParam();
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithIndex_MaxBound_Equal_Limit) {
auto* func = b.Function("func", ty.void_());
b.Append(func->Block(), [&] {
Var* idx = nullptr;
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = 0u
idx = b.Var("idx", 0_u);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 20u
auto* binary = b.LessThan<bool>(b.Load(idx), 20_u);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_arr = arr[idx]
// idx: [0u, 19u]
auto* load_idx = b.Load(idx);
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, load_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
%func = func():void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
loop [i: $B2, b: $B3, c: $B4] { # loop_1
$B2: { # initializer
%idx:ptr<function, u32, read_write> = var 0u
next_iteration # -> $B3
}
$B3: { # body
%4:u32 = load %idx
%5:bool = lt %4, 20u
if %5 [t: $B5, f: $B6] { # if_1
$B5: { # true
exit_if # if_1
}
$B6: { # false
exit_loop # loop_1
}
}
%6:u32 = load %idx
%7:ptr<function, u32, read_write> = access %arr, %6
%8:u32 = load %7
continue # -> $B4
}
$B4: { # continuing
%9:u32 = load %idx
%10:u32 = add %9, 1u
store %idx, %10
next_iteration # -> $B3
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithIndex_MaxBound_LessThan_Limit) {
auto* func = b.Function("func", ty.void_());
b.Append(func->Block(), [&] {
Var* idx = nullptr;
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = 0u
idx = b.Var("idx", 0_u);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 19u
auto* binary = b.LessThan<bool>(b.Load(idx), 19_u);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_arr = arr[idx]
// idx: [0u, 18u]
auto* load_idx = b.Load(idx);
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, load_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
%func = func():void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
loop [i: $B2, b: $B3, c: $B4] { # loop_1
$B2: { # initializer
%idx:ptr<function, u32, read_write> = var 0u
next_iteration # -> $B3
}
$B3: { # body
%4:u32 = load %idx
%5:bool = lt %4, 19u
if %5 [t: $B5, f: $B6] { # if_1
$B5: { # true
exit_if # if_1
}
$B6: { # false
exit_loop # loop_1
}
}
%6:u32 = load %idx
%7:ptr<function, u32, read_write> = access %arr, %6
%8:u32 = load %7
continue # -> $B4
}
$B4: { # continuing
%9:u32 = load %idx
%10:u32 = add %9, 1u
store %idx, %10
next_iteration # -> $B3
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithIndex_MaxBound_GreaterThan_Limit) {
auto* func = b.Function("func", ty.void_());
b.Append(func->Block(), [&] {
Var* idx = nullptr;
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = 0u
idx = b.Var("idx", 0_u);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 21u
auto* binary = b.LessThan<bool>(b.Load(idx), 21_u);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_arr = arr[idx]
// idx: [0, 20u]
auto* load_idx = b.Load(idx);
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, load_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
%func = func():void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
loop [i: $B2, b: $B3, c: $B4] { # loop_1
$B2: { # initializer
%idx:ptr<function, u32, read_write> = var 0u
next_iteration # -> $B3
}
$B3: { # body
%4:u32 = load %idx
%5:bool = lt %4, 21u
if %5 [t: $B5, f: $B6] { # if_1
$B5: { # true
exit_if # if_1
}
$B6: { # false
exit_loop # loop_1
}
}
%6:u32 = load %idx
%7:u32 = min %6, 19u
%8:ptr<function, u32, read_write> = access %arr, %7
%9:u32 = load %8
continue # -> $B4
}
$B4: { # continuing
%10:u32 = load %idx
%11:u32 = add %10, 1u
store %idx, %11
next_iteration # -> $B3
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithIndex_IndexNoRange) {
auto* func = b.Function("func", ty.void_());
auto* param = b.FunctionParam("param", ty.u32());
func->AppendParam(param);
b.Append(func->Block(), [&] {
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, param);
b.Load(access_arr);
b.Return(func);
});
auto* expect = R"(
%func = func(%param:u32):void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
%4:u32 = min %param, 19u
%5:ptr<function, u32, read_write> = access %arr, %4
%6:u32 = load %5
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithExpression_MaxBound_Equal_Limit) {
auto* func = b.ComputeFunction("my_func", 4_u, 1_u, 1_u);
auto* local_invocation_id = b.FunctionParam("local_id", mod.Types().vec3<u32>());
local_invocation_id->SetBuiltin(tint::core::BuiltinValue::kLocalInvocationId);
func->SetParams({local_invocation_id});
b.Append(func->Block(), [&] {
Var* idx = nullptr;
// factor = 4
auto* factor = b.Constant(4_u);
// arr = array<u32, 32>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 32>()));
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = 0
idx = b.Var("idx", 0_u);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 8
auto* binary = b.LessThan<bool>(b.Load(idx), 8_u);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_idx = idx * factor + local_id.x
// access_idx: [0, 31] (idx: [0, 7], factor = 4, local_id.x: [0, 3])
auto* load_idx = b.Load(idx);
auto* multiply = b.Multiply<u32>(load_idx, factor);
auto* access_local_id_x = b.Access(ty.u32(), local_invocation_id, 0_u);
auto* access_idx = b.Add<u32>(multiply, access_local_id_x);
// access_arr = arr[access_idx]
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, access_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
%my_func = @compute @workgroup_size(4u, 1u, 1u) func(%local_id:vec3<u32> [@local_invocation_id]):void {
$B1: {
%arr:ptr<function, array<u32, 32>, read_write> = var undef
loop [i: $B2, b: $B3, c: $B4] { # loop_1
$B2: { # initializer
%idx:ptr<function, u32, read_write> = var 0u
next_iteration # -> $B3
}
$B3: { # body
%5:u32 = load %idx
%6:bool = lt %5, 8u
if %6 [t: $B5, f: $B6] { # if_1
$B5: { # true
exit_if # if_1
}
$B6: { # false
exit_loop # loop_1
}
}
%7:u32 = load %idx
%8:u32 = mul %7, 4u
%9:u32 = access %local_id, 0u
%10:u32 = add %8, %9
%11:ptr<function, u32, read_write> = access %arr, %10
%12:u32 = load %11
continue # -> $B4
}
$B4: { # continuing
%13:u32 = load %idx
%14:u32 = add %13, 1u
store %idx, %14
next_iteration # -> $B3
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithIndex_I32_NegativeMinBound) {
auto* func = b.Function("func", ty.void_());
b.Append(func->Block(), [&] {
Var* idx = nullptr;
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = -1
idx = b.Var("idx", -1_i);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 20
auto* binary = b.LessThan<bool>(b.Load(idx), 20_i);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_arr = arr[idx]
// idx: [-1, 19]
auto* load_idx = b.Load(idx);
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, load_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<i32>(b.Load(idx), 1_i));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
%func = func():void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
loop [i: $B2, b: $B3, c: $B4] { # loop_1
$B2: { # initializer
%idx:ptr<function, i32, read_write> = var -1i
next_iteration # -> $B3
}
$B3: { # body
%4:i32 = load %idx
%5:bool = lt %4, 20i
if %5 [t: $B5, f: $B6] { # if_1
$B5: { # true
exit_if # if_1
}
$B6: { # false
exit_loop # loop_1
}
}
%6:i32 = load %idx
%7:u32 = convert %6
%8:u32 = min %7, 19u
%9:ptr<function, u32, read_write> = access %arr, %8
%10:u32 = load %9
continue # -> $B4
}
$B4: { # continuing
%11:i32 = load %idx
%12:i32 = add %11, 1i
store %idx, %12
next_iteration # -> $B3
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithIndex_MaxBound_Equal_Limit_I32) {
auto* func = b.Function("func", ty.void_());
b.Append(func->Block(), [&] {
Var* idx = nullptr;
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = 0
idx = b.Var("idx", 0_i);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 20
auto* binary = b.LessThan<bool>(b.Load(idx), 20_i);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_arr = arr[idx]
// idx: [0, 19]
auto* load_idx = b.Load(idx);
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, load_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<i32>(b.Load(idx), 1_i));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
%func = func():void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
loop [i: $B2, b: $B3, c: $B4] { # loop_1
$B2: { # initializer
%idx:ptr<function, i32, read_write> = var 0i
next_iteration # -> $B3
}
$B3: { # body
%4:i32 = load %idx
%5:bool = lt %4, 20i
if %5 [t: $B5, f: $B6] { # if_1
$B5: { # true
exit_if # if_1
}
$B6: { # false
exit_loop # loop_1
}
}
%6:i32 = load %idx
%7:ptr<function, u32, read_write> = access %arr, %6
%8:u32 = load %7
continue # -> $B4
}
$B4: { # continuing
%9:i32 = load %idx
%10:i32 = add %9, 1i
store %idx, %10
next_iteration # -> $B3
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest,
AccessArrayWithIndex_MaxBound_LessThan_Limit_I32) {
auto* func = b.Function("func", ty.void_());
b.Append(func->Block(), [&] {
Var* idx = nullptr;
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = 0
idx = b.Var("idx", 0_i);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 19
auto* binary = b.LessThan<bool>(b.Load(idx), 19_i);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_arr = arr[idx]
// idx: [0, 18]
auto* load_idx = b.Load(idx);
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, load_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<i32>(b.Load(idx), 1_i));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
%func = func():void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
loop [i: $B2, b: $B3, c: $B4] { # loop_1
$B2: { # initializer
%idx:ptr<function, i32, read_write> = var 0i
next_iteration # -> $B3
}
$B3: { # body
%4:i32 = load %idx
%5:bool = lt %4, 19i
if %5 [t: $B5, f: $B6] { # if_1
$B5: { # true
exit_if # if_1
}
$B6: { # false
exit_loop # loop_1
}
}
%6:i32 = load %idx
%7:ptr<function, u32, read_write> = access %arr, %6
%8:u32 = load %7
continue # -> $B4
}
$B4: { # continuing
%9:i32 = load %idx
%10:i32 = add %9, 1i
store %idx, %10
next_iteration # -> $B3
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest,
AccessArrayWithIndex_MaxBound_GreaterThan_Limit_I32) {
auto* func = b.Function("func", ty.void_());
b.Append(func->Block(), [&] {
Var* idx = nullptr;
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = 0
idx = b.Var("idx", 0_i);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 21
auto* binary = b.LessThan<bool>(b.Load(idx), 21_i);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_arr = arr[idx]
// idx: [0, 20]
auto* load_idx = b.Load(idx);
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, load_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<i32>(b.Load(idx), 1_i));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
%func = func():void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
loop [i: $B2, b: $B3, c: $B4] { # loop_1
$B2: { # initializer
%idx:ptr<function, i32, read_write> = var 0i
next_iteration # -> $B3
}
$B3: { # body
%4:i32 = load %idx
%5:bool = lt %4, 21i
if %5 [t: $B5, f: $B6] { # if_1
$B5: { # true
exit_if # if_1
}
$B6: { # false
exit_loop # loop_1
}
}
%6:i32 = load %idx
%7:u32 = convert %6
%8:u32 = min %7, 19u
%9:ptr<function, u32, read_write> = access %arr, %8
%10:u32 = load %9
continue # -> $B4
}
$B4: { # continuing
%11:i32 = load %idx
%12:i32 = add %11, 1i
store %idx, %12
next_iteration # -> $B3
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithIndex_IndexNoRange_I32) {
auto* func = b.Function("func", ty.void_());
auto* param = b.FunctionParam("param", ty.i32());
func->AppendParam(param);
b.Append(func->Block(), [&] {
// arr = array<u32, 20>
auto* arr = b.Var("arr", ty.ptr(function, ty.array<u32, 20>()));
auto* access_arr = b.Access(ty.ptr<function, u32>(), arr, param);
b.Load(access_arr);
b.Return(func);
});
auto* expect = R"(
%func = func(%param:i32):void {
$B1: {
%arr:ptr<function, array<u32, 20>, read_write> = var undef
%4:u32 = convert %param
%5:u32 = min %4, 19u
%6:ptr<function, u32, read_write> = access %arr, %5
%7:u32 = load %6
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_RobustnessWithIntegerRangeAnalysisTest, AccessArrayWithIndex_DynamicSizedArray) {
// arr = array<u32>
auto* arr = b.Var("arr", ty.ptr(storage, ty.array<u32>()));
arr->SetBindingPoint(0, 0);
mod.root_block->Append(arr);
auto* func = b.Function("func", ty.void_());
b.Append(func->Block(), [&] {
Var* idx = nullptr;
auto* loop = b.Loop();
b.Append(loop->Initializer(), [&] {
// idx = 0u
idx = b.Var("idx", 0_u);
b.NextIteration(loop);
});
b.Append(loop->Body(), [&] {
// idx < 20u
auto* binary = b.LessThan<bool>(b.Load(idx), 20_u);
auto* ifelse = b.If(binary);
b.Append(ifelse->True(), [&] { b.ExitIf(ifelse); });
b.Append(ifelse->False(), [&] { b.ExitLoop(loop); });
// access_arr = arr[idx]
// idx: [0u, 19u]
auto* load_idx = b.Load(idx);
auto* access_arr = b.Access(ty.ptr<storage, u32>(), arr, load_idx);
b.Load(access_arr);
b.Continue(loop);
});
b.Append(loop->Continuing(), [&] {
// idx++
b.Store(idx, b.Add<u32>(b.Load(idx), 1_u));
b.NextIteration(loop);
});
b.Return(func);
});
auto* expect = R"(
$B1: { # root
%arr:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%func = func():void {
$B2: {
loop [i: $B3, b: $B4, c: $B5] { # loop_1
$B3: { # initializer
%idx:ptr<function, u32, read_write> = var 0u
next_iteration # -> $B4
}
$B4: { # body
%4:u32 = load %idx
%5:bool = lt %4, 20u
if %5 [t: $B6, f: $B7] { # if_1
$B6: { # true
exit_if # if_1
}
$B7: { # false
exit_loop # loop_1
}
}
%6:u32 = load %idx
%7:u32 = arrayLength %arr
%8:u32 = sub %7, 1u
%9:u32 = min %6, %8
%10:ptr<storage, u32, read_write> = access %arr, %9
%11:u32 = load %10
continue # -> $B5
}
$B5: { # continuing
%12:u32 = load %idx
%13:u32 = add %12, 1u
store %idx, %13
next_iteration # -> $B4
}
}
ret
}
}
)";
RobustnessConfig cfg;
cfg.clamp_function = true;
cfg.clamp_storage = true;
cfg.use_integer_range_analysis = true;
Run(Robustness, cfg);
EXPECT_EQ(expect, str());
}
INSTANTIATE_TEST_SUITE_P(, IR_RobustnessTest, testing::Values(false, true));
INSTANTIATE_TEST_SUITE_P(,
IR_BindingVariableRobustnessTest,
testing::Values(BindingVariableCase{true, false},
BindingVariableCase{false, false},
BindingVariableCase{true, true},
BindingVariableCase{false, false}));
} // namespace
} // namespace tint::core::ir::transform