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
|
"gvif" // Run full inliner
|
||||||
"CTUcarrLsTFOtfDncarrIulc" // SSA plus simplify
|
"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.
|
/// No optimisations at all - not recommended.
|
||||||
static OptimiserSettings none()
|
static OptimiserSettings none()
|
||||||
|
@ -143,6 +143,8 @@ add_library(yul
|
|||||||
optimiser/FunctionGrouper.h
|
optimiser/FunctionGrouper.h
|
||||||
optimiser/FunctionHoister.cpp
|
optimiser/FunctionHoister.cpp
|
||||||
optimiser/FunctionHoister.h
|
optimiser/FunctionHoister.h
|
||||||
|
optimiser/FunctionOutliner.h
|
||||||
|
optimiser/FunctionOutliner.cpp
|
||||||
optimiser/FunctionSpecializer.cpp
|
optimiser/FunctionSpecializer.cpp
|
||||||
optimiser/FunctionSpecializer.h
|
optimiser/FunctionSpecializer.h
|
||||||
optimiser/InlinableExpressionFunctionFinder.cpp
|
optimiser/InlinableExpressionFunctionFinder.cpp
|
||||||
|
@ -67,8 +67,10 @@ public:
|
|||||||
static constexpr uint64_t fnvPrime = 1099511628211u;
|
static constexpr uint64_t fnvPrime = 1099511628211u;
|
||||||
static constexpr uint64_t fnvEmptyHash = 14695981039346656037u;
|
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) {}
|
BlockHasher(std::map<Block const*, uint64_t>& _blockHashes): m_blockHashes(_blockHashes) {}
|
||||||
|
private:
|
||||||
|
|
||||||
void hash8(uint8_t _value)
|
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/ExpressionJoiner.h>
|
||||||
#include <libyul/optimiser/ExpressionInliner.h>
|
#include <libyul/optimiser/ExpressionInliner.h>
|
||||||
#include <libyul/optimiser/FullInliner.h>
|
#include <libyul/optimiser/FullInliner.h>
|
||||||
|
#include <libyul/optimiser/FunctionOutliner.h>
|
||||||
#include <libyul/optimiser/ForLoopConditionIntoBody.h>
|
#include <libyul/optimiser/ForLoopConditionIntoBody.h>
|
||||||
#include <libyul/optimiser/ForLoopConditionOutOfBody.h>
|
#include <libyul/optimiser/ForLoopConditionOutOfBody.h>
|
||||||
#include <libyul/optimiser/ForLoopInitRewriter.h>
|
#include <libyul/optimiser/ForLoopInitRewriter.h>
|
||||||
@ -199,6 +200,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
|
|||||||
FunctionGrouper,
|
FunctionGrouper,
|
||||||
FunctionHoister,
|
FunctionHoister,
|
||||||
FunctionSpecializer,
|
FunctionSpecializer,
|
||||||
|
FunctionOutliner,
|
||||||
LiteralRematerialiser,
|
LiteralRematerialiser,
|
||||||
LoadResolver,
|
LoadResolver,
|
||||||
LoopInvariantCodeMotion,
|
LoopInvariantCodeMotion,
|
||||||
@ -238,6 +240,7 @@ map<string, char> const& OptimiserSuite::stepNameToAbbreviationMap()
|
|||||||
{FullInliner::name, 'i'},
|
{FullInliner::name, 'i'},
|
||||||
{FunctionGrouper::name, 'g'},
|
{FunctionGrouper::name, 'g'},
|
||||||
{FunctionHoister::name, 'h'},
|
{FunctionHoister::name, 'h'},
|
||||||
|
{FunctionOutliner::name, 'y'},
|
||||||
{FunctionSpecializer::name, 'F'},
|
{FunctionSpecializer::name, 'F'},
|
||||||
{LiteralRematerialiser::name, 'T'},
|
{LiteralRematerialiser::name, 'T'},
|
||||||
{LoadResolver::name, 'L'},
|
{LoadResolver::name, 'L'},
|
||||||
|
Loading…
Reference in New Issue
Block a user