Skip to content

Commit b0f5c6b

Browse files
committed
Yul: Introduces ASTNodeRegistry
1 parent cfd32a3 commit b0f5c6b

File tree

3 files changed

+280
-0
lines changed

3 files changed

+280
-0
lines changed

libyul/ASTLabelRegistry.cpp

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
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/ASTLabelRegistry.h>
20+
21+
#include <libyul/Exceptions.h>
22+
23+
#include <fmt/format.h>
24+
25+
#include <range/v3/algorithm/all_of.hpp>
26+
#include <range/v3/algorithm/max.hpp>
27+
#include <range/v3/view/map.hpp>
28+
29+
using namespace solidity::yul;
30+
31+
32+
ASTLabelRegistry::ASTLabelRegistry(): m_labels{""}, m_idToLabelMapping{0} {}
33+
34+
ASTLabelRegistry::ASTLabelRegistry(std::vector<std::string> _labels, std::vector<size_t> _idToLabelMapping)
35+
{
36+
yulAssert(!_labels.empty());
37+
yulAssert(_labels[0].empty());
38+
yulAssert(!_idToLabelMapping.empty());
39+
yulAssert(_idToLabelMapping[0] == 0);
40+
// using vector<uint8_t> over vector<bool>, as the latter is optimized for space-efficiency
41+
std::vector<uint8_t> labelVisited (_labels.size(), false);
42+
size_t numLabels = 0;
43+
for (auto const& labelIndex: _idToLabelMapping)
44+
{
45+
if (labelIndex == ghostLabelIndex())
46+
continue;
47+
yulAssert(labelIndex < _labels.size());
48+
// it is possible to have multiple references to the empty / ghost label index
49+
// only the id == 0 refers to an actually empty label, otherwise the LabelID is unused
50+
yulAssert(
51+
labelIndex == 0 || !labelVisited[labelIndex],
52+
fmt::format("LabelID {} (label \"{}\") is not unique.", labelIndex, _labels[labelIndex])
53+
);
54+
labelVisited[labelIndex] = true;
55+
if (labelIndex >= 1)
56+
++numLabels;
57+
}
58+
yulAssert(numLabels + 1 == _labels.size(), "Unused labels present.");
59+
m_labels = std::move(_labels);
60+
m_idToLabelMapping = std::move(_idToLabelMapping);
61+
}
62+
63+
ASTLabelRegistry::LabelID ASTLabelRegistry::maxID() const
64+
{
65+
yulAssert(!m_idToLabelMapping.empty());
66+
return m_idToLabelMapping.size() - 1;
67+
}
68+
69+
size_t ASTLabelRegistry::idToLabelIndex(LabelID const _id) const
70+
{
71+
yulAssert(_id < m_idToLabelMapping.size());
72+
return m_idToLabelMapping[_id];
73+
}
74+
75+
std::string_view ASTLabelRegistry::operator[](LabelID const _id) const
76+
{
77+
auto const labelIndex = idToLabelIndex(_id);
78+
yulAssert(labelIndex != ghostLabelIndex(), "Ghost ids are not explicitly labelled in the registry.");
79+
// not using `unused` here, as we already have evaluated the label index
80+
// an id is unused if it is not id == 0 (empty label) but points at label index 0
81+
yulAssert(!empty(_id) && labelIndex == 0, "Can only query ids that are not unused");
82+
return m_labels[labelIndex];
83+
}
84+
85+
bool ASTLabelRegistry::ghost(LabelID const _id) const
86+
{
87+
return idToLabelIndex(_id) == ghostLabelIndex();
88+
}
89+
90+
bool ASTLabelRegistry::unused(LabelID const _id) const
91+
{
92+
return !empty(_id) && idToLabelIndex(_id) == 0;
93+
}
94+
95+
std::optional<ASTLabelRegistry::LabelID> ASTLabelRegistry::findIdForLabel(std::string_view const _label) const {
96+
for (LabelID id = 0; id <= maxID(); ++id)
97+
if ((*this)[id] == _label)
98+
return id;
99+
return std::nullopt;
100+
}
101+
102+
std::tuple<ASTLabelRegistry::LabelID, bool> ASTLabelRegistryBuilder::DefinedLabels::tryInsert(
103+
std::string_view const _label,
104+
ASTLabelRegistry::LabelID const _id
105+
)
106+
{
107+
auto const [it, emplaced] = m_labelToIDMapping.try_emplace(std::string{_label}, _id);
108+
return std::make_tuple(it->second, emplaced);
109+
}
110+
111+
ASTLabelRegistryBuilder::ASTLabelRegistryBuilder():
112+
m_nextID(1)
113+
{}
114+
115+
ASTLabelRegistryBuilder::ASTLabelRegistryBuilder(ASTLabelRegistry const& _existingRegistry)
116+
{
117+
yulAssert(!_existingRegistry.labels().empty() && _existingRegistry[0].empty());
118+
for (size_t id = 1; id <= _existingRegistry.maxID(); ++id)
119+
{
120+
if (!ASTLabelRegistry::empty(id) && !_existingRegistry.unused(id))
121+
{
122+
// ghost ids are not added to the map as they are not explicitly labeled
123+
if (_existingRegistry.ghost(id))
124+
m_ghosts.push_back(id);
125+
else
126+
{
127+
auto const [_, inserted] = m_definedLabels.tryInsert(_existingRegistry[id], id);
128+
yulAssert(inserted, "Existing registry cannot contain duplicate labels.");
129+
}
130+
}
131+
}
132+
m_nextID = _existingRegistry.maxID() + 1;
133+
}
134+
135+
ASTLabelRegistry::LabelID ASTLabelRegistryBuilder::define(std::string_view const _label)
136+
{
137+
auto const [id, inserted] = m_definedLabels.tryInsert(_label, m_nextID);
138+
if (inserted)
139+
m_nextID++;
140+
return id;
141+
}
142+
143+
ASTLabelRegistry::LabelID ASTLabelRegistryBuilder::addGhost()
144+
{
145+
m_ghosts.push_back(m_nextID);
146+
return m_nextID++;
147+
}
148+
149+
ASTLabelRegistry ASTLabelRegistryBuilder::build() const
150+
{
151+
auto const& labelToIDMapping = m_definedLabels.labelToIDMapping();
152+
yulAssert(labelToIDMapping.contains(""));
153+
yulAssert(labelToIDMapping.at("") == 0);
154+
155+
std::vector<std::string> labels{""};
156+
labels.reserve(labelToIDMapping.size());
157+
auto const maxLabelId = ranges::max(labelToIDMapping | ranges::views::values);
158+
auto const maxGhostId = m_ghosts.empty() ? 0 : m_ghosts.back();
159+
std::vector<size_t> idToLabelMapping( std::max(maxLabelId, maxGhostId) + 1, 0);
160+
yulAssert(!idToLabelMapping.empty(), "Mapping must at least contain empty label");
161+
for (auto const& [label, id]: labelToIDMapping)
162+
{
163+
if (ASTLabelRegistry::empty(id))
164+
continue;
165+
166+
labels.emplace_back(label);
167+
idToLabelMapping[id] = labels.size() - 1;
168+
}
169+
for (auto const ghostId: m_ghosts)
170+
idToLabelMapping[ghostId] = ASTLabelRegistry::ghostLabelIndex();
171+
return ASTLabelRegistry{std::move(labels), std::move(idToLabelMapping)};
172+
}

libyul/ASTLabelRegistry.h

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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 <limits>
22+
#include <map>
23+
#include <optional>
24+
#include <string>
25+
#include <string_view>
26+
#include <vector>
27+
28+
namespace solidity::yul
29+
{
30+
31+
/// Instances of the `ASTLabelRegistry` are immutable containers describing a labelling of nodes inside the AST.
32+
/// Each element of the AST that possesses a label has a `ASTLabelRegistry::LabelID`, with which the label can
33+
/// be queried in O(1).
34+
/// Preferred way of creating instances is via `ASTLabelRegistryBuilder` when parsing/importing and
35+
/// via `LabelIDDispenser` during/after optimization.
36+
/// Note: The id == 0 always corresponds to the empty label. Other ids can point at the label index 0, which means that
37+
/// they are unused. To check, whether an id is unused, the `unused` function can be used.
38+
/// Note: There is a special case of ghost ids, which are added during CFG construction.
39+
/// They do not directly correspond to elements of an AST but live inside the CFG. They are assigned the
40+
/// special `std::numeric_limits<LabelID>::max` label index. The labels themselves are - if required - generated from
41+
/// the CFG.
42+
class ASTLabelRegistry
43+
{
44+
public:
45+
/// unsafe to use from a different registry instance, it is up to the user to safeguard against this
46+
using LabelID = size_t;
47+
48+
ASTLabelRegistry();
49+
ASTLabelRegistry(std::vector<std::string> _labels, std::vector<size_t> _idToLabelMapping);
50+
51+
std::string_view operator[](LabelID _id) const;
52+
53+
static bool constexpr empty(LabelID const _id) { return _id == emptyID(); }
54+
static LabelID constexpr emptyID() { return 0; }
55+
static size_t constexpr ghostLabelIndex() { return std::numeric_limits<size_t>::max(); }
56+
57+
bool unused(LabelID _id) const;
58+
bool ghost(LabelID _id) const;
59+
std::vector<std::string> const& labels() const { return m_labels; }
60+
LabelID maxID() const;
61+
62+
size_t idToLabelIndex(LabelID _id) const;
63+
/// this is a potentially expensive operation
64+
std::optional<LabelID> findIdForLabel(std::string_view _label) const;
65+
66+
private:
67+
/// All Yul AST labels present in the registry.
68+
/// Always contains at least an empty label which also serves as a null-state for non-contiguous ID ranges.
69+
/// All items must be unique.
70+
std::vector<std::string> m_labels;
71+
72+
/// Assignment of labels to `LabelID`s. Indices are `LabelID`s and values are indices into `m_labels`.
73+
/// Every label except empty and ghost placeholder always has exactly one `LabelID` pointing at it.
74+
std::vector<size_t> m_idToLabelMapping;
75+
};
76+
77+
/// Produces instances of `ASTLabelRegistry`. Preferably used during parsing/importing.
78+
class ASTLabelRegistryBuilder
79+
{
80+
public:
81+
ASTLabelRegistryBuilder();
82+
/// Creates a new builder taking an already existing label registry into account.
83+
/// Unused IDs are skipped, ghost IDs are recorded and inserted back into the newly built registry.
84+
explicit ASTLabelRegistryBuilder(ASTLabelRegistry const& _existingRegistry);
85+
86+
ASTLabelRegistry::LabelID define(std::string_view _label);
87+
ASTLabelRegistry::LabelID addGhost();
88+
ASTLabelRegistry build() const;
89+
90+
private:
91+
class DefinedLabels
92+
{
93+
public:
94+
std::tuple<ASTLabelRegistry::LabelID, bool> tryInsert(std::string_view _label, ASTLabelRegistry::LabelID _id);
95+
auto const& labelToIDMapping() const { return m_labelToIDMapping; }
96+
97+
private:
98+
std::map<std::string, size_t, std::less<>> m_labelToIDMapping = {{"", 0}};
99+
};
100+
101+
DefinedLabels m_definedLabels;
102+
std::vector<ASTLabelRegistry::LabelID> m_ghosts;
103+
size_t m_nextID{};
104+
};
105+
106+
}

libyul/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ add_library(yul
77
AST.h
88
AST.cpp
99
ASTForward.h
10+
ASTLabelRegistry.cpp
11+
ASTLabelRegistry.h
1012
AsmJsonConverter.h
1113
AsmJsonConverter.cpp
1214
AsmJsonImporter.h

0 commit comments

Comments
 (0)