resolver: Deterministic constant ID allocation
Use global declaration order for allocating implicit pipeline-overridable constant IDs, instead of iterating over an unordered_map. Bug: tint:1155 Change-Id: Ia5ff534c617b0d57e45fc20dd0a5a591854e6473 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/65522 Kokoro: Kokoro <noreply+kokoro@google.com> Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
parent
5ffae32177
commit
ee23c17345
|
@ -14,16 +14,26 @@
|
||||||
|
|
||||||
#include "src/resolver/resolver.h"
|
#include "src/resolver/resolver.h"
|
||||||
|
|
||||||
#include "gmock/gmock.h"
|
|
||||||
#include "src/resolver/resolver_test_helper.h"
|
#include "src/resolver/resolver_test_helper.h"
|
||||||
|
|
||||||
using ::testing::UnorderedElementsAre;
|
|
||||||
|
|
||||||
namespace tint {
|
namespace tint {
|
||||||
namespace resolver {
|
namespace resolver {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
using ResolverPipelineOverridableConstantTest = ResolverTest;
|
class ResolverPipelineOverridableConstantTest : public ResolverTest {
|
||||||
|
protected:
|
||||||
|
/// Verify that the AST node `var` was resolved to an overridable constant
|
||||||
|
/// with an ID equal to `id`.
|
||||||
|
/// @param var the overridable constant AST node
|
||||||
|
/// @param id the expected constant ID
|
||||||
|
void ExpectConstantId(const ast::Variable* var, uint16_t id) {
|
||||||
|
auto* sem = Sem().Get<sem::GlobalVariable>(var);
|
||||||
|
ASSERT_NE(sem, nullptr);
|
||||||
|
EXPECT_EQ(sem->Declaration(), var);
|
||||||
|
EXPECT_TRUE(sem->IsPipelineConstant());
|
||||||
|
EXPECT_EQ(sem->ConstantId(), id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
TEST_F(ResolverPipelineOverridableConstantTest, NonOverridable) {
|
TEST_F(ResolverPipelineOverridableConstantTest, NonOverridable) {
|
||||||
auto* a = GlobalConst("a", ty.f32(), Construct(ty.f32()));
|
auto* a = GlobalConst("a", ty.f32(), Construct(ty.f32()));
|
||||||
|
@ -41,11 +51,7 @@ TEST_F(ResolverPipelineOverridableConstantTest, WithId) {
|
||||||
|
|
||||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
|
||||||
auto* sem_a = Sem().Get<sem::GlobalVariable>(a);
|
ExpectConstantId(a, 7u);
|
||||||
ASSERT_NE(sem_a, nullptr);
|
|
||||||
EXPECT_EQ(sem_a->Declaration(), a);
|
|
||||||
EXPECT_TRUE(sem_a->IsPipelineConstant());
|
|
||||||
EXPECT_EQ(sem_a->ConstantId(), 7u);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ResolverPipelineOverridableConstantTest, WithoutId) {
|
TEST_F(ResolverPipelineOverridableConstantTest, WithoutId) {
|
||||||
|
@ -53,37 +59,27 @@ TEST_F(ResolverPipelineOverridableConstantTest, WithoutId) {
|
||||||
|
|
||||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
|
||||||
auto* sem_a = Sem().Get<sem::GlobalVariable>(a);
|
ExpectConstantId(a, 0u);
|
||||||
ASSERT_NE(sem_a, nullptr);
|
|
||||||
EXPECT_EQ(sem_a->Declaration(), a);
|
|
||||||
EXPECT_TRUE(sem_a->IsPipelineConstant());
|
|
||||||
EXPECT_EQ(sem_a->ConstantId(), 0u);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ResolverPipelineOverridableConstantTest, WithAndWithoutIds) {
|
TEST_F(ResolverPipelineOverridableConstantTest, WithAndWithoutIds) {
|
||||||
std::vector<ast::Variable*> variables;
|
std::vector<ast::Variable*> variables;
|
||||||
variables.push_back(
|
auto* a = GlobalConst("a", ty.f32(), Construct(ty.f32()), {Override()});
|
||||||
GlobalConst("a", ty.f32(), Construct(ty.f32()), {Override()}));
|
auto* b = GlobalConst("b", ty.f32(), Construct(ty.f32()), {Override()});
|
||||||
variables.push_back(
|
auto* c = GlobalConst("c", ty.f32(), Construct(ty.f32()), {Override(2u)});
|
||||||
GlobalConst("b", ty.f32(), Construct(ty.f32()), {Override()}));
|
auto* d = GlobalConst("d", ty.f32(), Construct(ty.f32()), {Override(4u)});
|
||||||
variables.push_back(
|
auto* e = GlobalConst("e", ty.f32(), Construct(ty.f32()), {Override()});
|
||||||
GlobalConst("c", ty.f32(), Construct(ty.f32()), {Override(2u)}));
|
auto* f = GlobalConst("f", ty.f32(), Construct(ty.f32()), {Override(1u)});
|
||||||
variables.push_back(
|
|
||||||
GlobalConst("d", ty.f32(), Construct(ty.f32()), {Override(4u)}));
|
|
||||||
variables.push_back(
|
|
||||||
GlobalConst("e", ty.f32(), Construct(ty.f32()), {Override()}));
|
|
||||||
variables.push_back(
|
|
||||||
GlobalConst("f", ty.f32(), Construct(ty.f32()), {Override(1u)}));
|
|
||||||
|
|
||||||
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
EXPECT_TRUE(r()->Resolve()) << r()->error();
|
||||||
|
|
||||||
std::vector<uint16_t> constant_ids;
|
// Verify that constant id allocation order is deterministic.
|
||||||
for (auto* var : variables) {
|
ExpectConstantId(a, 0u);
|
||||||
auto* sem = Sem().Get<sem::GlobalVariable>(var);
|
ExpectConstantId(b, 3u);
|
||||||
ASSERT_NE(sem, nullptr);
|
ExpectConstantId(c, 2u);
|
||||||
constant_ids.push_back(static_cast<uint16_t>(sem->ConstantId()));
|
ExpectConstantId(d, 4u);
|
||||||
}
|
ExpectConstantId(e, 5u);
|
||||||
EXPECT_THAT(constant_ids, UnorderedElementsAre(0u, 3u, 2u, 4u, 5u, 1u));
|
ExpectConstantId(f, 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ResolverPipelineOverridableConstantTest, DuplicateIds) {
|
TEST_F(ResolverPipelineOverridableConstantTest, DuplicateIds) {
|
||||||
|
|
|
@ -264,6 +264,8 @@ bool Resolver::ResolveInternal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllocateOverridableConstantIds();
|
||||||
|
|
||||||
if (!ValidatePipelineStages()) {
|
if (!ValidatePipelineStages()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -576,6 +578,46 @@ ast::Access Resolver::DefaultAccessForStorageClass(
|
||||||
return ast::Access::kReadWrite;
|
return ast::Access::kReadWrite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Resolver::AllocateOverridableConstantIds() {
|
||||||
|
// The next pipeline constant ID to try to allocate.
|
||||||
|
uint16_t next_constant_id = 0;
|
||||||
|
|
||||||
|
// Allocate constant IDs in global declaration order, so that they are
|
||||||
|
// deterministic.
|
||||||
|
// TODO(crbug.com/tint/1192): If a transform changes the order or removes an
|
||||||
|
// unused constant, the allocation may change on the next Resolver pass.
|
||||||
|
for (auto* decl : builder_->AST().GlobalDeclarations()) {
|
||||||
|
auto* var = decl->As<ast::Variable>();
|
||||||
|
if (!var) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto* override_deco =
|
||||||
|
ast::GetDecoration<ast::OverrideDecoration>(var->decorations());
|
||||||
|
if (!override_deco) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t constant_id;
|
||||||
|
if (override_deco->HasValue()) {
|
||||||
|
constant_id = static_cast<uint16_t>(override_deco->value());
|
||||||
|
} else {
|
||||||
|
// No ID was specified, so allocate the next available ID.
|
||||||
|
constant_id = next_constant_id;
|
||||||
|
while (constant_ids_.count(constant_id)) {
|
||||||
|
if (constant_id == UINT16_MAX) {
|
||||||
|
TINT_ICE(Resolver, builder_->Diagnostics())
|
||||||
|
<< "no more pipeline constant IDs available";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
constant_id++;
|
||||||
|
}
|
||||||
|
next_constant_id = constant_id + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
variable_to_info_[var]->constant_id = constant_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Resolver::ValidateVariableConstructor(const ast::Variable* var,
|
bool Resolver::ValidateVariableConstructor(const ast::Variable* var,
|
||||||
ast::StorageClass storage_class,
|
ast::StorageClass storage_class,
|
||||||
const sem::Type* storage_type,
|
const sem::Type* storage_type,
|
||||||
|
@ -3709,9 +3751,6 @@ void Resolver::CreateSemanticNodes() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The next pipeline constant ID to try to allocate.
|
|
||||||
uint16_t next_constant_id = 0;
|
|
||||||
|
|
||||||
// Create semantic nodes for all ast::Variables
|
// Create semantic nodes for all ast::Variables
|
||||||
std::unordered_map<const tint::ast::Variable*, sem::Parameter*> sem_params;
|
std::unordered_map<const tint::ast::Variable*, sem::Parameter*> sem_params;
|
||||||
for (auto it : variable_to_info_) {
|
for (auto it : variable_to_info_) {
|
||||||
|
@ -3720,28 +3759,10 @@ void Resolver::CreateSemanticNodes() const {
|
||||||
|
|
||||||
sem::Variable* sem_var = nullptr;
|
sem::Variable* sem_var = nullptr;
|
||||||
|
|
||||||
if (auto* override_deco =
|
if (ast::HasDecoration<ast::OverrideDecoration>(var->decorations())) {
|
||||||
ast::GetDecoration<ast::OverrideDecoration>(var->decorations())) {
|
|
||||||
// Create a pipeline overridable constant.
|
// Create a pipeline overridable constant.
|
||||||
uint16_t constant_id;
|
sem_var = builder_->create<sem::GlobalVariable>(var, info->type,
|
||||||
if (override_deco->HasValue()) {
|
info->constant_id);
|
||||||
constant_id = static_cast<uint16_t>(override_deco->value());
|
|
||||||
} else {
|
|
||||||
// No ID was specified, so allocate the next available ID.
|
|
||||||
constant_id = next_constant_id;
|
|
||||||
while (constant_ids_.count(constant_id)) {
|
|
||||||
if (constant_id == UINT16_MAX) {
|
|
||||||
TINT_ICE(Resolver, builder_->Diagnostics())
|
|
||||||
<< "no more pipeline constant IDs available";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
constant_id++;
|
|
||||||
}
|
|
||||||
next_constant_id = constant_id + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
sem_var =
|
|
||||||
builder_->create<sem::GlobalVariable>(var, info->type, constant_id);
|
|
||||||
} else {
|
} else {
|
||||||
switch (info->kind) {
|
switch (info->kind) {
|
||||||
case VariableKind::kGlobal:
|
case VariableKind::kGlobal:
|
||||||
|
|
|
@ -116,6 +116,7 @@ class Resolver {
|
||||||
sem::BindingPoint binding_point;
|
sem::BindingPoint binding_point;
|
||||||
VariableKind kind;
|
VariableKind kind;
|
||||||
uint32_t index = 0; // Parameter index, if kind == kParameter
|
uint32_t index = 0; // Parameter index, if kind == kParameter
|
||||||
|
uint16_t constant_id = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct IntrinsicCallInfo {
|
struct IntrinsicCallInfo {
|
||||||
|
@ -391,6 +392,9 @@ class Resolver {
|
||||||
/// @returns the default access control for the given storage class
|
/// @returns the default access control for the given storage class
|
||||||
ast::Access DefaultAccessForStorageClass(ast::StorageClass storage_class);
|
ast::Access DefaultAccessForStorageClass(ast::StorageClass storage_class);
|
||||||
|
|
||||||
|
/// Allocate constant IDs for pipeline-overridable constants.
|
||||||
|
void AllocateOverridableConstantIds();
|
||||||
|
|
||||||
/// @returns the resolved type of the ast::Expression `expr`
|
/// @returns the resolved type of the ast::Expression `expr`
|
||||||
/// @param expr the expression
|
/// @param expr the expression
|
||||||
sem::Type* TypeOf(const ast::Expression* expr);
|
sem::Type* TypeOf(const ast::Expression* expr);
|
||||||
|
|
Loading…
Reference in New Issue