-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Add id dispenser for numerical yul node ids #15838
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
/* | ||
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 <http://www.gnu.org/licenses/>. | ||
*/ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
#include <libyul/optimiser/LabelIDDispenser.h> | ||
|
||
#include <libyul/optimiser/OptimizerUtilities.h> | ||
|
||
#include <fmt/compile.h> | ||
|
||
#include <range/v3/range/conversion.hpp> | ||
#include <range/v3/view/filter.hpp> | ||
#include <range/v3/view/iota.hpp> | ||
|
||
using namespace solidity::yul; | ||
|
||
namespace | ||
{ | ||
bool isInvalidLabel( | ||
std::string_view const _label, | ||
std::set<std::string, std::less<>> const& _reservedLabels, | ||
Dialect const& _dialect | ||
) | ||
{ | ||
return isRestrictedIdentifier(_dialect, _label) || _reservedLabels.contains(_label); | ||
} | ||
} | ||
|
||
LabelIDDispenser::LabelIDDispenser(ASTLabelRegistry const& _labels, std::set<std::string> const& _reservedLabels): | ||
m_labels(_labels), | ||
m_reservedLabels(_reservedLabels.begin(), _reservedLabels.end()) | ||
{} | ||
|
||
LabelIDDispenser::LabelID LabelIDDispenser::newID(LabelID const _parent) | ||
{ | ||
auto const parentLabelID = resolveParentLabelID(_parent); | ||
yulAssert(!m_labels.ghost(parentLabelID), "Use newGhost to add new ghosts."); | ||
m_idToLabelMapping.push_back(parentLabelID); | ||
return m_idToLabelMapping.size() + m_labels.maxID(); | ||
} | ||
|
||
LabelIDDispenser::LabelID LabelIDDispenser::newGhost() | ||
{ | ||
m_idToLabelMapping.push_back(ASTLabelRegistry::ghostLabelIndex()); | ||
return m_idToLabelMapping.size() + m_labels.maxID(); | ||
} | ||
|
||
LabelIDDispenser::LabelID LabelIDDispenser::resolveParentLabelID(LabelID _id) const | ||
{ | ||
yulAssert(_id < m_idToLabelMapping.size() + m_labels.maxID() + 1, "ID exceeds bounds."); | ||
// bigger than maxID means that the input label id was spawned by this dispenser | ||
if (_id > m_labels.maxID()) | ||
_id = m_idToLabelMapping[_id - m_labels.maxID() - 1]; | ||
yulAssert( | ||
_id <= m_labels.maxID() && !m_labels.unused(_id), | ||
"We can have at most one level of indirection and the derived-from label cannot be unused." | ||
); | ||
return _id; | ||
} | ||
|
||
bool LabelIDDispenser::ghost(LabelID const _id) const | ||
{ | ||
yulAssert(_id < m_idToLabelMapping.size() + m_labels.maxID() + 1, "ID exceeds bounds."); | ||
if (_id > m_labels.maxID()) | ||
return m_idToLabelMapping[_id - m_labels.maxID() - 1] == ASTLabelRegistry::ghostLabelIndex(); | ||
|
||
return m_labels.ghost(_id); | ||
} | ||
|
||
ASTLabelRegistry LabelIDDispenser::generateNewLabels(Dialect const& _dialect) const | ||
{ | ||
auto const usedIDs = | ||
ranges::views::iota(static_cast<size_t>(1), m_idToLabelMapping.size() + m_labels.maxID() + 1) | | ||
ranges::to<std::set>; | ||
return generateNewLabels(usedIDs, _dialect); | ||
} | ||
|
||
ASTLabelRegistry LabelIDDispenser::generateNewLabels(std::set<LabelID> const& _usedIDs, Dialect const& _dialect) const | ||
{ | ||
if (_usedIDs.empty()) | ||
return {}; | ||
|
||
auto const& originalLabels = m_labels.labels(); | ||
|
||
std::vector<uint8_t> reusedLabels (originalLabels.size()); | ||
// this means that everything that is derived from empty needs to be generated | ||
reusedLabels[0] = true; | ||
|
||
// start with the empty label | ||
std::vector<std::string> labels{""}; | ||
labels.reserve(originalLabels.size()+1); | ||
|
||
// 0 maps to "" | ||
yulAssert(!_usedIDs.empty()); | ||
std::vector<size_t> idToLabelMap (*std::prev(_usedIDs.end()) + 1); | ||
|
||
std::set<std::string, std::less<>> alreadyDefinedLabels = m_reservedLabels; | ||
|
||
// we record which labels have to be newly generated, some we can just take over from the existing registry | ||
std::vector<LabelID> toGenerate; | ||
for (auto const& id: _usedIDs) | ||
{ | ||
if (ghost(id)) | ||
{ | ||
idToLabelMap[id] = ASTLabelRegistry::ghostLabelIndex(); | ||
continue; | ||
} | ||
|
||
auto const parentLabelID = resolveParentLabelID(id); | ||
|
||
auto const originalLabelIndex = m_labels.idToLabelIndex(parentLabelID); | ||
std::string const& originalLabel = originalLabels[originalLabelIndex]; | ||
|
||
// It is important that the used ids are in ascending order to ensure that ids which occur in the provided AST | ||
// and have unchanged IDs will have their labels reused first, before anything derived from it gets assigned | ||
// said label. | ||
static_assert(std::is_same_v<std::decay_t<decltype(_usedIDs)>, std::set<LabelID>>); | ||
|
||
// if we haven't already reused the label, check that either the id didn't change, then we can just | ||
// take over the old label, otherwise check that it is a valid label and then reuse | ||
if (!reusedLabels[originalLabelIndex] && (parentLabelID == id || !isInvalidLabel(originalLabel, m_reservedLabels, _dialect))) | ||
{ | ||
labels.push_back(originalLabel); | ||
idToLabelMap[id] = labels.size() - 1; | ||
alreadyDefinedLabels.insert(originalLabel); | ||
reusedLabels[originalLabelIndex] = true; | ||
} | ||
else | ||
toGenerate.push_back(id); | ||
} | ||
|
||
std::vector<size_t> labelSuffixes(m_labels.maxID() + 1, 1); | ||
for (auto const& id: toGenerate) | ||
{ | ||
yulAssert(!ghost(id)); | ||
|
||
auto const parentLabelID = resolveParentLabelID(id); | ||
auto const parentLabelIndex = m_labels.idToLabelIndex(parentLabelID); | ||
auto const& parentLabel = originalLabels[parentLabelIndex]; | ||
|
||
std::string generatedLabel = parentLabel; | ||
do | ||
{ | ||
generatedLabel = format(FMT_COMPILE("{}_{}"), parentLabel, labelSuffixes[parentLabelID]++); | ||
} while (isInvalidLabel(generatedLabel, alreadyDefinedLabels, _dialect)); | ||
|
||
labels.push_back(generatedLabel); | ||
idToLabelMap[id] = labels.size() - 1; | ||
alreadyDefinedLabels.insert(generatedLabel); | ||
} | ||
|
||
return ASTLabelRegistry{std::move(labels), std::move(idToLabelMap)}; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* | ||
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 <http://www.gnu.org/licenses/>. | ||
*/ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
#pragma once | ||
|
||
#include <libyul/ASTLabelRegistry.h> | ||
|
||
#include <functional> | ||
#include <set> | ||
|
||
namespace solidity::yul | ||
{ | ||
|
||
class Dialect; | ||
|
||
/// Can spawn new `LabelID`s which depend on `LabelID`s from a parent label registry. Once generation is completed, | ||
/// a new `ASTLabelRegistry` can be generated based on the used subset of spawned and original IDs. | ||
class LabelIDDispenser | ||
{ | ||
public: | ||
using LabelID = ASTLabelRegistry::LabelID; | ||
/// A set of reserved labels may be provided, which is excluded when generating new labels. If a reserved label | ||
/// already appears in the label registry and is used as-is in the AST, it will be reused despite it being | ||
/// provided here. | ||
/// Original labels will always be preserved even if they are not valid Yul identifiers. | ||
explicit LabelIDDispenser( | ||
ASTLabelRegistry const& _labels, | ||
std::set<std::string> const& _reserved = {} | ||
); | ||
|
||
ASTLabelRegistry const& labels() const { return m_labels; } | ||
|
||
/// Spawns a new LabelID which depends on a parent LabelID that will be used for its string representation. | ||
/// Parent must not be unused. For spawning new ghost labels, `newGhost` must be used. | ||
LabelID newID(LabelID _parent = 0); | ||
/// Spawns a new ghost label. | ||
LabelID newGhost(); | ||
|
||
/// Creates a new label registry based on the added labels. | ||
/// Ghost IDs are always preserved, as these are not referenced in the AST. | ||
/// Labels are guaranteed to be valid and not reserved if and only if they were valid and not reserved in the | ||
/// original registry. No new invalid and/or reserved labels are introduced. | ||
ASTLabelRegistry generateNewLabels(std::set<LabelID> const& _usedIDs, Dialect const& _dialect) const; | ||
ASTLabelRegistry generateNewLabels(Dialect const& _dialect) const; | ||
private: | ||
/// For newly added label IDs, this yields the parent ID which is contained in the provided registry. | ||
/// For label IDs which already are not new, this function is the identity. | ||
LabelID resolveParentLabelID(LabelID _id) const; | ||
bool ghost(LabelID _id) const; | ||
|
||
ASTLabelRegistry const& m_labels; | ||
/// Reserved labels, equipped with the transparent less comparison operator to be able to handle string_view. | ||
std::set<std::string, std::less<>> m_reservedLabels; | ||
/// Contains references to parent label IDs. Indices are new IDs offset by `m_labels.maxID() + 1`. | ||
std::vector<LabelID> m_idToLabelMapping; | ||
}; | ||
|
||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring should say that the parent label must not be an unused one.
Same for
resolveParentLabelID()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And for
generateNewLabels()
we should mention that the resulting registry will never contain any unused IDs. They will always have new labels generated.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will have unused IDs. Right now I am not compressing the id range but it's append-only. As ids grow, so does the number of unused ones in the lower ranges. I thought about compressing them to the used ones, which would mean potentially remapping existing IDs in the AST and make
generateNewLabels
return a freshAST
. We could still do that if we figure it's a performance bottleneck or we waste too much memory. But personally I don't think so.