Merge pull request #7826 from ethereum/develop

Merge develop into develop_060
This commit is contained in:
chriseth 2019-11-28 13:37:19 +01:00 committed by GitHub
commit f7fc42d8c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 440 additions and 22 deletions

View File

@ -4,7 +4,7 @@
The docker images are build locally on the developer machine:
```!sh
```sh
cd .circleci/docker/
docker build -t ethereum/solidity-buildpack-deps:ubuntu1904-<revision> -f Dockerfile.ubuntu1904 .
@ -21,7 +21,7 @@ where the image tag reflects the target OS and revision to build Solidity and ru
### Testing docker images locally
```!sh
```sh
cd solidity
# Mounts your local solidity directory in docker container for testing
docker run -v `pwd`:/src/solidity -ti ethereum/solidity-buildpack-deps:ubuntu1904-<revision> /bin/bash

View File

@ -47,6 +47,7 @@ Language Features:
Compiler Features:
* Yul: When compiling via Yul, string literals from the Solidity code are kept as string literals if every character is safely printable.
* Yul Optimizer: Perform loop-invariant code motion.
Bugfixes:

View File

@ -31,7 +31,7 @@ Instructions about how to build and install the Solidity compiler can be found i
A "Hello World" program in Solidity is of even less use than in other languages, but still:
```
```solidity
pragma solidity ^0.5.0;
contract HelloWorld {

View File

@ -208,7 +208,7 @@ The evaluation order of expressions is not specified (more formally, the order
in which the children of one node in the expression tree are evaluated is not
specified, but they are of course evaluated before the node itself). It is only
guaranteed that statements are executed in order and short-circuiting for
boolean expressions is done. See :ref:`order` for more information.
boolean expressions is done.
.. index:: ! assignment

View File

@ -538,6 +538,15 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
}
}
bool SMTEncoder::visit(ModifierInvocation const& _node)
{
if (auto const* args = _node.arguments())
for (auto const& arg: *args)
if (arg)
arg->accept(*this);
return false;
}
void SMTEncoder::initContract(ContractDefinition const& _contract)
{
solAssert(m_currentContract == nullptr, "");
@ -606,9 +615,7 @@ void SMTEncoder::endVisit(Identifier const& _identifier)
defineExpr(_identifier, m_context.thisAddress());
m_uninterpretedTerms.insert(&_identifier);
}
else if (
_identifier.annotation().type->category() != Type::Category::Modifier
)
else
createExpr(_identifier);
}

View File

@ -82,6 +82,7 @@ protected:
bool visit(BinaryOperation const& _node) override;
void endVisit(BinaryOperation const& _node) override;
void endVisit(FunctionCall const& _node) override;
bool visit(ModifierInvocation const& _node) override;
void endVisit(Identifier const& _node) override;
void endVisit(ElementaryTypeNameExpression const& _node) override;
void endVisit(Literal const& _node) override;

View File

@ -112,6 +112,8 @@ add_library(yul
optimiser/KnowledgeBase.h
optimiser/LoadResolver.cpp
optimiser/LoadResolver.h
optimiser/LoopInvariantCodeMotion.cpp
optimiser/LoopInvariantCodeMotion.h
optimiser/MainFunction.cpp
optimiser/MainFunction.h
optimiser/Metrics.cpp

View File

@ -0,0 +1,115 @@
/*
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/>.
*/
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
#include <libyul/optimiser/CallGraphGenerator.h>
#include <libyul/optimiser/NameCollector.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/SSAValueTracker.h>
#include <libyul/AsmData.h>
#include <libdevcore/CommonData.h>
#include <utility>
using namespace std;
using namespace dev;
using namespace yul;
void LoopInvariantCodeMotion::run(OptimiserStepContext& _context, Block& _ast)
{
map<YulString, SideEffects> functionSideEffects =
SideEffectsPropagator::sideEffects(_context.dialect, CallGraphGenerator::callGraph(_ast));
set<YulString> ssaVars = SSAValueTracker::ssaVariables(_ast);
LoopInvariantCodeMotion{_context.dialect, ssaVars, functionSideEffects}(_ast);
}
void LoopInvariantCodeMotion::operator()(Block& _block)
{
iterateReplacing(
_block.statements,
[&](Statement& _s) -> optional<vector<Statement>>
{
visit(_s);
if (holds_alternative<ForLoop>(_s))
return rewriteLoop(get<ForLoop>(_s));
else
return {};
}
);
}
bool LoopInvariantCodeMotion::canBePromoted(
VariableDeclaration const& _varDecl,
set<YulString> const& _varsDefinedInCurrentScope
) const
{
// A declaration can be promoted iff
// 1. Its LHS is a SSA variable
// 2. Its RHS only references SSA variables declared outside of the current scope
// 3. Its RHS is movable
for (auto const& var: _varDecl.variables)
if (!m_ssaVariables.count(var.name))
return false;
if (_varDecl.value)
{
for (auto const& ref: ReferencesCounter::countReferences(*_varDecl.value, ReferencesCounter::OnlyVariables))
if (_varsDefinedInCurrentScope.count(ref.first) || !m_ssaVariables.count(ref.first))
return false;
if (!SideEffectsCollector{m_dialect, *_varDecl.value, &m_functionSideEffects}.movable())
return false;
}
return true;
}
optional<vector<Statement>> LoopInvariantCodeMotion::rewriteLoop(ForLoop& _for)
{
assertThrow(_for.pre.statements.empty(), OptimizerException, "");
vector<Statement> replacement;
for (Block* block: {&_for.post, &_for.body})
{
set<YulString> varsDefinedInScope;
iterateReplacing(
block->statements,
[&](Statement& _s) -> optional<vector<Statement>>
{
if (holds_alternative<VariableDeclaration>(_s))
{
VariableDeclaration const& varDecl = std::get<VariableDeclaration>(_s);
if (canBePromoted(varDecl, varsDefinedInScope))
{
replacement.emplace_back(std::move(_s));
// Do not add the variables declared here to varsDefinedInScope because we are moving them.
return vector<Statement>{};
}
for (auto const& var: varDecl.variables)
varsDefinedInScope.insert(var.name);
}
return {};
}
);
}
if (replacement.empty())
return {};
else
{
replacement.emplace_back(std::move(_for));
return { std::move(replacement) };
}
}

View File

@ -0,0 +1,67 @@
/*
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/>.
*/
#pragma once
#include <libyul/optimiser/ASTWalker.h>
#include <libyul/optimiser/Semantics.h>
#include <libyul/optimiser/OptimiserStep.h>
namespace yul
{
/**
* Loop-invariant code motion.
*
* This optimization moves movable SSA variable declarations outside the loop.
*
* Only statements at the top level in a loop's body or post block are considered, i.e variable
* declarations inside conditional branches will not be moved out of the loop.
*
* Requirements:
* - The Disambiguator, ForLoopInitRewriter and FunctionHoister must be run upfront.
* - Expression splitter and SSA transform should be run upfront to obtain better result.
*/
class LoopInvariantCodeMotion: public ASTModifier
{
public:
static constexpr char const* name{"LoopInvariantCodeMotion"};
static void run(OptimiserStepContext& _context, Block& _ast);
void operator()(Block& _block) override;
private:
explicit LoopInvariantCodeMotion(
Dialect const& _dialect,
std::set<YulString> const& _ssaVariables,
std::map<YulString, SideEffects> const& _functionSideEffects
):
m_dialect(_dialect),
m_ssaVariables(_ssaVariables),
m_functionSideEffects(_functionSideEffects)
{ }
/// @returns true if the given variable declaration can be moved to in front of the loop.
bool canBePromoted(VariableDeclaration const& _varDecl, std::set<YulString> const& _varsDefinedInCurrentScope) const;
std::optional<std::vector<Statement>> rewriteLoop(ForLoop& _for);
Dialect const& m_dialect;
std::set<YulString> const& m_ssaVariables;
std::map<YulString, SideEffects> const& m_functionSideEffects;
};
}

View File

@ -49,27 +49,28 @@ void ReferencesCounter::operator()(Identifier const& _identifier)
void ReferencesCounter::operator()(FunctionCall const& _funCall)
{
++m_references[_funCall.functionName.name];
if (m_countWhat == VariablesAndFunctions)
++m_references[_funCall.functionName.name];
ASTWalker::operator()(_funCall);
}
map<YulString, size_t> ReferencesCounter::countReferences(Block const& _block)
map<YulString, size_t> ReferencesCounter::countReferences(Block const& _block, CountWhat _countWhat)
{
ReferencesCounter counter;
ReferencesCounter counter(_countWhat);
counter(_block);
return counter.references();
}
map<YulString, size_t> ReferencesCounter::countReferences(FunctionDefinition const& _function)
map<YulString, size_t> ReferencesCounter::countReferences(FunctionDefinition const& _function, CountWhat _countWhat)
{
ReferencesCounter counter;
ReferencesCounter counter(_countWhat);
counter(_function);
return counter.references();
}
map<YulString, size_t> ReferencesCounter::countReferences(Expression const& _expression)
map<YulString, size_t> ReferencesCounter::countReferences(Expression const& _expression, CountWhat _countWhat)
{
ReferencesCounter counter;
ReferencesCounter counter(_countWhat);
counter.visit(_expression);
return counter.references();
}

View File

@ -54,16 +54,23 @@ private:
class ReferencesCounter: public ASTWalker
{
public:
enum CountWhat { VariablesAndFunctions, OnlyVariables };
explicit ReferencesCounter(CountWhat _countWhat = VariablesAndFunctions):
m_countWhat(_countWhat)
{}
using ASTWalker::operator ();
virtual void operator()(Identifier const& _identifier);
virtual void operator()(FunctionCall const& _funCall);
static std::map<YulString, size_t> countReferences(Block const& _block);
static std::map<YulString, size_t> countReferences(FunctionDefinition const& _function);
static std::map<YulString, size_t> countReferences(Expression const& _expression);
static std::map<YulString, size_t> countReferences(Block const& _block, CountWhat _countWhat = VariablesAndFunctions);
static std::map<YulString, size_t> countReferences(FunctionDefinition const& _function, CountWhat _countWhat = VariablesAndFunctions);
static std::map<YulString, size_t> countReferences(Expression const& _expression, CountWhat _countWhat = VariablesAndFunctions);
std::map<YulString, size_t> const& references() const { return m_references; }
private:
CountWhat m_countWhat = CountWhat::VariablesAndFunctions;
std::map<YulString, size_t> m_references;
};

View File

@ -49,6 +49,16 @@ void SSAValueTracker::operator()(VariableDeclaration const& _varDecl)
setValue(_varDecl.variables.front().name, _varDecl.value.get());
}
set<YulString> SSAValueTracker::ssaVariables(Block const& _ast)
{
SSAValueTracker t;
t(_ast);
set<YulString> ssaVars;
for (auto const& value: t.values())
ssaVars.insert(value.first);
return ssaVars;
}
void SSAValueTracker::setValue(YulString _name, Expression const* _value)
{
assertThrow(

View File

@ -49,6 +49,8 @@ public:
std::map<YulString, Expression const*> const& values() const { return m_values; }
Expression const* value(YulString _name) const { return m_values.at(_name); }
static std::set<YulString> ssaVariables(Block const& _ast);
private:
void setValue(YulString _name, Expression const* _value);

View File

@ -52,6 +52,7 @@
#include <libyul/optimiser/RedundantAssignEliminator.h>
#include <libyul/optimiser/VarNameCleaner.h>
#include <libyul/optimiser/LoadResolver.h>
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
#include <libyul/optimiser/Metrics.h>
#include <libyul/backends/evm/ConstantOptimiser.h>
#include <libyul/AsmAnalysis.h>
@ -129,7 +130,8 @@ void OptimiserSuite::run(
RedundantAssignEliminator::name,
ExpressionSimplifier::name,
CommonSubexpressionEliminator::name,
LoadResolver::name
LoadResolver::name,
LoopInvariantCodeMotion::name
}, ast);
}
@ -357,6 +359,7 @@ map<string, unique_ptr<OptimiserStep>> const& OptimiserSuite::allSteps()
FunctionHoister,
LiteralRematerialiser,
LoadResolver,
LoopInvariantCodeMotion,
RedundantAssignEliminator,
Rematerialiser,
SSAReverser,

View File

@ -10,3 +10,4 @@ fo
compilability
errorstring
hist
otion

View File

@ -0,0 +1,17 @@
pragma experimental SMTChecker;
contract A {
uint x;
constructor() public {
x = 2;
}
}
contract B is A {
constructor() A() public {
x = 3;
}
}
// ----
// Warning: (56-90): Assertion checker does not yet support constructors.
// Warning: (113-151): Assertion checker does not yet support constructors.

View File

@ -41,6 +41,7 @@
#include <libyul/optimiser/ForLoopConditionOutOfBody.h>
#include <libyul/optimiser/ForLoopInitRewriter.h>
#include <libyul/optimiser/LoadResolver.h>
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
#include <libyul/optimiser/MainFunction.h>
#include <libyul/optimiser/NameDisplacer.h>
#include <libyul/optimiser/Rematerialiser.h>
@ -279,6 +280,12 @@ TestCase::TestResult YulOptimizerTest::run(ostream& _stream, string const& _line
ExpressionJoiner::run(*m_context, *m_ast);
ExpressionJoiner::run(*m_context, *m_ast);
}
else if (m_optimizerStep == "loopInvariantCodeMotion")
{
disambiguate();
ForLoopInitRewriter::run(*m_context, *m_ast);
LoopInvariantCodeMotion::run(*m_context, *m_ast);
}
else if (m_optimizerStep == "controlFlowSimplifier")
{
disambiguate();

View File

@ -483,7 +483,7 @@
// let _5 := 0xffffffffffffffff
// if gt(offset, _5) { revert(_1, _1) }
// let value2 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_4, offset), _3)
// let offset_1 := calldataload(add(_4, 96))
// let offset_1 := calldataload(add(_4, 0x60))
// if gt(offset_1, _5) { revert(_1, _1) }
// let value3 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_4, offset_1), _3)
// sstore(calldataload(_4), calldataload(add(_4, 0x20)))

View File

@ -311,7 +311,7 @@
// }
// b := add(b, _5)
// }
// if lt(m, n) { validatePairing(0x64) }
// if lt(m, n) { validatePairing(100) }
// if iszero(eq(mod(keccak256(0x2a0, add(b, not(671))), _2), challenge))
// {
// mstore(0, 404)

View File

@ -12,7 +12,7 @@
// {
// {
// let y := mload(0x20)
// for { } and(y, 8) { if y { revert(0, 0) } }
// for { } iszero(iszero(and(y, 8))) { if y { revert(0, 0) } }
// {
// if y { continue }
// sstore(1, 0)

View File

@ -0,0 +1,34 @@
{
sstore(0, array_sum(calldataload(0)))
function array_sum(x) -> sum {
let length := calldataload(x)
for { let i := 0 } lt(i, length) { i := add(i, 1) } {
sum := add(sum, array_load(x, i))
}
}
function array_load(x, i) -> v {
let len := calldataload(x)
if iszero(lt(i, len)) { revert(0, 0) }
let data := add(x, 0x20)
v := calldataload(add(data, mul(i, 0x20)))
// this is just to have some additional code that
// can be moved out of the loop.
v := add(v, calldataload(7))
}
}
// ====
// step: fullSuite
// ----
// {
// {
// let _1 := calldataload(0)
// let sum := 0
// let i := sum
// for { } lt(i, calldataload(_1)) { i := add(i, 1) }
// {
// sum := add(sum, add(calldataload(add(add(_1, mul(i, 0x20)), 0x20)), calldataload(7)))
// }
// sstore(0, sum)
// }
// }

View File

@ -0,0 +1,23 @@
{
let b := 1
for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } {
let c := mload(3) // c cannot be moved because non-movable
let not_inv := add(b, c) // no_inv cannot be moved because its value depends on c
a := add(a, 1)
mstore(a, not_inv)
}
}
// ====
// step: loopInvariantCodeMotion
// ----
// {
// let b := 1
// let a := 1
// for { } iszero(eq(a, 10)) { a := add(a, 1) }
// {
// let c := mload(3)
// let not_inv := add(b, c)
// a := add(a, 1)
// mstore(a, not_inv)
// }
// }

View File

@ -0,0 +1,26 @@
{
let b := 1
// tests if c, d, and inv can be moved outside in single pass
for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } {
let c := b
let d := mul(c, 2)
let inv := add(c, d)
a := add(a, 1)
mstore(a, inv)
}
}
// ====
// step: loopInvariantCodeMotion
// ----
// {
// let b := 1
// let a := 1
// let c := b
// let d := mul(c, 2)
// let inv := add(c, d)
// for { } iszero(eq(a, 10)) { a := add(a, 1) }
// {
// a := add(a, 1)
// mstore(a, inv)
// }
// }

View File

@ -0,0 +1,23 @@
{
let b := 1
for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } {
let not_inv := add(b, 42)
not_inv := add(not_inv, 1)
a := add(a, 1)
mstore(a, not_inv)
}
}
// ====
// step: loopInvariantCodeMotion
// ----
// {
// let b := 1
// let a := 1
// for { } iszero(eq(a, 10)) { a := add(a, 1) }
// {
// let not_inv := add(b, 42)
// not_inv := add(not_inv, 1)
// a := add(a, 1)
// mstore(a, not_inv)
// }
// }

View File

@ -0,0 +1,21 @@
{
let b := 0
for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } {
let inv := mload(b)
a := add(a, 1)
mstore(a, inv)
}
}
// ====
// step: loopInvariantCodeMotion
// ----
// {
// let b := 0
// let a := 1
// for { } iszero(eq(a, 10)) { a := add(a, 1) }
// {
// let inv := mload(b)
// a := add(a, 1)
// mstore(a, inv)
// }
// }

View File

@ -0,0 +1,25 @@
{
let b := 1
for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } {
for { let a2 := 1 } iszero(eq(a2, 10)) { a2 := add(a2, 1) } {
let inv := add(b, 42)
mstore(a, inv)
}
a := add(a, 1)
}
}
// ====
// step: loopInvariantCodeMotion
// ----
// {
// let b := 1
// let a := 1
// let inv := add(b, 42)
// for { } iszero(eq(a, 10)) { a := add(a, 1) }
// {
// let a2 := 1
// for { } iszero(eq(a2, 10)) { a2 := add(a2, 1) }
// { mstore(a, inv) }
// a := add(a, 1)
// }
// }

View File

@ -0,0 +1,21 @@
{
let b := 1
for { let a := 1 } iszero(eq(a, 10)) { a := add(a, 1) } {
let inv := add(b, 42)
a := add(a, 1)
mstore(a, inv)
}
}
// ====
// step: loopInvariantCodeMotion
// ----
// {
// let b := 1
// let a := 1
// let inv := add(b, 42)
// for { } iszero(eq(a, 10)) { a := add(a, 1) }
// {
// a := add(a, 1)
// mstore(a, inv)
// }
// }

View File

@ -62,6 +62,7 @@
#include <libyul/optimiser/VarDeclInitializer.h>
#include <libyul/optimiser/VarNameCleaner.h>
#include <libyul/optimiser/LoadResolver.h>
#include <libyul/optimiser/LoopInvariantCodeMotion.h>
#include <libyul/backends/evm/EVMDialect.h>
@ -141,7 +142,7 @@ public:
cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-init-rewriter/for-loop-condition-(I)nto-body/" << endl;
cout << " for-loop-condition-(O)ut-of-body/s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser/" << endl;
cout << " co(n)trol flow simplifier/stack com(p)ressor/(D)ead code eliminator/(L)oad resolver/" << endl;
cout << " (C)onditional simplifier?" << endl;
cout << " (C)onditional simplifier/loop-invariant code (M)otion?" << endl;
cout.flush();
int option = readStandardInputChar();
cout << ' ' << char(option) << endl;
@ -236,6 +237,9 @@ public:
case 'L':
LoadResolver::run(context, *m_ast);
break;
case 'M':
LoopInvariantCodeMotion::run(context, *m_ast);
break;
default:
cout << "Unknown option." << endl;
}