Skip to content

Commit 0076c71

Browse files
committed
Yul: add node id dispenser for numerical ids
1 parent 3d47deb commit 0076c71

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed

libyul/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ add_library(yul
159159
optimiser/NameDisplacer.h
160160
optimiser/NameSimplifier.cpp
161161
optimiser/NameSimplifier.h
162+
optimiser/NodeIdDispenser.cpp
163+
optimiser/NodeIdDispenser.h
162164
optimiser/OptimiserStep.h
163165
optimiser/OptimizerUtilities.cpp
164166
optimiser/OptimizerUtilities.h

libyul/optimiser/NodeIdDispenser.cpp

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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/NodeIdDispenser.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+
NodeIdDispenser::NodeIdDispenser(ASTNodeRegistry const& _labelRegistry, std::set<std::string> const& _reservedLabels):
44+
m_registry(_labelRegistry),
45+
m_reservedLabels(_reservedLabels.begin(), _reservedLabels.end()),
46+
m_offset(_labelRegistry.maximumId() + 1)
47+
{
48+
m_reservedLabels += std::set{"", ASTNodeRegistry::ghostPlaceholder};
49+
}
50+
51+
NodeIdDispenser::NodeId NodeIdDispenser::newId(NodeId const parent)
52+
{
53+
m_mapping.push_back(resolveBaseId(parent));
54+
return m_mapping.size() - 1 + m_offset;
55+
}
56+
57+
NodeIdDispenser::NodeId NodeIdDispenser::newGhost()
58+
{
59+
return newId(ASTNodeRegistry::ghostId());
60+
}
61+
62+
NodeIdDispenser::NodeId NodeIdDispenser::resolveBaseId(NodeId _id) const
63+
{
64+
if (_id >= m_offset)
65+
_id = m_mapping[_id - m_offset];
66+
yulAssert(_id < m_offset, "We have at most one level of indirection, this violates this assumption");
67+
return _id;
68+
}
69+
70+
ASTNodeRegistry NodeIdDispenser::generateNewLabels(Block const&, Dialect const& _dialect) const
71+
{
72+
// this can be replaced by the actually used ids in the provided block once the AST uses ids instead of YulString
73+
std::set<NodeId> usedIds = ranges::views::iota(static_cast<size_t>(0), m_mapping.size() + m_offset) | ranges::to<std::set<NodeId>>;
74+
75+
if (usedIds.empty())
76+
return {};
77+
78+
auto const& originalLabels = m_registry.labels();
79+
80+
std::vector<uint8_t> reusedLabels (originalLabels.size());
81+
// this means that everything that is derived from empty / ghost needs to be generated
82+
reusedLabels[0] = true;
83+
reusedLabels[1] = true;
84+
85+
std::vector<std::string> labels{"", ASTNodeRegistry::ghostPlaceholder};
86+
labels.reserve(originalLabels.size()+2);
87+
// this is fine as `usedIds` is guaranteed to be not empty
88+
std::vector<size_t> idToLabelMap(*std::prev(usedIds.end()) + 1, 0);
89+
idToLabelMap[0] = 0;
90+
idToLabelMap[1] = 1;
91+
92+
std::set<std::string, std::less<>> alreadyDefinedLabels = m_reservedLabels;
93+
94+
std::vector<NodeId> toGenerate;
95+
// filter out straightforward case: we just use whatever label was already there and put it into alreadyDefinedLabels
96+
// otherwise it goes into the toGenerate collection
97+
for (auto const& id: usedIds)
98+
{
99+
auto const baseId = resolveBaseId(id);
100+
auto const baseLabelIndex = m_registry.idToLabelIndex(baseId);
101+
auto const& baseLabel = originalLabels[baseLabelIndex];
102+
// if we haven't already reused the label, check that either the id didn't change, then we can just
103+
// take over the old label, otherwise check that it is a valid label and then reuse
104+
if (!reusedLabels[baseLabelIndex] && (baseId == id || !isInvalidLabel(baseLabel, m_reservedLabels, _dialect)))
105+
{
106+
labels.push_back(baseLabel);
107+
idToLabelMap[id] = labels.size() - 1;
108+
alreadyDefinedLabels.insert(baseLabel);
109+
reusedLabels[baseLabelIndex] = true;
110+
}
111+
else
112+
toGenerate.push_back(id);
113+
}
114+
115+
for (auto const& id: toGenerate)
116+
{
117+
auto const baseId = resolveBaseId(id);
118+
119+
// ghost variables get special treatment
120+
if (baseId == ASTNodeRegistry::ghostId())
121+
{
122+
idToLabelMap[id] = ASTNodeRegistry::ghostId();
123+
continue;
124+
}
125+
126+
auto const baseLabelIndex = m_registry.idToLabelIndex(baseId);
127+
auto const& baseLabel = originalLabels[baseLabelIndex];
128+
129+
std::string generatedLabel = baseLabel;
130+
size_t suffix = 1;
131+
do
132+
{
133+
generatedLabel = format(FMT_COMPILE("{}_{}"), baseLabel, suffix++);
134+
} while (isInvalidLabel(generatedLabel, alreadyDefinedLabels, _dialect));
135+
136+
labels.push_back(generatedLabel);
137+
idToLabelMap[id] = labels.size() - 1;
138+
alreadyDefinedLabels.insert(generatedLabel);
139+
}
140+
141+
return ASTNodeRegistry{labels, idToLabelMap};
142+
}

libyul/optimiser/NodeIdDispenser.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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/ASTNodeRegistry.h>
22+
23+
#include <functional>
24+
#include <set>
25+
26+
namespace solidity::yul
27+
{
28+
29+
struct Block;
30+
class Dialect;
31+
32+
class NodeIdDispenser
33+
{
34+
public:
35+
using NodeId = ASTNodeRegistry::NodeId;
36+
explicit NodeIdDispenser(
37+
ASTNodeRegistry const& _labels,
38+
std::set<std::string> const& _reserved = {}
39+
);
40+
41+
ASTNodeRegistry const& labels() const { return m_registry; }
42+
43+
NodeId newId(NodeId parent = 0);
44+
NodeId newGhost();
45+
ASTNodeRegistry generateNewLabels(Block const& _root, Dialect const& _dialect) const;
46+
private:
47+
NodeId resolveBaseId(NodeId _id) const;
48+
49+
ASTNodeRegistry const& m_registry;
50+
std::set<std::string, std::less<>> m_reservedLabels;
51+
size_t m_offset;
52+
std::vector<NodeId> m_mapping;
53+
};
54+
55+
}

0 commit comments

Comments
 (0)