diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index e790f6c17961..b52b9749919a 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..d2f75f8a508e --- /dev/null +++ b/libyul/YulName.cpp @@ -0,0 +1,162 @@ +/* + 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::optional YulNameRepository::labelOf(YulName _name) const +{ + if (!isDerivedName(_name)) + { + // if the parent is directly a defined label, we take that one + auto const labelIndex = std::get<0>(m_names[static_cast(_name)]); + yulAssert(labelIndex <= std::numeric_limits::max()); + return m_definedLabels.at(static_cast(labelIndex)); + } + return std::nullopt; +} + +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(); +} + +YulNameRepository::YulName YulNameRepository::baseNameOf(YulName _name) const +{ + while (isDerivedName(_name)) + _name = std::get<0>(m_names[static_cast(_name)]); + return _name; +} + +std::string_view YulNameRepository::baseLabelOf(YulName const& _name) const +{ + auto const labelIndex = std::get<0>(m_names[static_cast(baseNameOf(_name))]); + yulAssert(labelIndex <= std::numeric_limits::max()); + return m_definedLabels[static_cast(labelIndex)]; +} + +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; +} + +YulNameRepository::YulName YulNameRepository::defineName(std::string_view const _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); +} + +YulNameRepository::YulName YulNameRepository::deriveName(YulName const& _name) +{ + m_names.emplace_back(baseNameOf(_name), YulNameState::DERIVED); + return static_cast(m_names.size() - 1); +} + +bool YulNameRepository::isDerivedName(YulName const& _name) const +{ + yulAssert(_name <= std::numeric_limits::max()); + return std::get<1>(m_names.at(static_cast(_name))) == YulNameState::DERIVED; +} + +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..9a6881f4ee49 100644 --- a/libyul/YulName.h +++ b/libyul/YulName.h @@ -18,9 +18,87 @@ #pragma once +#include +#include #include +#include + +#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); + + /// 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); + + /// 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 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; + + /// Whether a name is considered derived, i.e., has no label but a parent name. + bool isDerivedName(YulName const& _name) const; + + /// 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; + + 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() + +}