Skip to content

Commit 0d84ba2

Browse files
committed
Yul: add label id dispenser
1 parent 1965d74 commit 0d84ba2

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed

libyul/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ add_library(yul
145145
optimiser/InlinableExpressionFunctionFinder.h
146146
optimiser/KnowledgeBase.cpp
147147
optimiser/KnowledgeBase.h
148+
optimiser/LabelIDDispenser.cpp
149+
optimiser/LabelIDDispenser.h
148150
optimiser/LoadResolver.cpp
149151
optimiser/LoadResolver.h
150152
optimiser/LoopInvariantCodeMotion.cpp

libyul/optimiser/LabelIDDispenser.cpp

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#include <libyul/optimiser/LabelIDDispenser.h>
20+
21+
#include <libyul/optimiser/NameCollector.h>
22+
#include <libyul/optimiser/OptimizerUtilities.h>
23+
24+
#include <fmt/compile.h>
25+
26+
#include <range/v3/range/conversion.hpp>
27+
#include <range/v3/view/filter.hpp>
28+
#include <range/v3/view/iota.hpp>
29+
30+
using namespace solidity::yul;
31+
32+
namespace
33+
{
34+
bool isInvalidLabel(
35+
std::string_view const _label,
36+
std::set<std::string, std::less<>> const& _reservedLabels,
37+
Dialect const& _dialect
38+
)
39+
{
40+
return isRestrictedIdentifier(_dialect, _label) || _reservedLabels.contains(_label);
41+
}
42+
}
43+
44+
LabelIDDispenser::LabelIDDispenser(ASTLabelRegistry const& _labels, std::set<std::string> const& _reservedLabels):
45+
m_labels(_labels),
46+
m_reservedLabels(_reservedLabels.begin(), _reservedLabels.end())
47+
{}
48+
49+
LabelIDDispenser::LabelID LabelIDDispenser::newID(LabelID const _parent)
50+
{
51+
auto const parentLabelID = resolveParentLabelID(_parent);
52+
yulAssert(!m_labels.ghost(parentLabelID), "Use newGhost to add new ghosts.");
53+
m_idToLabelMapping.push_back(parentLabelID);
54+
return m_idToLabelMapping.size() + m_labels.maxID();
55+
}
56+
57+
LabelIDDispenser::LabelID LabelIDDispenser::newGhost()
58+
{
59+
m_idToLabelMapping.push_back(ASTLabelRegistry::ghostLabelIndex());
60+
return m_idToLabelMapping.size() + m_labels.maxID();
61+
}
62+
63+
LabelIDDispenser::LabelID LabelIDDispenser::resolveParentLabelID(LabelID _id) const
64+
{
65+
yulAssert(_id < m_idToLabelMapping.size() + m_labels.maxID() + 1, "ID exceeds bounds.");
66+
// bigger than maxID means that the input label id that was spawned by this dispenser
67+
if (_id > m_labels.maxID())
68+
_id = m_idToLabelMapping[_id - m_labels.maxID() - 1];
69+
yulAssert(
70+
_id <= m_labels.maxID() && !m_labels.unused(_id),
71+
"We can have at most one level of indirection and the derived-from label cannot be unused."
72+
);
73+
return _id;
74+
}
75+
76+
bool LabelIDDispenser::ghost(LabelID const _id) const
77+
{
78+
yulAssert(_id < m_idToLabelMapping.size() + m_labels.maxID() + 1, "ID exceeds bounds.");
79+
if (_id > m_labels.maxID())
80+
return m_idToLabelMapping[_id - m_labels.maxID() - 1] == ASTLabelRegistry::ghostLabelIndex();
81+
82+
return m_labels.ghost(_id);
83+
}
84+
85+
ASTLabelRegistry LabelIDDispenser::generateNewLabels(Block const& _astRoot, Dialect const& _dialect) const
86+
{
87+
std::set<LabelID> usedIDs = NameCollector(_astRoot).names();
88+
// add ghosts to usedIDs as they're not referenced in the regular ast
89+
usedIDs += ranges::views::iota(static_cast<size_t>(1), m_idToLabelMapping.size() + m_labels.maxID() + 1) |
90+
ranges::views::filter([this](auto const& labelID) { return ghost(labelID); }) |
91+
ranges::to<std::set>;
92+
return generateNewLabels(usedIDs, _dialect);
93+
}
94+
95+
ASTLabelRegistry LabelIDDispenser::generateNewLabels(std::set<LabelID> const& _usedIDs, Dialect const& _dialect) const
96+
{
97+
if (_usedIDs.empty())
98+
return {};
99+
100+
auto const& originalLabels = m_labels.labels();
101+
102+
std::vector<uint8_t> reusedLabels (originalLabels.size());
103+
// this means that everything that is derived from empty needs to be generated
104+
reusedLabels[0] = true;
105+
106+
// start with the empty label
107+
std::vector<std::string> labels{""};
108+
labels.reserve(originalLabels.size()+1);
109+
110+
// 0 maps to ""
111+
std::vector<size_t> idToLabelMap = {0};
112+
if (!_usedIDs.empty())
113+
idToLabelMap.resize(*std::prev(_usedIDs.end()) + 1);
114+
115+
std::set<std::string, std::less<>> alreadyDefinedLabels = m_reservedLabels;
116+
117+
// we record which labels have to be newly generated, some we can just take over from the existing registry
118+
std::vector<LabelID> toGenerate;
119+
for (auto const& id: _usedIDs)
120+
{
121+
if (ghost(id))
122+
{
123+
idToLabelMap[id] = ASTLabelRegistry::ghostLabelIndex();
124+
continue;
125+
}
126+
127+
auto const parentLabelID = resolveParentLabelID(id);
128+
129+
auto const originalLabelIndex = m_labels.idToLabelIndex(parentLabelID);
130+
std::string const& originalLabel = originalLabels[originalLabelIndex];
131+
132+
// It is important that the used ids are in ascending order to ensure that ids which occur in the provided AST
133+
// and have unchanged IDs will have their labels reused first, before anything derived from it gets assigned
134+
// said label.
135+
static_assert(std::is_same_v<std::decay_t<decltype(_usedIDs)>, std::set<LabelID>>);
136+
137+
// if we haven't already reused the label, check that either the id didn't change, then we can just
138+
// take over the old label, otherwise check that it is a valid label and then reuse
139+
if (!reusedLabels[originalLabelIndex] && (parentLabelID == id || !isInvalidLabel(originalLabel, m_reservedLabels, _dialect)))
140+
{
141+
labels.push_back(originalLabel);
142+
idToLabelMap[id] = labels.size() - 1;
143+
alreadyDefinedLabels.insert(originalLabel);
144+
reusedLabels[originalLabelIndex] = true;
145+
}
146+
else
147+
toGenerate.push_back(id);
148+
}
149+
150+
std::vector<size_t> labelSuffixes(m_labels.maxID() + 1, 1);
151+
for (auto const& id: toGenerate)
152+
{
153+
yulAssert(!ghost(id));
154+
155+
auto const parentLabelID = resolveParentLabelID(id);
156+
auto const parentLabelIndex = m_labels.idToLabelIndex(parentLabelID);
157+
auto const& parentLabel = originalLabels[parentLabelIndex];
158+
159+
std::string generatedLabel = parentLabel;
160+
do
161+
{
162+
generatedLabel = format(FMT_COMPILE("{}_{}"), parentLabel, labelSuffixes[parentLabelID]++);
163+
} while (isInvalidLabel(generatedLabel, alreadyDefinedLabels, _dialect));
164+
165+
labels.push_back(generatedLabel);
166+
idToLabelMap[id] = labels.size() - 1;
167+
alreadyDefinedLabels.insert(generatedLabel);
168+
}
169+
170+
return ASTLabelRegistry{std::move(labels), std::move(idToLabelMap)};
171+
}

libyul/optimiser/LabelIDDispenser.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
19+
#pragma once
20+
21+
#include <libyul/ASTLabelRegistry.h>
22+
23+
#include <functional>
24+
#include <set>
25+
26+
namespace solidity::yul
27+
{
28+
29+
struct Block;
30+
class Dialect;
31+
32+
/// Can spawn new `LabelID`s which depend on `LabelID`s from a parent label registry. Once generation is completed,
33+
/// a new `ASTLabelRegistry` can be generated based on the used subset of spawned and original IDs.
34+
class LabelIDDispenser
35+
{
36+
public:
37+
using LabelID = ASTLabelRegistry::LabelID;
38+
/// A set of reserved labels may be provided, which is excluded when generating new labels. If a reserved label
39+
/// already appears in the label registry and is used as-is in the AST, it will be reused despite it being
40+
/// provided here.
41+
/// Original labels will always be preserved even if they are not valid Yul identifiers.
42+
explicit LabelIDDispenser(
43+
ASTLabelRegistry const& _labels,
44+
std::set<std::string> const& _reserved = {}
45+
);
46+
47+
ASTLabelRegistry const& labels() const { return m_labels; }
48+
49+
/// Spawns a new LabelID which depends on a parent LabelID that will be used for its string representation.
50+
/// Parent must not be unused. For spawning new ghost labels, `newGhost` must be used.
51+
LabelID newID(LabelID _parent = 0);
52+
/// Spawns a new ghost label.
53+
LabelID newGhost();
54+
55+
/// Creates a new label registry based on the added labels.
56+
/// Ghost IDs are always preserved, as these are not referenced in the AST.
57+
/// Labels are guaranteed to be valid and not reserved if and only if they were valid and not reserved in the
58+
/// original registry. No new invalid and/or reserved labels are introduced.
59+
ASTLabelRegistry generateNewLabels(std::set<LabelID> const& _usedIDs, Dialect const& _dialect) const;
60+
ASTLabelRegistry generateNewLabels(Block const& _astRoot, Dialect const& _dialect) const;
61+
private:
62+
/// For newly added label IDs, this yields the parent ID which is contained in the provided registry.
63+
/// For label IDs which already are not new, this function is the identity.
64+
LabelID resolveParentLabelID(LabelID _id) const;
65+
bool ghost(LabelID _id) const;
66+
67+
ASTLabelRegistry const& m_labels;
68+
/// Reserved labels, equipped with the transparent less comparison operator to be able to handle string_view.
69+
std::set<std::string, std::less<>> m_reservedLabels;
70+
/// Contains references to parent label IDs. Indices are new IDs offset by `m_offset`.
71+
std::vector<LabelID> m_idToLabelMapping;
72+
};
73+
74+
}

0 commit comments

Comments
 (0)