blob: ae92c66e384fb60db097e996ac60bce9422927fb [file] [edit]
// Copyright 2024 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/core/ir/transform/substitute_overrides.h"
#include <limits>
#include <utility>
#include "gtest/gtest.h"
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/core/ir/override.h"
#include "src/tint/lang/core/ir/transform/helper_test.h"
#include "src/tint/lang/core/ir/type/array_count.h"
#include "src/tint/lang/core/ir/var.h"
#include "src/tint/lang/core/type/array.h"
namespace tint::core::ir::transform {
namespace {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
using IR_SubstituteOverridesTest = TransformTest;
TEST_F(IR_SubstituteOverridesTest, NoOverridesNoChange) {
auto* func = b.Function("foo", ty.void_());
func->Block()->Append(b.Return(func));
auto* expect = R"(
%foo = func():void {
$B1: {
ret
}
}
)";
SubstituteOverridesConfig cfg{};
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, UnsetOverrideTriggersError) {
b.Append(mod.root_block, [&] {
auto* o = b.Override(Source{{1, 2}}, "a", ty.i32());
o->SetOverrideId({1});
});
auto* src = R"(
$B1: { # root
%a:i32 = override undef @id(1)
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason,
R"(1:2 error: Initializer not provided for override, and override not overridden.)");
}
TEST_F(IR_SubstituteOverridesTest, OverrideNotInFile) {
auto* f = b.ComputeFunction("main");
b.Append(f->Block(), [&] { b.Return(f); });
auto* src = R"(
%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B1: {
ret
}
}
)";
auto* expect = R"(
%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B1: {
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{99}] = 55;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithDefault) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
o = b.Override(Source{{1, 2}}, "a", 2_u);
o->SetOverrideId({1});
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%a:u32 = override 2u @id(1)
}
%foo = func():u32 {
$B2: {
ret %a
}
}
)";
auto* expect = R"(
%foo = func():u32 {
$B1: {
ret 2u
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithDefaultWithOverride) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
o = b.Override(Source{{1, 2}}, "a", 2_u);
o->SetOverrideId({1});
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%a:u32 = override 2u @id(1)
}
%foo = func():u32 {
$B2: {
ret %a
}
}
)";
auto* expect = R"(
%foo = func():u32 {
$B1: {
ret 55u
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{1}] = 55;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithoutDefaultWithOverride) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%a:u32 = override undef @id(1)
}
%foo = func():u32 {
$B2: {
ret %a
}
}
)";
auto* expect = R"(
%foo = func():u32 {
$B1: {
ret 55u
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{1}] = 55;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithComplexInitNoOverrides) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* add = b.Add(2_u, 4_u);
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%1:u32 = add 2u, 4u
%a:u32 = override %1 @id(1)
}
%foo = func():u32 {
$B2: {
ret %a
}
}
)";
auto* expect = R"(
%foo = func():u32 {
$B1: {
ret 6u
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithComplexInitComponentOverride) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* add = b.Add(2_u, 4_u);
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%1:u32 = add 2u, 4u
%a:u32 = override %1 @id(1)
}
%foo = func():u32 {
$B2: {
ret %a
}
}
)";
auto* expect = R"(
%foo = func():u32 {
$B1: {
ret 55u
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{1}] = 55;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithComplexIncludingOverride) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_u);
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%2:u32 = add %x, 4u
%a:u32 = override %2 @id(1)
}
%foo = func():u32 {
$B2: {
ret %a
}
}
)";
auto* expect = R"(
%foo = func():u32 {
$B1: {
ret 9u
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithSubgroupShuffle) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_u);
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* shuffle_func = b.Call(ty.u32(), core::BuiltinFn::kSubgroupShuffle, 1_u, o);
b.Return(func, shuffle_func->Result());
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%2:u32 = add %x, 4u
%a:u32 = override %2 @id(1)
}
%foo = func():u32 {
$B2: {
%5:u32 = subgroupShuffle 1u, %a
ret %5
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 125.0;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason,
R"(error: The sourceLaneIndex argument of subgroupShuffle must be less than 128)");
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithQuantizeF16) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.f32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_f);
o = b.Override(Source{{1, 2}}, "a", ty.f32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.Function("foo", ty.f32());
b.Append(func->Block(), [&] {
auto* shuffle_func = b.Call(ty.f32(), core::BuiltinFn::kQuantizeToF16, o);
b.Return(func, shuffle_func->Result());
});
auto* src = R"(
$B1: { # root
%x:f32 = override undef @id(2)
%2:f32 = add %x, 4.0f
%a:f32 = override %2 @id(1)
}
%foo = func():f32 {
$B2: {
%5:f32 = quantizeToF16 %a
ret %5
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = -65505.0 - 4.0;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(error: value -65505.0 cannot be represented as 'f16')");
}
TEST_F(IR_SubstituteOverridesTest, OverrideWithComplexGenError) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.f32());
x->SetOverrideId({2});
auto* add = b.Add(x, f32(std::numeric_limits<float>::max() - 1));
b.ir.SetSource(add, Source{{1, 2}});
o = b.Override("a", ty.f32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.Function("foo", ty.f32());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%x:f32 = override undef @id(2)
%2:f32 = add %x, 340282346638528859811704183484516925440.0f
%a:f32 = override %2 @id(1)
}
%foo = func():f32 {
$B2: {
ret %a
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = static_cast<double>(std::numeric_limits<float>::max());
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(
result.Failure().reason,
R"(1:2 error: '340282346638528859811704183484516925440.0 + 340282346638528859811704183484516925440.0' cannot be represented as 'f32')");
}
TEST_F(IR_SubstituteOverridesTest, OverrideWorkgroupSize) {
core::ir::Override* o = nullptr;
core::ir::Override* x = nullptr;
b.Append(mod.root_block, [&] {
x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_u);
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.ComputeFunction("foo", o, x, o);
b.Append(func->Block(), [&] { b.Return(func); });
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%2:u32 = add %x, 4u
%a:u32 = override %2 @id(1)
}
%foo = @compute @workgroup_size(%a, %x, %a) func():void {
$B2: {
ret
}
}
)";
auto* expect = R"(
%foo = @compute @workgroup_size(9u, 5u, 9u) func():void {
$B1: {
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, FunctionExpression) {
core::ir::Override* o = nullptr;
core::ir::Override* x = nullptr;
b.Append(mod.root_block, [&] {
x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_u);
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] {
b.Let("y", b.Divide(10_u, x));
b.Let("z", b.Multiply(5_u, o));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%2:u32 = add %x, 4u
%a:u32 = override %2 @id(1)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%5:u32 = div 10u, %x
%y:u32 = let %5
%7:u32 = mul 5u, %a
%z:u32 = let %7
ret
}
}
)";
auto* expect = R"(
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B1: {
%y:u32 = let 2u
%z:u32 = let 45u
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, FunctionExpressionNonConstBuiltin) {
core::ir::Override* x = nullptr;
b.Append(mod.root_block, [&] {
x = b.Override("x", ty.f32());
x->SetOverrideId({2});
});
auto* func = b.FragmentFunction("foo", ty.void_());
b.Append(func->Block(), [&] {
b.Let("y", b.Call(ty.f32(), core::BuiltinFn::kDpdx, b.Multiply(x, 4_f)));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%x:f32 = override undef @id(2)
}
%foo = @fragment func():void {
$B2: {
%3:f32 = mul %x, 4.0f
%4:f32 = dpdx %3
%y:f32 = let %4
ret
}
}
)";
auto* expect = R"(
%foo = @fragment func():void {
$B1: {
%2:f32 = dpdx 20.0f
%y:f32 = let %2
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, FunctionExpressionMultiOperand) {
core::ir::Override* o = nullptr;
core::ir::Override* x = nullptr;
b.Append(mod.root_block, [&] {
x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_u);
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] {
b.Let("y", b.Divide(10_u, o));
auto* k = b.Add(1_u, b.Multiply(2_u, x));
b.Let("z", b.Multiply(k, o));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%2:u32 = add %x, 4u
%a:u32 = override %2 @id(1)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%5:u32 = div 10u, %a
%y:u32 = let %5
%7:u32 = mul 2u, %x
%8:u32 = add 1u, %7
%9:u32 = mul %8, %a
%z:u32 = let %9
ret
}
}
)";
auto* expect = R"(
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B1: {
%y:u32 = let 1u
%z:u32 = let 99u
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, FunctionExpressionMultiOperandFlipOrder) {
core::ir::Override* o = nullptr;
core::ir::Override* x = nullptr;
b.Append(mod.root_block, [&] {
x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_u);
o = b.Override(Source{{1, 2}}, "a", ty.u32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] {
b.Let("y", b.Divide(10_u, o));
auto* k = b.Add(1_u, b.Multiply(2_u, o));
b.Let("z", b.Multiply(k, x));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%2:u32 = add %x, 4u
%a:u32 = override %2 @id(1)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%5:u32 = div 10u, %a
%y:u32 = let %5
%7:u32 = mul 2u, %a
%8:u32 = add 1u, %7
%9:u32 = mul %8, %x
%z:u32 = let %9
ret
}
}
)";
auto* expect = R"(
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B1: {
%y:u32 = let 1u
%z:u32 = let 95u
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, FunctionExpressionMultiOperandNonConstFn) {
core::ir::Override* o = nullptr;
core::ir::Override* x = nullptr;
b.Append(mod.root_block, [&] {
x = b.Override("x", ty.f32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_f);
o = b.Override(Source{{1, 2}}, "a", ty.f32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.FragmentFunction("foo", ty.void_());
b.Append(func->Block(), [&] {
b.Let("y", b.Divide(10_f, x));
auto* k = b.Call(ty.f32(), core::BuiltinFn::kDpdx, x);
b.Let("z", b.Multiply(k, o));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%x:f32 = override undef @id(2)
%2:f32 = add %x, 4.0f
%a:f32 = override %2 @id(1)
}
%foo = @fragment func():void {
$B2: {
%5:f32 = div 10.0f, %x
%y:f32 = let %5
%7:f32 = dpdx %x
%8:f32 = mul %7, %a
%z:f32 = let %8
ret
}
}
)";
auto* expect = R"(
%foo = @fragment func():void {
$B1: {
%y:f32 = let 2.0f
%3:f32 = dpdx 5.0f
%4:f32 = mul %3, 9.0f
%z:f32 = let %4
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, FunctionExpressionMultiOperandLet) {
core::ir::Override* o = nullptr;
core::ir::Override* x = nullptr;
b.Append(mod.root_block, [&] {
x = b.Override("x", ty.f32());
x->SetOverrideId({2});
auto* add = b.Add(x, 4_f);
o = b.Override(Source{{1, 2}}, "a", ty.f32());
o->SetOverrideId({1});
o->SetInitializer(add->Result());
});
auto* func = b.FragmentFunction("foo", ty.void_());
b.Append(func->Block(), [&] {
b.Let("y", b.Divide(10_f, x));
auto* k = b.Let("k", b.Call(ty.f32(), core::BuiltinFn::kDpdx, x));
b.Let("z", b.Multiply(k, o));
b.Return(func);
});
auto* src = R"(
$B1: { # root
%x:f32 = override undef @id(2)
%2:f32 = add %x, 4.0f
%a:f32 = override %2 @id(1)
}
%foo = @fragment func():void {
$B2: {
%5:f32 = div 10.0f, %x
%y:f32 = let %5
%7:f32 = dpdx %x
%k:f32 = let %7
%9:f32 = mul %k, %a
%z:f32 = let %9
ret
}
}
)";
auto* expect = R"(
%foo = @fragment func():void {
$B1: {
%y:f32 = let 2.0f
%3:f32 = dpdx 5.0f
%k:f32 = let %3
%5:f32 = mul %k, 9.0f
%z:f32 = let %5
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideArraySize) {
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(x->Result());
auto* ary = ty.Get<core::type::Array>(ty.i32(), cnt, 4_u);
b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
});
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] { b.Return(func); });
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%v:ptr<workgroup, array<i32, %x>, read_write> = var undef
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
ret
}
}
)";
auto* expect = R"(
$B1: { # root
%v:ptr<workgroup, array<i32, 5>, read_write> = var undef
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideArraySizeOverrideOutOfBounds) {
ir::Var* v = nullptr;
ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
o = b.Override("y", ty.u32());
o->SetOverrideId({3});
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(x->Result());
auto* ary = ty.Get<core::type::Array>(ty.u32(), cnt, 4_u);
v = b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<workgroup, u32>(), v, o);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%y:u32 = override undef @id(3)
%v:ptr<workgroup, array<u32, %x>, read_write> = var undef
}
%foo = func():u32 {
$B2: {
%5:ptr<workgroup, u32, read_write> = access %v, %y
%6:u32 = load %5
ret %6
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
cfg.map[OverrideId{3}] = 7;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(error: index 7 out of bounds [0..4])");
}
TEST_F(IR_SubstituteOverridesTest, OverrideArraySizeLetOutOfBounds) {
ir::Var* v = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(x->Result());
auto* ary = ty.Get<core::type::Array>(ty.u32(), cnt, 4_u);
v = b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* p = b.Let("p", v);
auto* access = b.Access(ty.ptr<workgroup, u32>(), p, 7_u);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%v:ptr<workgroup, array<u32, %x>, read_write> = var undef
}
%foo = func():u32 {
$B2: {
%p:ptr<workgroup, array<u32, %x>, read_write> = let %v
%5:ptr<workgroup, u32, read_write> = access %p, 7u
%6:u32 = load %5
ret %6
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(error: index 7 out of bounds [0..4])");
}
TEST_F(IR_SubstituteOverridesTest, OverrideArraySizeOutOfBounds) {
ir::Var* v = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(x->Result());
auto* ary = ty.Get<core::type::Array>(ty.u32(), cnt, 4_u);
v = b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<workgroup, u32>(), v, 7_u);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%v:ptr<workgroup, array<u32, %x>, read_write> = var undef
}
%foo = func():u32 {
$B2: {
%4:ptr<workgroup, u32, read_write> = access %v, 7u
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(error: index 7 out of bounds [0..4])");
}
TEST_F(IR_SubstituteOverridesTest, OverrideArraySizeExpression) {
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* inst = b.Multiply(x, 2_u);
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(inst->Result());
auto* ary = ty.Get<core::type::Array>(ty.i32(), cnt, 4_u);
b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
});
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] { b.Return(func); });
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%2:u32 = mul %x, 2u
%v:ptr<workgroup, array<i32, %2>, read_write> = var undef
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
ret
}
}
)";
auto* expect = R"(
$B1: { # root
%v:ptr<workgroup, array<i32, 10>, read_write> = var undef
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideArraySizeIntoLet) {
core::ir::Var* v = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(x->Result());
auto* ary = ty.Get<core::type::Array>(ty.i32(), cnt, 4_u);
v = b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
});
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] {
auto* y = b.Let("y", v);
b.Let("z", y);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%v:ptr<workgroup, array<i32, %x>, read_write> = var undef
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%y:ptr<workgroup, array<i32, %x>, read_write> = let %v
%z:ptr<workgroup, array<i32, %x>, read_write> = let %y
ret
}
}
)";
auto* expect = R"(
$B1: { # root
%v:ptr<workgroup, array<i32, 5>, read_write> = var undef
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%y:ptr<workgroup, array<i32, 5>, read_write> = let %v
%z:ptr<workgroup, array<i32, 5>, read_write> = let %y
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 5;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideCondConstExprSuccess) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* cond = b.Override("cond", ty.bool_());
cond->SetOverrideId({0});
auto* one_f32 = b.Override("one_f32", 1_f);
one_f32->SetOverrideId({2});
auto* constexpr_if = b.ConstExprIf(cond);
constexpr_if->SetResult(b.InstructionResult(ty.bool_()));
b.Append(constexpr_if->True(), [&] {
auto* three = b.Divide(one_f32, 0.0_f);
auto* four = b.Equal(three, 0.0_f);
b.ExitIf(constexpr_if, four);
});
b.Append(constexpr_if->False(), [&] { b.ExitIf(constexpr_if, false); });
o = b.Override(Source{{1, 2}}, "foo", ty.bool_());
o->SetOverrideId({1});
o->SetInitializer(constexpr_if->Result());
});
auto* func = b.Function("foo2", ty.bool_());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%cond:bool = override undef @id(0)
%one_f32:f32 = override 1.0f @id(2)
%3:bool = constexpr_if %cond [t: $B2, f: $B3] { # constexpr_if_1
$B2: { # true
%4:f32 = div %one_f32, 0.0f
%5:bool = eq %4, 0.0f
exit_if %5 # constexpr_if_1
}
$B3: { # false
exit_if false # constexpr_if_1
}
}
%foo:bool = override %3 @id(1)
}
%foo2 = func():bool {
$B4: {
ret %foo
}
}
)";
auto* expect = R"(
%foo2 = func():bool {
$B1: {
ret false
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{0}] = 0;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideCondConstExprFailure) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* cond = b.Override("cond", ty.bool_());
cond->SetOverrideId({0});
auto* one_f32 = b.Override("one_f32", 1_f);
one_f32->SetOverrideId({2});
auto* constexpr_if = b.ConstExprIf(cond);
constexpr_if->SetResult(b.InstructionResult(ty.bool_()));
b.Append(constexpr_if->True(), [&] {
auto* three = b.Divide(one_f32, 0.0_f);
auto* four = b.Equal(three, 0.0_f);
b.ExitIf(constexpr_if, four);
});
b.Append(constexpr_if->False(), [&] { b.ExitIf(constexpr_if, false); });
o = b.Override(Source{{1, 2}}, "foo", ty.bool_());
o->SetOverrideId({1});
o->SetInitializer(constexpr_if->Result());
});
auto* func = b.Function("foo2", ty.bool_());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%cond:bool = override undef @id(0)
%one_f32:f32 = override 1.0f @id(2)
%3:bool = constexpr_if %cond [t: $B2, f: $B3] { # constexpr_if_1
$B2: { # true
%4:f32 = div %one_f32, 0.0f
%5:bool = eq %4, 0.0f
exit_if %5 # constexpr_if_1
}
$B3: { # false
exit_if false # constexpr_if_1
}
}
%foo:bool = override %3 @id(1)
}
%foo2 = func():bool {
$B4: {
ret %foo
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{0}] = 1;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(error: '1.0 / 0.0' cannot be represented as 'f32')");
}
TEST_F(IR_SubstituteOverridesTest, OverrideCondComplexConstExprSuccess) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* cond = b.Override("cond", ty.bool_());
cond->SetOverrideId({0});
auto* one_f32 = b.Override("one_f32", 1_f);
one_f32->SetOverrideId({2});
auto* constexpr_if = b.ConstExprIf(cond);
constexpr_if->SetResult(b.InstructionResult(ty.bool_()));
b.Append(constexpr_if->True(), [&] {
auto* three = b.Divide(one_f32, 1.0_f);
auto* four = b.Equal(three, 1.0_f);
b.ExitIf(constexpr_if, four);
});
b.Append(constexpr_if->False(), [&] { b.ExitIf(constexpr_if, true); });
o = b.Override(Source{{1, 2}}, "foo", ty.bool_());
o->SetOverrideId({1});
o->SetInitializer(constexpr_if->Result());
});
auto* func = b.Function("foo2", ty.bool_());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%cond:bool = override undef @id(0)
%one_f32:f32 = override 1.0f @id(2)
%3:bool = constexpr_if %cond [t: $B2, f: $B3] { # constexpr_if_1
$B2: { # true
%4:f32 = div %one_f32, 1.0f
%5:bool = eq %4, 1.0f
exit_if %5 # constexpr_if_1
}
$B3: { # false
exit_if true # constexpr_if_1
}
}
%foo:bool = override %3 @id(1)
}
%foo2 = func():bool {
$B4: {
ret %foo
}
}
)";
auto* expect = R"(
%foo2 = func():bool {
$B1: {
ret true
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{0}] = 1;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideCondComplexConstExprNestedSuccess) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
auto* cond = b.Override("cond", ty.bool_());
cond->SetOverrideId({0});
auto* zero_f32 = b.Override("zero_f32", 0_f);
zero_f32->SetOverrideId({2});
auto* constexpr_if = b.ConstExprIf(cond);
constexpr_if->SetResult(b.InstructionResult(ty.bool_()));
b.Append(constexpr_if->True(), [&] {
// Both sides (t/f) of this ConstExprIf branch will cause division by zero if evaluated.
// However it does not get evaluated if the outer branch constant evaluates to false.
auto* constexpr_if_inner = b.ConstExprIf(cond);
constexpr_if_inner->SetResult(b.InstructionResult(ty.bool_()));
b.Append(constexpr_if_inner->True(), [&] {
auto* bad_eval = b.Divide(1.0_f, zero_f32);
auto* bad_eval_equal = b.Equal(bad_eval, 1.0_f);
b.ExitIf(constexpr_if_inner, bad_eval_equal);
});
b.Append(constexpr_if_inner->False(), [&] {
auto* bad_eval = b.Divide(1.0_f, zero_f32);
auto* bad_eval_equal = b.Equal(bad_eval, 1.0_f);
b.ExitIf(constexpr_if_inner, bad_eval_equal);
});
b.ExitIf(constexpr_if, constexpr_if_inner);
});
b.Append(constexpr_if->False(), [&] { b.ExitIf(constexpr_if, false); });
o = b.Override(Source{{1, 2}}, "foo", ty.bool_());
o->SetOverrideId({1});
o->SetInitializer(constexpr_if->Result());
});
auto* func = b.Function("foo2", ty.bool_());
b.Append(func->Block(), [&] { b.Return(func, o->Result()); });
auto* src = R"(
$B1: { # root
%cond:bool = override undef @id(0)
%zero_f32:f32 = override 0.0f @id(2)
%3:bool = constexpr_if %cond [t: $B2, f: $B3] { # constexpr_if_1
$B2: { # true
%4:bool = constexpr_if %cond [t: $B4, f: $B5] { # constexpr_if_2
$B4: { # true
%5:f32 = div 1.0f, %zero_f32
%6:bool = eq %5, 1.0f
exit_if %6 # constexpr_if_2
}
$B5: { # false
%7:f32 = div 1.0f, %zero_f32
%8:bool = eq %7, 1.0f
exit_if %8 # constexpr_if_2
}
}
exit_if %4 # constexpr_if_1
}
$B3: { # false
exit_if false # constexpr_if_1
}
}
%foo:bool = override %3 @id(1)
}
%foo2 = func():bool {
$B6: {
ret %foo
}
}
)";
auto* expect = R"(
%foo2 = func():bool {
$B1: {
ret false
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{0}] = 0;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, ConstExprIfInsideKernel) {
core::ir::Override* o = nullptr;
core::ir::Override* x = nullptr;
b.Append(mod.root_block, [&] {
x = b.Override("x", ty.u32());
x->SetOverrideId({1});
o = b.Override("y", ty.bool_());
o->SetOverrideId({2});
});
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] {
auto* constexpr_if = b.ConstExprIf(o);
constexpr_if->SetResult(b.InstructionResult(ty.bool_()));
b.Append(constexpr_if->True(), [&] {
auto* k4 = b.Add(10_u, 5_u);
auto* k = b.Divide(k4, x);
auto* k2 = b.Equal(k, 10_u);
b.ExitIf(constexpr_if, k2);
});
b.Append(constexpr_if->False(), [&] { b.ExitIf(constexpr_if, false); });
b.Let("z", constexpr_if);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(1)
%y:bool = override undef @id(2)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%4:bool = constexpr_if %y [t: $B3, f: $B4] { # constexpr_if_1
$B3: { # true
%5:u32 = add 10u, 5u
%6:u32 = div %5, %x
%7:bool = eq %6, 10u
exit_if %7 # constexpr_if_1
}
$B4: { # false
exit_if false # constexpr_if_1
}
}
%z:bool = let %4
ret
}
}
)";
auto* expect = R"(
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B1: {
%z:bool = let false
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{1}] = 0;
cfg.map[OverrideId{2}] = 0;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, ConstExpIfDuplicateUsage) {
core::ir::Override* y = nullptr;
b.Append(mod.root_block, [&] {
y = b.Override("y", ty.bool_());
y->SetOverrideId({1});
});
auto* func = b.ComputeFunction("foo");
b.Append(func->Block(), [&] {
auto* constexpr_if = b.ConstExprIf(y);
constexpr_if->SetResult(b.InstructionResult(ty.bool_()));
b.Append(constexpr_if->True(), [&] {
auto* k4 = b.Divide(10_u, 0_u);
auto* k = b.Add(k4, k4);
auto* k2 = b.Equal(k, 10_u);
b.ExitIf(constexpr_if, k2);
});
b.Append(constexpr_if->False(), [&] { b.ExitIf(constexpr_if, false); });
b.Let("z", constexpr_if);
b.Return(func);
});
auto* src = R"(
$B1: { # root
%y:bool = override undef @id(1)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:bool = constexpr_if %y [t: $B3, f: $B4] { # constexpr_if_1
$B3: { # true
%4:u32 = div 10u, 0u
%5:u32 = add %4, %4
%6:bool = eq %5, 10u
exit_if %6 # constexpr_if_1
}
$B4: { # false
exit_if false # constexpr_if_1
}
}
%z:bool = let %3
ret
}
}
)";
auto* expect = R"(
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B1: {
%z:bool = let false
ret
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{1}] = 0;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideArrayAccessAndFailure) {
core::ir::Override* o = nullptr;
b.Append(mod.root_block, [&] {
o = b.Override("x", ty.u32());
o->SetOverrideId({0});
});
auto* func = b.Function("foo2", ty.u32());
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* access = b.Access(ty.u32(), b.Constant(arr), o);
auto* r = b.Let("q", access);
b.Return(func, r);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(0)
}
%foo2 = func():u32 {
$B2: {
%3:u32 = access array<u32, 4>(1u, 2u, 3u, 4u), %x
%q:u32 = let %3
ret %q
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{0}] = 10;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(error: index 10 out of bounds [0..3])");
}
TEST_F(IR_SubstituteOverridesTest, OverrideRuntimeSizedArrayFailure) {
core::ir::Override* o = nullptr;
core::ir::Var* arr = nullptr;
b.Append(mod.root_block, [&] {
o = b.Override("x", ty.i32());
o->SetOverrideId({0});
arr = b.Var("arr", ty.ptr(storage, ty.array<u32>()));
arr->SetBindingPoint(0, 0);
});
auto* func = b.Function("foo2", ty.u32());
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<storage, u32>(), arr, o);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%x:i32 = override undef @id(0)
%arr:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo2 = func():u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %arr, %x
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{0}] = -10;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(error: index -10 out of bounds)");
}
TEST_F(IR_SubstituteOverridesTest, OverrideConstruct) {
core::ir::Var* global = nullptr;
b.Append(mod.root_block, [&] {
auto* o0 = b.Override("o0", ty.f16());
o0->SetOverrideId({0});
auto* o1 = b.Override("o1", ty.f16());
o1->SetOverrideId({1});
auto* o2 = b.Override("o2", ty.f16());
o2->SetOverrideId({2});
auto* o3 = b.Override("o3", ty.f16());
o3->SetOverrideId({3});
auto* e = b.Construct(ty.vec4h(), o0, o1, o2, o3);
// auto* e = b.Splat(ty.vec4h(), 1.0_h);
auto* call_func = b.Call(ty.vec4(ty.f16()), core::BuiltinFn::kCeil, e);
global = b.Var<private_>("global", call_func->Result());
// global = b.Var<private_>("global", e);//e->Result());
});
auto* func = b.Function("foo2", ty.vec4(ty.f16()));
b.Append(func->Block(), [&] {
auto* inst = b.Load(global);
b.Return(func, inst->Result());
});
auto* src = R"(
$B1: { # root
%o0:f16 = override undef @id(0)
%o1:f16 = override undef @id(1)
%o2:f16 = override undef @id(2)
%o3:f16 = override undef @id(3)
%5:vec4<f16> = construct %o0, %o1, %o2, %o3
%6:vec4<f16> = ceil %5
%global:ptr<private, vec4<f16>, read_write> = var %6
}
%foo2 = func():vec4<f16> {
$B2: {
%9:vec4<f16> = load %global
ret %9
}
}
)";
EXPECT_EQ(src, str());
auto* expect = R"(
$B1: { # root
%global:ptr<private, vec4<f16>, read_write> = var vec4<f16>(2.0h, 2.0h, 3.0h, 4.0h)
}
%foo2 = func():vec4<f16> {
$B2: {
%3:vec4<f16> = load %global
ret %3
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{0}] = 1.3;
cfg.map[OverrideId{1}] = 2;
cfg.map[OverrideId{2}] = 3;
cfg.map[OverrideId{3}] = 4;
Run(SubstituteOverrides, cfg);
EXPECT_EQ(expect, str());
}
TEST_F(IR_SubstituteOverridesTest, OverrideInvalidRepresentationU32) {
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = -100.0;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(
result.Failure().reason,
R"(error: Pipeline overridable constant 2 with value (-100.0) is not representable in type (u32))");
}
TEST_F(IR_SubstituteOverridesTest, OverrideInvalidRepresentationI32) {
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.i32());
x->SetOverrideId({2});
});
auto* src = R"(
$B1: { # root
%x:i32 = override undef @id(2)
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 8'000'000'000.0;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(
result.Failure().reason,
R"(error: Pipeline overridable constant 2 with value (8000000000.0) is not representable in type (i32))");
}
TEST_F(IR_SubstituteOverridesTest, OverrideInvalidRepresentationF32) {
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.f32());
x->SetOverrideId({2});
});
auto* src = R"(
$B1: { # root
%x:f32 = override undef @id(2)
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 3.14e40;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(
result.Failure().reason,
R"(error: Pipeline overridable constant 2 with value (31399999999999998802000170346751583059968.0) is not representable in type (f32))");
}
TEST_F(IR_SubstituteOverridesTest, OverrideInvalidRepresentationF16) {
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.f16());
x->SetOverrideId({2});
});
auto* src = R"(
$B1: { # root
%x:f16 = override undef @id(2)
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 65505;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(
result.Failure().reason,
R"(error: Pipeline overridable constant 2 with value (65505.0) is not representable in type (f16))");
}
TEST_F(IR_SubstituteOverridesTest, OverrideArraySizeZeroFailure) {
ir::Var* v = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.u32());
x->SetOverrideId({2});
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(x->Result());
mod.SetSource(cnt->value, Source{{5, 8}});
auto* ary = ty.Get<core::type::Array>(ty.u32(), cnt, 4_u);
v = b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
mod.SetSource(v, Source{{3, 2}});
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<workgroup, u32>(), v, 0_u);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%x:u32 = override undef @id(2)
%v:ptr<workgroup, array<u32, %x>, read_write> = var undef
}
%foo = func():u32 {
$B2: {
%4:ptr<workgroup, u32, read_write> = access %v, 0u
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = 0;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(5:8 error: array count (0) must be greater than 0)");
}
TEST_F(IR_SubstituteOverridesTest, OverrideArraySizeNegativeFailure) {
ir::Var* v = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.i32());
x->SetOverrideId({2});
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(x->Result());
mod.SetSource(cnt->value, Source{{5, 8}});
auto* ary = ty.Get<core::type::Array>(ty.u32(), cnt, 4_u);
v = b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
mod.SetSource(v, Source{{3, 2}});
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<workgroup, u32>(), v, 0_u);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%x:i32 = override undef @id(2)
%v:ptr<workgroup, array<u32, %x>, read_write> = var undef
}
%foo = func():u32 {
$B2: {
%4:ptr<workgroup, u32, read_write> = access %v, 0u
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{2}] = -1;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(5:8 error: array count (-1) must be greater than 0)");
}
// See https://crbug.com/483751167
TEST_F(IR_SubstituteOverridesTest, OverrideArraySizeOverflow) {
ir::Var* v = nullptr;
b.Append(mod.root_block, [&] {
auto* x = b.Override("x", ty.i32());
x->SetOverrideId({0});
auto* cnt = ty.Get<core::ir::type::ValueArrayCount>(x->Result());
mod.SetSource(cnt->value, Source{{5, 8}});
auto* ary = ty.Get<core::type::Array>(ty.u32(), cnt, 4_u);
v = b.Var("v", ty.ptr(core::AddressSpace::kWorkgroup, ary, core::Access::kReadWrite));
mod.SetSource(v, Source{{3, 2}});
});
auto* func = b.Function("foo", ty.u32());
b.Append(func->Block(), [&] {
auto* access = b.Access(ty.ptr<workgroup, u32>(), v, 10000_u);
auto* load = b.Load(access);
b.Return(func, load);
});
auto* src = R"(
$B1: { # root
%x:i32 = override undef @id(0)
%v:ptr<workgroup, array<u32, %x>, read_write> = var undef
}
%foo = func():u32 {
$B2: {
%4:ptr<workgroup, u32, read_write> = access %v, 10000u
%5:u32 = load %4
ret %5
}
}
)";
EXPECT_EQ(src, str());
SubstituteOverridesConfig cfg{};
cfg.map[OverrideId{0}] = 1'073'741'825;
auto result = RunWithFailure(SubstituteOverrides, cfg);
ASSERT_NE(result, Success);
EXPECT_EQ(result.Failure().reason, R"(5:8 error: array size (4294967300) is too large)");
}
} // namespace
} // namespace tint::core::ir::transform