From 04b2082b0ec933fc97eaba251371e54cfe6b9beb Mon Sep 17 00:00:00 2001
From: clonker <1685266+clonker@users.noreply.github.com>
Date: Tue, 6 Aug 2024 09:44:14 +0200
Subject: [PATCH] Yul: introduce YulNameRepository
---
libyul/CMakeLists.txt | 2 +
libyul/YulName.cpp | 114 ++++++++++++++++++++++++++++++++++++++++
libyul/YulName.h | 105 ++++++++++++++++++++++++++++++++++++
test/CMakeLists.txt | 1 +
test/libyul/YulName.cpp | 86 ++++++++++++++++++++++++++++++
5 files changed, 308 insertions(+)
create mode 100644 libyul/YulName.cpp
create mode 100644 test/libyul/YulName.cpp
diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt
index f621b2089e9b..1a470065fd3f 100644
--- a/libyul/CMakeLists.txt
+++ b/libyul/CMakeLists.txt
@@ -43,6 +43,8 @@ add_library(yul
YulControlFlowGraphExporter.cpp
YulName.h
YulString.h
+ YulName.cpp
+ YulName.h
backends/evm/AbstractAssembly.h
backends/evm/AsmCodeGen.cpp
backends/evm/AsmCodeGen.h
diff --git a/libyul/YulName.cpp b/libyul/YulName.cpp
new file mode 100644
index 000000000000..fbe890353f35
--- /dev/null
+++ b/libyul/YulName.cpp
@@ -0,0 +1,114 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see .
+*/
+// SPDX-License-Identifier: GPL-3.0
+
+#include
+
+#include
+
+#include
+
+namespace solidity::yul
+{
+
+YulNameRepository::YulNameRepository()
+{
+ auto const emptyName = defineName("");
+ yulAssert(emptyName == YulNameRepository::emptyName());
+}
+
+std::string_view YulNameRepository::requiredLabelOf(YulName const& _name) const
+{
+ auto const label = labelOf(_name);
+ yulAssert(label.has_value(), "YulName currently has no defined label in the YulNameRepository.");
+ return label.value();
+}
+
+std::optional YulNameRepository::nameOfLabel(std::string_view const _label) const
+{
+ auto const it = std::find(m_definedLabels.begin(), m_definedLabels.end(), _label);
+ if (it != m_definedLabels.end())
+ {
+ YulName labelName{static_cast(std::distance(m_definedLabels.begin(), it))};
+ // mostly it'll be iota
+ if (!isDerivedName(labelName) && std::get<0>(m_names[static_cast(labelName)]) == labelName)
+ return std::get<0>(m_names[static_cast(labelName)]);
+ // if not iota, we have to search
+ auto itName = std::find(m_names.rbegin(), m_names.rend(), std::make_tuple(labelName, YulNameState::DEFINED));
+ if (itName != m_names.rend())
+ return std::get<0>(*itName);
+ }
+ return std::nullopt;
+}
+
+void YulNameRepository::generateLabels(std::set const& _usedNames, std::set const& _illegal)
+{
+ std::set used;
+ std::set toDerive;
+ for (auto const name: _usedNames)
+ {
+ if (!isDerivedName(name))
+ {
+ auto const label = labelOf(name);
+ yulAssert(label.has_value());
+ auto const [it, emplaced] = used.emplace(*label);
+ if (!emplaced || _illegal.count(*it) > 0)
+ // there's been a clash ,e.g., by calling generate labels twice;
+ // let's remove this name and derive it instead
+ toDerive.insert(name);
+ }
+ else
+ yulAssert(isDerivedName(name) || _illegal.count(std::string(*labelOf(name))) == 0);
+ }
+
+ std::vector> generated;
+ auto namesIt = _usedNames.begin();
+ for (size_t nameValue = 1; nameValue < m_names.size(); ++nameValue)
+ {
+ auto name = static_cast(nameValue);
+ if (namesIt != _usedNames.end() && name == *namesIt)
+ {
+ if (isDerivedName(name) || toDerive.find(name) != toDerive.end())
+ {
+ std::string const baseLabel(baseLabelOf(name));
+ std::string label (baseLabel);
+ size_t bump = 1;
+ while (used.count(label) > 0 || _illegal.count(label) > 0)
+ label = fmt::format(FMT_COMPILE("{}_{}"), baseLabel, bump++);
+ if (auto const existingDefinedName = nameOfLabel(label); existingDefinedName.has_value())
+ {
+ std::get<0>(m_names[static_cast(name)]) = *existingDefinedName;
+ std::get<1>(m_names[static_cast(name)]) = YulNameState::DEFINED;
+ }
+ else
+ generated.emplace_back(label, name);
+ used.insert(label);
+ }
+ ++namesIt;
+ }
+ }
+
+ for (auto const& [label, name] : generated)
+ {
+ yulAssert(_illegal.count(label) == 0);
+ m_definedLabels.emplace_back(label);
+ std::get<0>(m_names[static_cast(name)]) = static_cast(m_definedLabels.size() - 1);
+ std::get<1>(m_names[static_cast(name)]) = YulNameState::DEFINED;
+ }
+}
+
+}
diff --git a/libyul/YulName.h b/libyul/YulName.h
index 34fd6a5a26b0..b457f9dfef7a 100644
--- a/libyul/YulName.h
+++ b/libyul/YulName.h
@@ -18,9 +18,114 @@
#pragma once
+#include
#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
namespace solidity::yul
{
+
+enum class LiteralKind;
+struct Block;
+
+/**
+ * Yul Name repository.
+ *
+ * A database of numerical identifiers for Yul nodes in an AST (`YulName`s). Identifiers can be
+ *
+ * - 'defined', i.e., they are equipped with a string label, which can be retrieved by a call to `labelOf(yulName)`;
+ * - 'derived', i.e., they don't possess a string label but have a parent yul name which is either also derived or
+ * defined. All dependency chains of derived labels terminate in a defined label.
+ *
+ * Such derived identifiers can be introduced, e.g., during certain optimization steps. If the AST (or segments thereof)
+ * should be printed and/or the string label of identifiers should be retrieved which are still in derived state,
+ * a call to `generateLabels(identifiers)` changes the status of all derived labels to defined and generates
+ * unique labels for all identifiers provided to the method. The generated labels are based on their parents.
+ */
+class YulNameRepository
+{
+public:
+ using YulName = std::uint64_t;
+ YulNameRepository();
+ /// Defines a new name based on a label. If the label already was defined, it returns the corresponding YulName
+ /// instead of a new one (`defineName("xyz") == defineName("xyz")`).
+ YulName defineName(std::string_view _label)
+ {
+ if (auto const name = nameOfLabel(_label); name.has_value())
+ return *name;
+
+ m_definedLabels.emplace_back(_label);
+ m_names.emplace_back(m_definedLabels.size() - 1, YulNameState::DEFINED);
+ return static_cast(m_names.size() - 1);
+ }
+
+ /// Defines a new name based on a parent name. When generating labels, the generated label will be based on the
+ /// parent's (`deriveName(id) != deriveName(id)`).
+ YulName deriveName(YulName const& _baseName)
+ {
+ m_names.emplace_back(baseNameOf(_baseName), YulNameState::DERIVED);
+ return static_cast(m_names.size() - 1);
+ }
+
+ /// The empty name.
+ static constexpr YulName emptyName() { return 0; }
+
+ /// Yields the label of a yul name. The name must have been added via `defineName`, a label must have been
+ /// generated with `generateLabels`, or it is a builtin.
+ std::optional labelOf(YulName _name) const
+ {
+ if (isDerivedName(_name))
+ return std::nullopt;
+
+ auto const labelIndex = std::get<0>(m_names.at(static_cast(_name)));
+ return m_definedLabels.at(static_cast(labelIndex));
+ }
+
+ /// If it can be assumed that the label was already generated, this function will yield it (or fail with an
+ /// assertion error).
+ std::string_view requiredLabelOf(YulName const& _name) const;
+
+ /// Yields the label of the base name of the provided name. Opposed to `labelOf`, this must always exist.
+ std::string_view baseLabelOf(YulName const& _name) const
+ {
+ auto const labelIndex = std::get<0>(m_names.at(static_cast(baseNameOf(_name))));
+ return m_definedLabels[static_cast(labelIndex)];
+ }
+
+ /// Whether a name is considered derived, i.e., has no label but a parent name.
+ bool isDerivedName(YulName const& _name) const
+ {
+ return std::get<1>(m_names.at(static_cast(_name))) == YulNameState::DERIVED;
+ }
+
+ /// Tries to find the label in the defined names and returns the corresponding name.
+ std::optional nameOfLabel(std::string_view label) const;
+
+ /// Generates labels for derived names over the set of _usedNames, respecting a set of _illegal labels.
+ /// This will change the state of all derived names in _usedNames to "not derived" with a label associated to them.
+ void generateLabels(std::set const& _usedNames, std::set const& _illegal = {});
+
+private:
+ enum class YulNameState { DERIVED, DEFINED };
+
+ /// Yields the name that the provided name was based on - or the name itself, if the name was directly "defined".
+ YulName baseNameOf(YulName _name) const
+ {
+ while (isDerivedName(_name))
+ _name = std::get<0>(m_names[static_cast(_name)]);
+ return _name;
+ }
+
+ std::vector m_definedLabels{};
+ std::vector> m_names{};
+};
using YulName = YulString;
+
}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index da115f0d4a5b..2681c347d516 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -164,6 +164,7 @@ set(libyul_sources
libyul/SyntaxTest.cpp
libyul/YulInterpreterTest.cpp
libyul/YulInterpreterTest.h
+ libyul/YulName.cpp
libyul/YulOptimizerTest.cpp
libyul/YulOptimizerTest.h
libyul/YulOptimizerTestCommon.cpp
diff --git a/test/libyul/YulName.cpp b/test/libyul/YulName.cpp
new file mode 100644
index 000000000000..baaad248d2a0
--- /dev/null
+++ b/test/libyul/YulName.cpp
@@ -0,0 +1,86 @@
+/*
+ This file is part of solidity.
+
+ solidity is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ solidity is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with solidity. If not, see .
+*/
+// SPDX-License-Identifier: GPL-3.0
+
+#include
+
+#include
+#include
+#include
+
+using namespace solidity;
+using namespace solidity::yul;
+
+namespace solidity::yul::test
+{
+
+BOOST_AUTO_TEST_SUITE(YulName)
+
+BOOST_AUTO_TEST_CASE(repository_generate_labels_for_derived_types)
+{
+ Dialect dialect{};
+ YulNameRepository nameRepository;
+ auto const test1 = nameRepository.defineName("test1");
+ BOOST_CHECK(!nameRepository.isDerivedName(test1));
+ auto const test1_1 = nameRepository.deriveName(test1);
+ BOOST_CHECK(nameRepository.isDerivedName(test1_1));
+ auto const test1_2 = nameRepository.deriveName(test1);
+ BOOST_CHECK(nameRepository.isDerivedName(test1_2));
+ auto const test1_2_1 = nameRepository.deriveName(test1_2);
+ BOOST_CHECK(nameRepository.isDerivedName(test1_2_1));
+ auto const test2 = nameRepository.defineName("test2_1");
+ auto const test2_1 = nameRepository.deriveName(test2);
+ auto const test3 = nameRepository.defineName("test3");
+ auto const test3_1 = nameRepository.deriveName(test3);
+ BOOST_CHECK(test1 != test1_1);
+ BOOST_CHECK(test1 < test1_1);
+ nameRepository.generateLabels({test1, test1_1, test1_2, test1_2_1, test2_1, test3, test3_1}, {"test1"});
+
+ // marking test1 as invalid means that all labels get bumped up by one
+ BOOST_CHECK(nameRepository.labelOf(test1) == "test1_1");
+ BOOST_CHECK(nameRepository.labelOf(test1_1) == "test1_2");
+ BOOST_CHECK(nameRepository.labelOf(test1_2) == "test1_3");
+ BOOST_CHECK(nameRepository.labelOf(test1_2_1) == "test1_4");
+
+ BOOST_CHECK(nameRepository.labelOf(test2) == "test2_1");
+ // the label of test2 is reused as it's not in the used names when generating labels
+ BOOST_CHECK(nameRepository.labelOf(test2_1) == "test2_1");
+
+ BOOST_CHECK(nameRepository.labelOf(test3) == "test3");
+ BOOST_CHECK(nameRepository.labelOf(test3_1) == "test3_1");
+
+ // derive a name from the (now labelled) test2_1 name
+ auto const test2_1_1 = nameRepository.deriveName(test2_1);
+ // but we have a conflict with an already defined/labelled name, expectation is that we get test2_1_2
+ auto const conflict = nameRepository.defineName("test2_1_1");
+ nameRepository.generateLabels({test1, test1_1, test1_2, test1_2_1, test2_1, test2_1_1, test3, test3_1, conflict});
+ // test2_1 is in the list, so produce a new name
+ BOOST_CHECK(nameRepository.labelOf(test2_1_1) == "test2_1_2");
+ BOOST_CHECK(nameRepository.labelOf(conflict) == "test2_1_1");
+
+ nameRepository.generateLabels({test2, test2_1, test2_1_1, conflict});
+ BOOST_CHECK(nameRepository.labelOf(test2) == "test2_1");
+ // this label gets reassigned, as test2_1 is back in the game
+ BOOST_CHECK(nameRepository.labelOf(test2_1) == "test2_1_3");
+ BOOST_CHECK(nameRepository.labelOf(test2_1_1) == "test2_1_2");
+ BOOST_CHECK(nameRepository.labelOf(conflict) == "test2_1_1");
+
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}