ast: Tweak cloning rules for module-scope decls
Previously the Clone() of the AST would clone all the functions, globals
and type declarations in a temporary vector, then assign this to the
ast::Module. This meant that adding new module-scope declarations inside
callbacks of ReplaceAll() would place them right at the top, before any
of the cloned declarations.
As top-level declarations are not statements, ensuring that a new object
comes before the current ReplaceAll() declaration is surprisingly
tricky.
With this change, we can now safely assume that calling
ProgramBuilder::Var(), ProgramBuilder::Func(), ProgramBuilder::Alias()
or ProgramBuilder::Structure() inside a ReplaceAll() will add that
module-scoped declaration before the currently processed top-level
declaration.
Change-Id: I52772fdc85940c8ac8d941fbd53374a4dd64a9f4
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/54320
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/ast/module.cc b/src/ast/module.cc
index e85c033..12c8eba 100644
--- a/src/ast/module.cc
+++ b/src/ast/module.cc
@@ -69,6 +69,7 @@
void Module::AddTypeDecl(ast::TypeDecl* type) {
TINT_ASSERT(type);
+ TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(type, program_id());
type_decls_.push_back(type);
global_declarations_.push_back(type);
}
@@ -87,17 +88,21 @@
}
void Module::Copy(CloneContext* ctx, const Module* src) {
- for (auto* decl : ctx->Clone(src->global_declarations_)) {
+ ctx->Clone(global_declarations_, src->global_declarations_);
+ for (auto* decl : global_declarations_) {
if (!decl) {
TINT_ICE(ctx->dst->Diagnostics()) << "src global declaration was nullptr";
continue;
}
- if (auto* ty = decl->As<ast::TypeDecl>()) {
- AddTypeDecl(ty);
+ if (auto* type = decl->As<ast::TypeDecl>()) {
+ TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(type, program_id());
+ type_decls_.push_back(type);
} else if (auto* func = decl->As<Function>()) {
- AddFunction(func);
+ TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(func, program_id());
+ functions_.push_back(func);
} else if (auto* var = decl->As<Variable>()) {
- AddGlobalVariable(var);
+ TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(var, program_id());
+ global_variables_.push_back(var);
} else {
TINT_ICE(ctx->dst->Diagnostics()) << "Unknown global declaration type";
}
diff --git a/src/ast/module_test.cc b/src/ast/module_test.cc
index 28bf548..510844a 100644
--- a/src/ast/module_test.cc
+++ b/src/ast/module_test.cc
@@ -14,6 +14,7 @@
#include "gtest/gtest-spi.h"
#include "src/ast/test_helper.h"
+#include "src/clone_context.h"
namespace tint {
namespace ast {
@@ -96,6 +97,53 @@
"internal compiler error");
}
+TEST_F(ModuleTest, CloneOrder) {
+ // Create a program with a function, alias decl and var decl.
+ Program p = [] {
+ ProgramBuilder b;
+ b.Func("F", {}, b.ty.void_(), {});
+ b.Alias("A", b.ty.u32());
+ b.Global("V", b.ty.i32(), ast::StorageClass::kPrivate);
+ return Program(std::move(b));
+ }();
+
+ // Clone the program, using ReplaceAll() to create new module-scope
+ // declarations. We want to test that these are added just before the
+ // declaration that triggered the ReplaceAll().
+ ProgramBuilder cloned;
+ CloneContext ctx(&cloned, &p);
+ ctx.ReplaceAll([&](ast::Function*) -> ast::Function* {
+ ctx.dst->Alias("inserted_before_F", cloned.ty.u32());
+ return nullptr;
+ });
+ ctx.ReplaceAll([&](ast::Alias*) -> ast::Alias* {
+ ctx.dst->Alias("inserted_before_A", cloned.ty.u32());
+ return nullptr;
+ });
+ ctx.ReplaceAll([&](ast::Variable*) -> ast::Variable* {
+ ctx.dst->Alias("inserted_before_V", cloned.ty.u32());
+ return nullptr;
+ });
+ ctx.Clone();
+
+ auto& decls = cloned.AST().GlobalDeclarations();
+ ASSERT_EQ(decls.size(), 6u);
+ EXPECT_TRUE(decls[1]->Is<ast::Function>());
+ EXPECT_TRUE(decls[3]->Is<ast::Alias>());
+ EXPECT_TRUE(decls[5]->Is<ast::Variable>());
+
+ ASSERT_TRUE(decls[0]->Is<ast::Alias>());
+ ASSERT_TRUE(decls[2]->Is<ast::Alias>());
+ ASSERT_TRUE(decls[4]->Is<ast::Alias>());
+
+ ASSERT_EQ(cloned.Symbols().NameFor(decls[0]->As<ast::Alias>()->name()),
+ "inserted_before_F");
+ ASSERT_EQ(cloned.Symbols().NameFor(decls[2]->As<ast::Alias>()->name()),
+ "inserted_before_A");
+ ASSERT_EQ(cloned.Symbols().NameFor(decls[4]->As<ast::Alias>()->name()),
+ "inserted_before_V");
+}
+
} // namespace
} // namespace ast
} // namespace tint
diff --git a/src/clone_context.h b/src/clone_context.h
index 0b20d25..78b47f6 100644
--- a/src/clone_context.h
+++ b/src/clone_context.h
@@ -210,7 +210,7 @@
return out;
}
- /// Clones each of the elements of the vector `v` into the ProgramBuilder
+ /// Clones each of the elements of the vector `v` using the ProgramBuilder
/// #dst, inserting any additional elements into the list that were registered
/// with calls to InsertBefore().
///
@@ -221,40 +221,53 @@
template <typename T>
std::vector<T*> Clone(const std::vector<T*>& v) {
std::vector<T*> out;
- out.reserve(v.size());
+ Clone(out, v);
+ return out;
+ }
- auto list_transform_it = list_transforms_.find(&v);
+ /// Clones each of the elements of the vector `from` into the vector `to`,
+ /// inserting any additional elements into the list that were registered with
+ /// calls to InsertBefore().
+ ///
+ /// All the elements of the vector `from` must be owned by the Program #src.
+ ///
+ /// @param from the vector to clone
+ /// @param to the cloned result
+ template <typename T>
+ void Clone(std::vector<T*>& to, const std::vector<T*>& from) {
+ to.reserve(from.size());
+
+ auto list_transform_it = list_transforms_.find(&from);
if (list_transform_it != list_transforms_.end()) {
const auto& transforms = list_transform_it->second;
for (auto* o : transforms.insert_front_) {
- out.emplace_back(CheckedCast<T>(o));
+ to.emplace_back(CheckedCast<T>(o));
}
- for (auto& el : v) {
+ for (auto& el : from) {
auto insert_before_it = transforms.insert_before_.find(el);
if (insert_before_it != transforms.insert_before_.end()) {
for (auto insert : insert_before_it->second) {
- out.emplace_back(CheckedCast<T>(insert));
+ to.emplace_back(CheckedCast<T>(insert));
}
}
if (transforms.remove_.count(el) == 0) {
- out.emplace_back(Clone(el));
+ to.emplace_back(Clone(el));
}
auto insert_after_it = transforms.insert_after_.find(el);
if (insert_after_it != transforms.insert_after_.end()) {
for (auto insert : insert_after_it->second) {
- out.emplace_back(CheckedCast<T>(insert));
+ to.emplace_back(CheckedCast<T>(insert));
}
}
}
for (auto* o : transforms.insert_back_) {
- out.emplace_back(CheckedCast<T>(o));
+ to.emplace_back(CheckedCast<T>(o));
}
} else {
- for (auto& el : v) {
- out.emplace_back(Clone(el));
+ for (auto& el : from) {
+ to.emplace_back(Clone(el));
}
}
- return out;
}
/// Clones each of the elements of the vector `v` into the ProgramBuilder