Skip to content

Commit bc7c678

Browse files
committed
Yul: add label id dispenser
1 parent cc6a9cf commit bc7c678

File tree

3 files changed

+242
-0
lines changed

3 files changed

+242
-0
lines changed

Diff for: libyul/CMakeLists.txt

+2
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

Diff for: libyul/optimiser/LabelIDDispenser.cpp

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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/OptimizerUtilities.h>
22+
23+
#include <fmt/compile.h>
24+
25+
#include <range/v3/range/conversion.hpp>
26+
#include <range/v3/view/filter.hpp>
27+
#include <range/v3/view/iota.hpp>
28+
29+
using namespace solidity::yul;
30+
31+
namespace
32+
{
33+
bool isInvalidLabel(
34+
std::string_view const _label,
35+
std::set<std::string, std::less<>> const& _reservedLabels,
36+
Dialect const& _dialect
37+
)
38+
{
39+
return isRestrictedIdentifier(_dialect, _label) || _reservedLabels.contains(_label);
40+
}
41+
}
42+
43+
LabelIDDispenser::LabelIDDispenser(ASTLabelRegistry const& _labels, std::set<std::string> const& _reservedLabels):
44+
m_labels(_labels),
45+
m_reservedLabels(_reservedLabels.begin(), _reservedLabels.end())
46+
{}
47+
48+
LabelIDDispenser::LabelID LabelIDDispenser::newID(LabelID const _parent)
49+
{
50+
auto const parentLabelID = resolveParentLabelID(_parent);
51+
yulAssert(!m_labels.ghost(parentLabelID), "Use newGhost to add new ghosts.");
52+
m_idToLabelMapping.push_back(parentLabelID);
53+
return m_idToLabelMapping.size() + m_labels.maxID();
54+
}
55+
56+
LabelIDDispenser::LabelID LabelIDDispenser::newGhost()
57+
{
58+
m_idToLabelMapping.push_back(ASTLabelRegistry::ghostLabelIndex());
59+
return m_idToLabelMapping.size() + m_labels.maxID();
60+
}
61+
62+
LabelIDDispenser::LabelID LabelIDDispenser::resolveParentLabelID(LabelID _id) const
63+
{
64+
yulAssert(_id < m_idToLabelMapping.size() + m_labels.maxID() + 1, "ID exceeds bounds.");
65+
// bigger than maxID means that the input label id was spawned by this dispenser
66+
if (_id > m_labels.maxID())
67+
_id = m_idToLabelMapping[_id - m_labels.maxID() - 1];
68+
yulAssert(
69+
_id <= m_labels.maxID() && !m_labels.unused(_id),
70+
"We can have at most one level of indirection and the derived-from label cannot be unused."
71+
);
72+
return _id;
73+
}
74+
75+
bool LabelIDDispenser::ghost(LabelID const _id) const
76+
{
77+
yulAssert(_id < m_idToLabelMapping.size() + m_labels.maxID() + 1, "ID exceeds bounds.");
78+
if (_id > m_labels.maxID())
79+
return m_idToLabelMapping[_id - m_labels.maxID() - 1] == ASTLabelRegistry::ghostLabelIndex();
80+
81+
return m_labels.ghost(_id);
82+
}
83+
84+
ASTLabelRegistry LabelIDDispenser::generateNewLabels(Dialect const& _dialect) const
85+
{
86+
auto const usedIDs =
87+
ranges::views::iota(static_cast<size_t>(1), m_idToLabelMapping.size() + m_labels.maxID() + 1) |
88+
ranges::to<std::set>;
89+
return generateNewLabels(usedIDs, _dialect);
90+
}
91+
92+
ASTLabelRegistry LabelIDDispenser::generateNewLabels(std::set<LabelID> const& _usedIDs, Dialect const& _dialect) const
93+
{
94+
if (_usedIDs.empty())
95+
return {};
96+
97+
auto const& originalLabels = m_labels.labels();
98+
99+
std::vector<uint8_t> reusedLabels (originalLabels.size());
100+
// this means that everything that is derived from empty needs to be generated
101+
reusedLabels[0] = true;
102+
103+
// start with the empty label
104+
std::vector<std::string> labels{""};
105+
labels.reserve(originalLabels.size()+1);
106+
107+
// 0 maps to ""
108+
yulAssert(!_usedIDs.empty());
109+
std::vector<size_t> idToLabelMap (*std::prev(_usedIDs.end()) + 1);
110+
111+
std::set<std::string, std::less<>> alreadyDefinedLabels = m_reservedLabels;
112+
113+
// we record which labels have to be newly generated, some we can just take over from the existing registry
114+
std::vector<LabelID> toGenerate;
115+
for (auto const& id: _usedIDs)
116+
{
117+
if (ghost(id))
118+
{
119+
idToLabelMap[id] = ASTLabelRegistry::ghostLabelIndex();
120+
continue;
121+
}
122+
123+
auto const parentLabelID = resolveParentLabelID(id);
124+
125+
auto const originalLabelIndex = m_labels.idToLabelIndex(parentLabelID);
126+
std::string const& originalLabel = originalLabels[originalLabelIndex];
127+
128+
// It is important that the used ids are in ascending order to ensure that ids which occur in the provided AST
129+
// and have unchanged IDs will have their labels reused first, before anything derived from it gets assigned
130+
// said label.
131+
static_assert(std::is_same_v<std::decay_t<decltype(_usedIDs)>, std::set<LabelID>>);
132+
133+
// if we haven't already reused the label, check that either the id didn't change, then we can just
134+
// take over the old label, otherwise check that it is a valid label and then reuse
135+
if (!reusedLabels[originalLabelIndex] && (parentLabelID == id || !isInvalidLabel(originalLabel, m_reservedLabels, _dialect)))
136+
{
137+
labels.push_back(originalLabel);
138+
idToLabelMap[id] = labels.size() - 1;
139+
alreadyDefinedLabels.insert(originalLabel);
140+
reusedLabels[originalLabelIndex] = true;
141+
}
142+
else
143+
toGenerate.push_back(id);
144+
}
145+
146+
std::vector<size_t> labelSuffixes(m_labels.maxID() + 1, 1);
147+
for (auto const& id: toGenerate)
148+
{
149+
yulAssert(!ghost(id));
150+
151+
auto const parentLabelID = resolveParentLabelID(id);
152+
auto const parentLabelIndex = m_labels.idToLabelIndex(parentLabelID);
153+
auto const& parentLabel = originalLabels[parentLabelIndex];
154+
155+
std::string generatedLabel = parentLabel;
156+
do
157+
{
158+
generatedLabel = format(FMT_COMPILE("{}_{}"), parentLabel, labelSuffixes[parentLabelID]++);
159+
} while (isInvalidLabel(generatedLabel, alreadyDefinedLabels, _dialect));
160+
161+
labels.push_back(generatedLabel);
162+
idToLabelMap[id] = labels.size() - 1;
163+
alreadyDefinedLabels.insert(generatedLabel);
164+
}
165+
166+
return ASTLabelRegistry{std::move(labels), std::move(idToLabelMap)};
167+
}

Diff for: libyul/optimiser/LabelIDDispenser.h

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
class Dialect;
30+
31+
/// Can spawn new `LabelID`s which depend on `LabelID`s from a parent label registry. Once generation is completed,
32+
/// a new `ASTLabelRegistry` can be generated based on the used subset of spawned and original IDs.
33+
class LabelIDDispenser
34+
{
35+
public:
36+
using LabelID = ASTLabelRegistry::LabelID;
37+
/// A set of reserved labels may be provided, which is excluded when generating new labels. If a reserved label
38+
/// already appears in the label registry and is used as-is in the AST, it will be reused despite it being
39+
/// provided here.
40+
/// Original labels will always be preserved even if they are not valid Yul identifiers.
41+
explicit LabelIDDispenser(
42+
ASTLabelRegistry const& _labels,
43+
std::set<std::string> const& _reserved = {}
44+
);
45+
46+
ASTLabelRegistry const& labels() const { return m_labels; }
47+
48+
/// Spawns a new LabelID which depends on a parent LabelID that will be used for its string representation.
49+
/// Parent must not be unused. For spawning new ghost labels, `newGhost` must be used.
50+
LabelID newID(LabelID _parent = 0);
51+
/// Spawns a new ghost label.
52+
LabelID newGhost();
53+
54+
/// Creates a new label registry based on the added labels.
55+
/// Ghost IDs are always preserved, as these are not referenced in the AST.
56+
/// Labels are guaranteed to be valid and not reserved if and only if they were valid and not reserved in the
57+
/// original registry. No new invalid and/or reserved labels are introduced.
58+
ASTLabelRegistry generateNewLabels(std::set<LabelID> const& _usedIDs, Dialect const& _dialect) const;
59+
ASTLabelRegistry generateNewLabels(Dialect const& _dialect) const;
60+
private:
61+
/// For newly added label IDs, this yields the parent ID which is contained in the provided registry.
62+
/// For label IDs which already are not new, this function is the identity.
63+
LabelID resolveParentLabelID(LabelID _id) const;
64+
bool ghost(LabelID _id) const;
65+
66+
ASTLabelRegistry const& m_labels;
67+
/// Reserved labels, equipped with the transparent less comparison operator to be able to handle string_view.
68+
std::set<std::string, std::less<>> m_reservedLabels;
69+
/// Contains references to parent label IDs. Indices are new IDs offset by `m_labels.maxID() + 1`.
70+
std::vector<LabelID> m_idToLabelMapping;
71+
};
72+
73+
}

0 commit comments

Comments
 (0)