Skip to content

Commit 37112b5

Browse files
committed
Yul: add label id dispenser
1 parent b0f5c6b commit 37112b5

File tree

3 files changed

+238
-0
lines changed

3 files changed

+238
-0
lines changed

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

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/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/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+
m_offset(_labels.maxID() + 1)
47+
{
48+
m_reservedLabels += std::set{""};
49+
}
50+
51+
LabelIDDispenser::LabelID LabelIDDispenser::newID(LabelID const parent)
52+
{
53+
m_idToLabelMapping.push_back(resolveParentLabelID(parent));
54+
return m_idToLabelMapping.size() - 1 + m_offset;
55+
}
56+
57+
LabelIDDispenser::LabelID LabelIDDispenser::newGhost()
58+
{
59+
m_idToLabelMapping.push_back(ASTLabelRegistry::ghostLabelIndex());
60+
return m_idToLabelMapping.size() - 1 + m_offset;
61+
}
62+
63+
LabelIDDispenser::LabelID LabelIDDispenser::resolveParentLabelID(LabelID _id) const
64+
{
65+
yulAssert(_id < m_idToLabelMapping.size() + m_offset, "ID exceeds bounds.");
66+
// bigger than offset means, that the input label id that was spawned by this dispenser
67+
if (_id >= m_offset)
68+
_id = m_idToLabelMapping[_id - m_offset];
69+
yulAssert(
70+
_id < m_offset && !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_offset, "ID exceeds bounds.");
79+
if (_id >= m_offset)
80+
return m_idToLabelMapping[_id - m_offset] == ASTLabelRegistry::ghostLabelIndex();
81+
82+
return m_labels.ghost(_id);
83+
}
84+
85+
86+
ASTLabelRegistry LabelIDDispenser::generateNewLabels(Block const&, Dialect const& _dialect) const
87+
{
88+
// this can be replaced by the actually used ids in the provided block once the AST uses ids instead of YulString
89+
std::set<LabelID> usedIDs = ranges::views::iota(static_cast<size_t>(0), m_idToLabelMapping.size() + m_offset) | ranges::to<std::set<LabelID>>;
90+
91+
if (usedIDs.empty())
92+
return {};
93+
94+
auto const& originalLabels = m_labels.labels();
95+
96+
std::vector<uint8_t> reusedLabels (originalLabels.size());
97+
// this means that everything that is derived from empty needs to be generated
98+
reusedLabels[0] = true;
99+
100+
// start with the empty label
101+
std::vector<std::string> labels{""};
102+
labels.reserve(originalLabels.size()+1);
103+
104+
// usedIDs contains at least one element at this point
105+
std::vector<size_t> idToLabelMap(*std::prev(usedIDs.end()) + 1, 0);
106+
107+
std::set<std::string, std::less<>> alreadyDefinedLabels = m_reservedLabels;
108+
109+
// we record which labels have to be newly generated, some we can just take over from the existing registry
110+
std::vector<LabelID> toGenerate;
111+
for (auto const& id: usedIDs)
112+
{
113+
if (ghost(id))
114+
{
115+
idToLabelMap[id] = ASTLabelRegistry::ghostLabelIndex();
116+
continue;
117+
}
118+
119+
auto const parentLabelID = resolveParentLabelID(id);
120+
121+
auto const originalLabelIndex = m_labels.idToLabelIndex(parentLabelID);
122+
auto const& originalLabel = originalLabels[originalLabelIndex];
123+
124+
// It is important that the used ids are in ascending order to ensure that ids which occur in the provided AST
125+
// and have unchanged IDs will have their labels reused first, before anything derived from it gets assigned
126+
// said label.
127+
static_assert(std::is_same_v<std::decay_t<decltype(usedIDs)>, std::set<LabelID>>);
128+
129+
// if we haven't already reused the label, check that either the id didn't change, then we can just
130+
// take over the old label, otherwise check that it is a valid label and then reuse
131+
if (!reusedLabels[originalLabelIndex] && (parentLabelID == id || !isInvalidLabel(originalLabel, m_reservedLabels, _dialect)))
132+
{
133+
labels.push_back(originalLabel);
134+
idToLabelMap[id] = labels.size() - 1;
135+
alreadyDefinedLabels.insert(originalLabel);
136+
reusedLabels[originalLabelIndex] = true;
137+
}
138+
else
139+
toGenerate.push_back(id);
140+
}
141+
142+
for (auto const& id: toGenerate)
143+
{
144+
if (ghost(id))
145+
{
146+
idToLabelMap[id] = ASTLabelRegistry::ghostLabelIndex();
147+
continue;
148+
}
149+
150+
auto const parentLabelID = resolveParentLabelID(id);
151+
auto const parentLabelIndex = m_labels.idToLabelIndex(parentLabelID);
152+
auto const& parentLabel = originalLabels[parentLabelIndex];
153+
154+
std::string generatedLabel = parentLabel;
155+
size_t suffix = 1;
156+
do
157+
{
158+
generatedLabel = format(FMT_COMPILE("{}_{}"), parentLabel, suffix++);
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{labels, idToLabelMap};
167+
}

libyul/optimiser/LabelIDDispenser.h

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
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+
LabelID newID(LabelID parent = 0);
49+
LabelID newGhost();
50+
51+
/// Creates a new label registry based on the added labels. Will only contain labels for ids referenced in the
52+
/// provided AST root and preserve ghost IDs, as these are not referenced in the AST.
53+
ASTLabelRegistry generateNewLabels(Block const& _root, Dialect const& _dialect) const;
54+
55+
private:
56+
/// For newly added label ids, this yields the parent ID which is contained in the provided registry.
57+
/// For label ids which already are not new, this function is the identity.
58+
LabelID resolveParentLabelID(LabelID _id) const;
59+
bool ghost(LabelID _id) const;
60+
61+
ASTLabelRegistry const& m_labels;
62+
std::set<std::string, std::less<>> m_reservedLabels;
63+
/// Offset by which LabelIDs must be shifted to be used in the dispenser's `m_idToLabelMapping`
64+
size_t m_offset;
65+
/// Contains references to parent label IDs. Indices are new IDs offset by `m_offset`.
66+
std::vector<LabelID> m_idToLabelMapping;
67+
};
68+
69+
}

0 commit comments

Comments
 (0)