mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Function outliner.
This commit is contained in:
parent
298af55cc5
commit
cbf387c38d
@ -57,7 +57,7 @@ struct OptimiserSettings
|
||||
"gvif" // Run full inliner
|
||||
"CTUcarrLsTFOtfDncarrIulc" // SSA plus simplify
|
||||
"]"
|
||||
"jmuljuljul VcTOcul jmul"; // Make source short and pretty
|
||||
"[y] jmuljuljul VcTOcul jmul"; // Make source short and pretty
|
||||
|
||||
/// No optimisations at all - not recommended.
|
||||
static OptimiserSettings none()
|
||||
|
@ -143,6 +143,8 @@ add_library(yul
|
||||
optimiser/FunctionGrouper.h
|
||||
optimiser/FunctionHoister.cpp
|
||||
optimiser/FunctionHoister.h
|
||||
optimiser/FunctionOutliner.h
|
||||
optimiser/FunctionOutliner.cpp
|
||||
optimiser/FunctionSpecializer.cpp
|
||||
optimiser/FunctionSpecializer.h
|
||||
optimiser/InlinableExpressionFunctionFinder.cpp
|
||||
|
@ -67,8 +67,10 @@ public:
|
||||
static constexpr uint64_t fnvPrime = 1099511628211u;
|
||||
static constexpr uint64_t fnvEmptyHash = 14695981039346656037u;
|
||||
|
||||
private:
|
||||
uint64_t currentHash() const { return m_hash; }
|
||||
|
||||
BlockHasher(std::map<Block const*, uint64_t>& _blockHashes): m_blockHashes(_blockHashes) {}
|
||||
private:
|
||||
|
||||
void hash8(uint8_t _value)
|
||||
{
|
||||
|
271
libyul/optimiser/FunctionOutliner.cpp
Normal file
271
libyul/optimiser/FunctionOutliner.cpp
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
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/optimiser/FunctionOutliner.h>
|
||||
#include <libyul/AST.h>
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <libyul/optimiser/Metrics.h>
|
||||
|
||||
#include <libyul/AsmPrinter.h>
|
||||
|
||||
#include <libyul/optimiser/FullInliner.h>
|
||||
|
||||
#include <libsolutil/CommonData.h>
|
||||
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::yul;
|
||||
|
||||
/**
|
||||
* Copies statements and replaces all variables by new names, not only
|
||||
* for declarations.
|
||||
*/
|
||||
class StatementSequenceCopier: public ASTCopier
|
||||
{
|
||||
public:
|
||||
explicit StatementSequenceCopier(NameDispenser& _dispenser): m_nameDispenser(_dispenser) {}
|
||||
|
||||
using ASTCopier::operator();
|
||||
Statement operator()(VariableDeclaration const& _varDecl) override
|
||||
{
|
||||
for (TypedName const& var: _varDecl.variables)
|
||||
{
|
||||
m_declaredHere.insert(var.name);
|
||||
markWritten(var.name);
|
||||
}
|
||||
return ASTCopier::operator()(_varDecl);
|
||||
}
|
||||
|
||||
Statement operator()(Assignment const& _assignment) override
|
||||
{
|
||||
for (Identifier const& var: _assignment.variableNames)
|
||||
markWritten(var.name);
|
||||
return ASTCopier::operator()(_assignment);
|
||||
}
|
||||
|
||||
Statement operator()(FunctionDefinition const&) override
|
||||
{
|
||||
solAssert(false);
|
||||
}
|
||||
|
||||
Expression operator()(Identifier const& _identifier) override
|
||||
{
|
||||
markRead(_identifier.name);
|
||||
return ASTCopier::operator()(_identifier);
|
||||
}
|
||||
|
||||
YulString translateIdentifier(YulString _name) override
|
||||
{
|
||||
if (m_translations.count(_name))
|
||||
return m_translations.at(_name);
|
||||
return _name;
|
||||
}
|
||||
|
||||
void addTranslation(YulString _name)
|
||||
{
|
||||
if (!m_translations.count(_name))
|
||||
m_translations[_name] = m_nameDispenser.newName(_name);
|
||||
}
|
||||
void markRead(YulString _name)
|
||||
{
|
||||
addTranslation(_name);
|
||||
if (!m_declaredHere.count(_name) && !util::contains(m_parameters, _name))
|
||||
m_parameters.emplace_back(_name);
|
||||
}
|
||||
void markWritten(YulString _name)
|
||||
{
|
||||
addTranslation(_name);
|
||||
if (!util::contains(m_returnParameters, _name))
|
||||
m_returnParameters.emplace_back(_name);
|
||||
}
|
||||
|
||||
NameDispenser& m_nameDispenser;
|
||||
map<YulString, YulString> m_translations = {};
|
||||
set<YulString> m_declaredHere = {};
|
||||
vector<YulString> m_parameters = {};
|
||||
vector<YulString> m_returnParameters = {};
|
||||
};
|
||||
|
||||
class StatementSequenceHasher: public ASTModifier
|
||||
{
|
||||
public:
|
||||
void operator()(Block& _block) override
|
||||
{
|
||||
// TODO the hasher should skip function definitions
|
||||
ASTModifier::operator()(_block);
|
||||
for (size_t i = 0; i < _block.statements.size(); ++i)
|
||||
{
|
||||
std::map<Block const*, uint64_t> hashes;
|
||||
BlockHasher blockHasher(hashes);
|
||||
for (size_t j = i; j < _block.statements.size(); ++j)
|
||||
{
|
||||
blockHasher.visit(_block.statements[j]);
|
||||
m_hashes[{&_block, i, j - i + 1}] = blockHasher.currentHash();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//private:
|
||||
map<tuple<Block*, size_t, size_t>, uint64_t> m_hashes;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
size_t sequenceSize(Block const& _block, size_t _start, size_t _length)
|
||||
{
|
||||
size_t size = 0;
|
||||
for (size_t i = 0; i < _length; ++i)
|
||||
size += CodeSize::codeSize(_block.statements[_start + i]);
|
||||
return size;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FunctionOutliner::run(OptimiserStepContext& _context, Block& _ast)
|
||||
{
|
||||
StatementSequenceHasher hasher;
|
||||
hasher(_ast);
|
||||
|
||||
// TODO make this deterministic
|
||||
|
||||
map<tuple<size_t, uint64_t>, vector<tuple<Block*, size_t, size_t>>> byHash;
|
||||
|
||||
// TODO can we do it in a way such that we can detect which blocks (and their sub-blocks)
|
||||
// are invalidated?
|
||||
|
||||
for (auto&& [key, hash]: hasher.m_hashes)
|
||||
{
|
||||
auto&& [block, start, length] = key;
|
||||
byHash[{sequenceSize(*block, start, length), hash}].emplace_back(block, start, length);
|
||||
}
|
||||
|
||||
for (auto&& [key, value]: byHash | ranges::views::reverse)
|
||||
{
|
||||
if (value.size() <= 4)
|
||||
continue;
|
||||
|
||||
// TODO check that they are actually syntactically equivalent.
|
||||
|
||||
// TODO check that there is no `break`, `continue` or `leave`
|
||||
|
||||
|
||||
cout << "-------------------------------------------" << endl;
|
||||
cout << value.size() << " items" << endl;
|
||||
cout << "code size: " << get<0>(key) << endl;
|
||||
for (auto&& [block, start, length]: value)
|
||||
{
|
||||
cout << " candidate: ------------ " << endl;
|
||||
AsmPrinter p;
|
||||
for (size_t i = 0; i < length; i++)
|
||||
cout << " " << std::visit(p, block->statements[start + i]) << endl;
|
||||
cout << "----" << endl;
|
||||
}
|
||||
|
||||
StatementSequenceCopier copier(_context.dispenser);
|
||||
auto&& [block, start, length] = value.front();
|
||||
FunctionDefinition outlined;
|
||||
for (size_t i = 0; i < length; i++)
|
||||
outlined.body.statements.emplace_back(
|
||||
std::visit(copier, block->statements[start + i])
|
||||
);
|
||||
for (auto&& param: copier.m_parameters)
|
||||
outlined.parameters.emplace_back(TypedName{DebugData::create(), copier.m_translations.at(param), {}});
|
||||
|
||||
for (auto&& param: copier.m_returnParameters)
|
||||
{
|
||||
YulString newName = _context.dispenser.newName(param);
|
||||
outlined.returnVariables.emplace_back(TypedName{DebugData::create(), newName, {}});
|
||||
outlined.body.statements.emplace_back(
|
||||
Assignment{
|
||||
DebugData::create(),
|
||||
{Identifier{DebugData::create(), newName}},
|
||||
make_unique<Expression>(
|
||||
Identifier{DebugData::create(), copier.m_translations.at(param)}
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
outlined.returnVariables.emplace_back(TypedName{DebugData::create(), copier.m_translations.at(param), {}});
|
||||
}
|
||||
outlined.debugData = DebugData::create();
|
||||
outlined.name = _context.dispenser.newName(YulString{"outlined"});
|
||||
|
||||
// TODO handle case where replacements happen in the same block.
|
||||
|
||||
vector<Expression> arguments;
|
||||
for (YulString argName: copier.m_parameters)
|
||||
arguments.emplace_back(Identifier{DebugData::create(), argName});
|
||||
FunctionCall functionCall{
|
||||
DebugData::create(),
|
||||
Identifier{DebugData::create(), outlined.name},
|
||||
move(arguments)
|
||||
};
|
||||
|
||||
vector<Statement> newStatements;
|
||||
if (outlined.returnVariables.empty())
|
||||
newStatements.emplace_back(ExpressionStatement{
|
||||
DebugData::create(),
|
||||
move(functionCall)
|
||||
});
|
||||
else
|
||||
{
|
||||
vector<Identifier> assigned;
|
||||
for (YulString retName: copier.m_returnParameters)
|
||||
assigned.emplace_back(Identifier{DebugData::create(), retName});
|
||||
|
||||
// TODO not deterministic
|
||||
for (YulString createdVar: copier.m_declaredHere)
|
||||
newStatements.emplace_back(VariableDeclaration{
|
||||
DebugData::create(),
|
||||
{TypedName{DebugData::create(), createdVar, {}}},
|
||||
{}
|
||||
});
|
||||
newStatements.emplace_back(Assignment{
|
||||
DebugData::create(),
|
||||
move(assigned),
|
||||
make_unique<Expression>(move(functionCall))
|
||||
});
|
||||
}
|
||||
cout << "===========before==============\n" << AsmPrinter{}(_ast) << endl;
|
||||
|
||||
block->statements.erase(
|
||||
block->statements.begin() + static_cast<unsigned>(start),
|
||||
block->statements.begin() + static_cast<unsigned>(start + length)
|
||||
);
|
||||
block->statements.insert(
|
||||
block->statements.begin() + static_cast<unsigned>(start),
|
||||
make_move_iterator(newStatements.begin()),
|
||||
make_move_iterator(newStatements.end())
|
||||
);
|
||||
|
||||
// TODO also replace the other code.
|
||||
|
||||
_ast.statements.emplace_back(move(outlined));
|
||||
cout << "===========after==============\n" << AsmPrinter{}(_ast) << endl;
|
||||
cout << "============end============\n";
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
40
libyul/optimiser/FunctionOutliner.h
Normal file
40
libyul/optimiser/FunctionOutliner.h
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 <libyul/optimiser/ASTWalker.h>
|
||||
#include <libyul/optimiser/BlockHasher.h>
|
||||
#include <libyul/optimiser/OptimiserStep.h>
|
||||
#include <libyul/ASTForward.h>
|
||||
|
||||
namespace solidity::yul
|
||||
{
|
||||
|
||||
struct OptimiserStepContext;
|
||||
|
||||
class FunctionOutliner: public ASTModifier
|
||||
{
|
||||
public:
|
||||
static constexpr char const* name{"FunctionOutliner"};
|
||||
static void run(OptimiserStepContext&, Block& _ast);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
|
||||
}
|
@ -37,6 +37,7 @@
|
||||
#include <libyul/optimiser/ExpressionJoiner.h>
|
||||
#include <libyul/optimiser/ExpressionInliner.h>
|
||||
#include <libyul/optimiser/FullInliner.h>
|
||||
#include <libyul/optimiser/FunctionOutliner.h>
|
||||
#include <libyul/optimiser/ForLoopConditionIntoBody.h>
|
||||
#include <libyul/optimiser/ForLoopConditionOutOfBody.h>
|
||||
#include <libyul/optimiser/ForLoopInitRewriter.h>
|
||||
@ -199,6 +200,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
|
||||
FunctionGrouper,
|
||||
FunctionHoister,
|
||||
FunctionSpecializer,
|
||||
FunctionOutliner,
|
||||
LiteralRematerialiser,
|
||||
LoadResolver,
|
||||
LoopInvariantCodeMotion,
|
||||
@ -238,6 +240,7 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
|
||||
{FullInliner::name, 'i'},
|
||||
{FunctionGrouper::name, 'g'},
|
||||
{FunctionHoister::name, 'h'},
|
||||
{FunctionOutliner::name, 'y'},
|
||||
{FunctionSpecializer::name, 'F'},
|
||||
{LiteralRematerialiser::name, 'T'},
|
||||
{LoadResolver::name, 'L'},
|
||||
|
Loading…
Reference in New Issue
Block a user