blob: 88196b39cde37863ad9d4f87ac296164ede45c01 [file] [log] [blame]
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/transform/first_index_offset.h"
#include <memory>
#include <string>
#include <utility>
#include "gtest/gtest.h"
#include "src/ast/block_statement.h"
#include "src/ast/builder.h"
#include "src/ast/builtin.h"
#include "src/ast/builtin_decoration.h"
#include "src/ast/call_expression.h"
#include "src/ast/call_statement.h"
#include "src/ast/function.h"
#include "src/ast/identifier_expression.h"
#include "src/ast/module.h"
#include "src/ast/return_statement.h"
#include "src/ast/storage_class.h"
#include "src/ast/type/u32_type.h"
#include "src/ast/variable.h"
#include "src/ast/variable_decoration.h"
#include "src/demangler.h"
#include "src/diagnostic/formatter.h"
#include "src/source.h"
#include "src/transform/manager.h"
namespace tint {
namespace transform {
namespace {
class FirstIndexOffsetTest : public testing::Test {};
struct ModuleBuilder : public ast::BuilderWithModule {
ast::Module Module() {
Build();
return std::move(*mod);
}
protected:
void AddBuiltinInput(const std::string& name, ast::Builtin builtin) {
mod->AddGlobalVariable(
Var(name, ast::StorageClass::kInput, ty.u32, nullptr,
{create<ast::BuiltinDecoration>(builtin, Source{})}));
}
ast::Function* AddFunction(const std::string& name,
ast::VariableList params = {}) {
auto* func = create<ast::Function>(
Source{}, mod->RegisterSymbol(name), name, std::move(params), ty.u32,
create<ast::BlockStatement>(Source{}), ast::FunctionDecorationList());
mod->AddFunction(func);
return func;
}
virtual void Build() = 0;
};
TEST_F(FirstIndexOffsetTest, Error_AlreadyTransformed) {
struct Builder : public ModuleBuilder {
void Build() override {
AddBuiltinInput("vert_idx", ast::Builtin::kVertexIdx);
AddFunction("test")->body()->append(create<ast::ReturnStatement>(
Source{},
create<ast::IdentifierExpression>(
Source{}, mod->RegisterSymbol("vert_idx"), "vert_idx")));
}
};
Manager manager;
manager.append(std::make_unique<FirstIndexOffset>(0, 0));
manager.append(std::make_unique<FirstIndexOffset>(1, 1));
auto module = Builder{}.Module();
auto result = manager.Run(&module);
// Release the source module to ensure there's no uncloned data in result
{ auto tmp = std::move(module); }
ASSERT_EQ(diag::Formatter().format(result.diagnostics),
"error: First index offset transform has already been applied.");
}
TEST_F(FirstIndexOffsetTest, EmptyModule) {
Manager manager;
manager.append(std::make_unique<FirstIndexOffset>(0, 0));
ast::Module module;
auto result = manager.Run(&module);
// Release the source module to ensure there's no uncloned data in result
{ auto tmp = std::move(module); }
ASSERT_FALSE(result.diagnostics.contains_errors())
<< diag::Formatter().format(result.diagnostics);
auto got = result.module.to_str();
auto* expected = "Module{\n}\n";
EXPECT_EQ(got, expected);
}
TEST_F(FirstIndexOffsetTest, BasicModuleVertexIndex) {
struct Builder : public ModuleBuilder {
void Build() override {
AddBuiltinInput("vert_idx", ast::Builtin::kVertexIdx);
AddFunction("test")->body()->append(create<ast::ReturnStatement>(
Source{},
create<ast::IdentifierExpression>(
Source{}, mod->RegisterSymbol("vert_idx"), "vert_idx")));
}
};
Manager manager;
manager.append(std::make_unique<FirstIndexOffset>(1, 2));
auto module = Builder{}.Module();
auto result = manager.Run(&module);
// Release the source module to ensure there's no uncloned data in result
{ auto tmp = std::move(module); }
ASSERT_FALSE(result.diagnostics.contains_errors())
<< diag::Formatter().format(result.diagnostics);
auto got = result.module.to_str();
auto* expected =
R"(Module{
TintFirstIndexOffsetData Struct{
[[block]]
StructMember{[[ offset 0 ]] tint_first_vertex_index: __u32}
}
Variable{
Decorations{
BuiltinDecoration{vertex_idx}
}
tint_first_index_offset_vert_idx
in
__u32
}
Variable{
Decorations{
BindingDecoration{1}
SetDecoration{2}
}
tint_first_index_data
uniform
__struct_TintFirstIndexOffsetData
}
Function test -> __u32
()
{
VariableDeclStatement{
VariableConst{
vert_idx
none
__u32
{
Binary[__u32]{
Identifier[__ptr_in__u32]{tint_first_index_offset_vert_idx}
add
MemberAccessor[__ptr_uniform__u32]{
Identifier[__ptr_uniform__struct_TintFirstIndexOffsetData]{tint_first_index_data}
Identifier[not set]{tint_first_vertex_index}
}
}
}
}
}
Return{
{
Identifier[__u32]{vert_idx}
}
}
}
}
)";
EXPECT_EQ(Demangler().Demangle(result.module, got), expected);
}
TEST_F(FirstIndexOffsetTest, BasicModuleInstanceIndex) {
struct Builder : public ModuleBuilder {
void Build() override {
AddBuiltinInput("inst_idx", ast::Builtin::kInstanceIdx);
AddFunction("test")->body()->append(create<ast::ReturnStatement>(
Source{},
create<ast::IdentifierExpression>(
Source{}, mod->RegisterSymbol("inst_idx"), "inst_idx")));
}
};
Manager manager;
manager.append(std::make_unique<FirstIndexOffset>(1, 7));
auto module = Builder{}.Module();
auto result = manager.Run(&module);
// Release the source module to ensure there's no uncloned data in result
{ auto tmp = std::move(module); }
ASSERT_FALSE(result.diagnostics.contains_errors())
<< diag::Formatter().format(result.diagnostics);
auto got = result.module.to_str();
auto* expected = R"(Module{
TintFirstIndexOffsetData Struct{
[[block]]
StructMember{[[ offset 0 ]] tint_first_instance_index: __u32}
}
Variable{
Decorations{
BuiltinDecoration{instance_idx}
}
tint_first_index_offset_inst_idx
in
__u32
}
Variable{
Decorations{
BindingDecoration{1}
SetDecoration{7}
}
tint_first_index_data
uniform
__struct_TintFirstIndexOffsetData
}
Function test -> __u32
()
{
VariableDeclStatement{
VariableConst{
inst_idx
none
__u32
{
Binary[__u32]{
Identifier[__ptr_in__u32]{tint_first_index_offset_inst_idx}
add
MemberAccessor[__ptr_uniform__u32]{
Identifier[__ptr_uniform__struct_TintFirstIndexOffsetData]{tint_first_index_data}
Identifier[not set]{tint_first_instance_index}
}
}
}
}
}
Return{
{
Identifier[__u32]{inst_idx}
}
}
}
}
)";
EXPECT_EQ(Demangler().Demangle(result.module, got), expected);
}
TEST_F(FirstIndexOffsetTest, BasicModuleBothIndex) {
struct Builder : public ModuleBuilder {
void Build() override {
AddBuiltinInput("inst_idx", ast::Builtin::kInstanceIdx);
AddBuiltinInput("vert_idx", ast::Builtin::kVertexIdx);
AddFunction("test")->body()->append(
create<ast::ReturnStatement>(Source{}, Expr(1u)));
}
};
auto transform = std::make_unique<FirstIndexOffset>(1, 7);
auto* transform_ptr = transform.get();
Manager manager;
manager.append(std::move(transform));
auto module = Builder{}.Module();
auto result = manager.Run(&module);
// Release the source module to ensure there's no uncloned data in result
{ auto tmp = std::move(module); }
ASSERT_FALSE(result.diagnostics.contains_errors())
<< diag::Formatter().format(result.diagnostics);
auto got = result.module.to_str();
auto* expected = R"(Module{
TintFirstIndexOffsetData Struct{
[[block]]
StructMember{[[ offset 0 ]] tint_first_vertex_index: __u32}
StructMember{[[ offset 4 ]] tint_first_instance_index: __u32}
}
Variable{
Decorations{
BuiltinDecoration{instance_idx}
}
tint_first_index_offset_inst_idx
in
__u32
}
Variable{
Decorations{
BuiltinDecoration{vertex_idx}
}
tint_first_index_offset_vert_idx
in
__u32
}
Variable{
Decorations{
BindingDecoration{1}
SetDecoration{7}
}
tint_first_index_data
uniform
__struct_TintFirstIndexOffsetData
}
Function test -> __u32
()
{
Return{
{
ScalarConstructor[__u32]{1}
}
}
}
}
)";
EXPECT_EQ(Demangler().Demangle(result.module, got), expected);
EXPECT_TRUE(transform_ptr->HasVertexIndex());
EXPECT_EQ(transform_ptr->GetFirstVertexOffset(), 0u);
EXPECT_TRUE(transform_ptr->HasInstanceIndex());
EXPECT_EQ(transform_ptr->GetFirstInstanceOffset(), 4u);
}
TEST_F(FirstIndexOffsetTest, NestedCalls) {
struct Builder : public ModuleBuilder {
void Build() override {
AddBuiltinInput("vert_idx", ast::Builtin::kVertexIdx);
ast::Function* func1 = AddFunction("func1");
func1->body()->append(create<ast::ReturnStatement>(
Source{},
create<ast::IdentifierExpression>(
Source{}, mod->RegisterSymbol("vert_idx"), "vert_idx")));
ast::Function* func2 = AddFunction("func2");
func2->body()->append(create<ast::ReturnStatement>(
Source{}, create<ast::CallExpression>(
Source{},
create<ast::IdentifierExpression>(
Source{}, mod->RegisterSymbol("func1"), "func1"),
ast::ExpressionList{})));
}
};
auto transform = std::make_unique<FirstIndexOffset>(2, 2);
Manager manager;
manager.append(std::move(transform));
auto module = Builder{}.Module();
auto result = manager.Run(&module);
// Release the source module to ensure there's no uncloned data in result
{ auto tmp = std::move(module); }
ASSERT_FALSE(result.diagnostics.contains_errors())
<< diag::Formatter().format(result.diagnostics);
auto got = result.module.to_str();
auto* expected = R"(Module{
TintFirstIndexOffsetData Struct{
[[block]]
StructMember{[[ offset 0 ]] tint_first_vertex_index: __u32}
}
Variable{
Decorations{
BuiltinDecoration{vertex_idx}
}
tint_first_index_offset_vert_idx
in
__u32
}
Variable{
Decorations{
BindingDecoration{2}
SetDecoration{2}
}
tint_first_index_data
uniform
__struct_TintFirstIndexOffsetData
}
Function func1 -> __u32
()
{
VariableDeclStatement{
VariableConst{
vert_idx
none
__u32
{
Binary[__u32]{
Identifier[__ptr_in__u32]{tint_first_index_offset_vert_idx}
add
MemberAccessor[__ptr_uniform__u32]{
Identifier[__ptr_uniform__struct_TintFirstIndexOffsetData]{tint_first_index_data}
Identifier[not set]{tint_first_vertex_index}
}
}
}
}
}
Return{
{
Identifier[__u32]{vert_idx}
}
}
}
Function func2 -> __u32
()
{
Return{
{
Call[__u32]{
Identifier[__u32]{func1}
(
)
}
}
}
}
}
)";
EXPECT_EQ(Demangler().Demangle(result.module, got), expected);
}
} // namespace
} // namespace transform
} // namespace tint