Skip to content

Commit

Permalink
Yul: Introduces ASTNodeRegistry
Browse files Browse the repository at this point in the history
  • Loading branch information
clonker committed Feb 5, 2025
1 parent 78ec8dd commit bf80a01
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 0 deletions.
150 changes: 150 additions & 0 deletions libyul/ASTNodeRegistry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#include <libyul/ASTNodeRegistry.h>

#include <libyul/Exceptions.h>

#include <fmt/format.h>

#include <range/v3/algorithm/max.hpp>
#include <range/v3/view/map.hpp>

using namespace solidity::yul;

ASTNodeRegistry::ASTNodeRegistry(): m_labels{"", ghostPlaceholder}, m_idToLabelMapping{0, 1} {}

ASTNodeRegistry::ASTNodeRegistry(std::vector<std::string> const& _labels, std::vector<size_t> const& _idToLabelMapping)
{
yulAssert(_labels.size() >= 2);
yulAssert(_labels[0].empty());
yulAssert(_labels[1] == ghostPlaceholder);
yulAssert(_idToLabelMapping.size() >= 2);
yulAssert(_idToLabelMapping[0] == 0);
yulAssert(_idToLabelMapping[1] == 1);
std::vector<uint8_t> labelVisited (_labels.size(), false);
for (auto const& id: _idToLabelMapping)
{
yulAssert(id < _labels.size());
// it is possible to have multiple references to empty / ghost
yulAssert(
id < 2 || !labelVisited[id],
fmt::format("NodeId {} (label \"{}\") is not unique.", id, _labels[id])
);
labelVisited[id] = true;
}
m_labels = _labels;
m_idToLabelMapping = _idToLabelMapping;
}

size_t ASTNodeRegistry::idToLabelIndex(NodeId const _id) const
{
yulAssert(_id < m_idToLabelMapping.size());
return m_idToLabelMapping[_id];
}

std::string_view ASTNodeRegistry::operator()(NodeId const _id) const
{
auto const labelIndex = idToLabelIndex(_id);
if (labelIndex == ghostId())
return lookupGhost(_id);
return m_labels[labelIndex];
}

std::optional<ASTNodeRegistry::NodeId> ASTNodeRegistry::findIdForLabel(std::string_view const _label) const {
if (_label.empty())
return emptyId();
if (_label == ghostPlaceholder)
return NodeId{1};
for (NodeId id = 2; id <= maximumId(); ++id)
if ((*this)(id) == _label)
return id;
return std::nullopt;
}

std::string_view ASTNodeRegistry::lookupGhost(NodeId const _id) const
{
yulAssert(idToLabelIndex(_id) == ghostId());
auto const [it, _] = m_ghostLabelCache.try_emplace(_id, fmt::format("GHOST[{}]", _id));
return it->second;
}

ASTNodeRegistryBuilder::LabelToIdMapping::LabelToIdMapping():
m_mapping{{"", 0}, {ASTNodeRegistry::ghostPlaceholder, 1}}
{}

std::tuple<ASTNodeRegistry::NodeId, bool> ASTNodeRegistryBuilder::LabelToIdMapping::tryInsert(
std::string_view const _label,
ASTNodeRegistry::NodeId const _id
)
{
yulAssert(_label != ASTNodeRegistry::ghostPlaceholder);
auto const [it, emplaced] = m_mapping.try_emplace(std::string{_label}, _id);
return std::make_tuple(it->second, emplaced);
}

ASTNodeRegistryBuilder::ASTNodeRegistryBuilder():
m_nextId(2)
{}

ASTNodeRegistryBuilder::ASTNodeRegistryBuilder(ASTNodeRegistry const& _existingRegistry)
{
for (size_t i = 2; i <= _existingRegistry.maximumId(); ++i)
{
auto const existingLabel = _existingRegistry(i);
if (!existingLabel.empty())
{
auto const [_, inserted] = m_mapping.tryInsert(_existingRegistry(i), i);
yulAssert(inserted);
}
}
m_nextId = _existingRegistry.maximumId() + 1;
}

ASTNodeRegistry::NodeId ASTNodeRegistryBuilder::define(std::string_view const _label)
{
auto const [id, inserted] = m_mapping.tryInsert(_label, m_nextId);
if (inserted)
m_nextId++;
return id;
}

ASTNodeRegistry ASTNodeRegistryBuilder::build() const
{
auto const& mapping = m_mapping.data();
yulAssert(mapping.contains(""));
yulAssert(mapping.at("") == 0);
yulAssert(mapping.contains(ASTNodeRegistry::ghostPlaceholder));
yulAssert(mapping.at(ASTNodeRegistry::ghostPlaceholder) == 1);

std::vector<std::string> labels{"", ASTNodeRegistry::ghostPlaceholder};
labels.reserve(mapping.size());
std::vector<size_t> idToLabelMapping(ranges::max(mapping | ranges::views::values) + 1, 0);
yulAssert(idToLabelMapping.size() >= 2, "Mapping at least contains empty and ghost entry");
idToLabelMapping[1] = 1;
for (auto const& [label, id]: mapping)
{
// skip empty and ghost
if (id < 2)
continue;

labels.emplace_back(label);
idToLabelMapping[id] = labels.size() - 1;
}
return ASTNodeRegistry{labels, idToLabelMapping};
}
84 changes: 84 additions & 0 deletions libyul/ASTNodeRegistry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0

#pragma once

#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

namespace solidity::yul
{

class ASTNodeRegistry
{
public:
/// unsafe to use from a different registry instance, it is up to the user to safeguard against this
using NodeId = size_t;

static constexpr auto ghostPlaceholder = "GHOST[@]";

ASTNodeRegistry();
ASTNodeRegistry(std::vector<std::string> const& _labels, std::vector<size_t> const& _idToLabelMapping);

std::string_view operator()(NodeId _id) const;

static bool constexpr empty(NodeId const _id) { return _id == emptyId(); }
static NodeId constexpr emptyId() { return 0; }
static NodeId constexpr ghostId() { return 1; }

std::vector<std::string> const& labels() const { return m_labels; }
NodeId maximumId() const { return m_idToLabelMapping.size() - 1; }

size_t idToLabelIndex(NodeId _id) const;
/// this is a potentially expensive operation
std::optional<NodeId> findIdForLabel(std::string_view _label) const;
private:
std::string_view lookupGhost(NodeId _id) const;

std::vector<std::string> m_labels;
std::vector<size_t> m_idToLabelMapping;
mutable std::map<NodeId, std::string> m_ghostLabelCache;
};

class ASTNodeRegistryBuilder
{
public:
ASTNodeRegistryBuilder();
explicit ASTNodeRegistryBuilder(ASTNodeRegistry const& _existingRegistry);
ASTNodeRegistry::NodeId define(std::string_view _label);
ASTNodeRegistry build() const;
private:
class LabelToIdMapping
{
public:
LabelToIdMapping();
std::tuple<ASTNodeRegistry::NodeId, bool> tryInsert(std::string_view _label, ASTNodeRegistry::NodeId _id);

auto const& data() const { return m_mapping; }
private:
std::map<std::string, size_t, std::less<>> m_mapping;
};

LabelToIdMapping m_mapping;
size_t m_nextId = 0;
};

}
2 changes: 2 additions & 0 deletions libyul/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ add_library(yul
AST.h
AST.cpp
ASTForward.h
ASTNodeRegistry.cpp
ASTNodeRegistry.h
AsmJsonConverter.h
AsmJsonConverter.cpp
AsmJsonImporter.h
Expand Down

0 comments on commit bf80a01

Please sign in to comment.