Browse Source

Structural gas estimator.

cl-refactor
chriseth 10 years ago
parent
commit
5b8614f178
  1. 104
      libevmasm/GasMeter.cpp
  2. 67
      libevmasm/GasMeter.h
  3. 8
      libsolidity/ASTPrinter.cpp
  4. 8
      libsolidity/ASTPrinter.h
  5. 44
      libsolidity/ASTVisitor.h
  6. 108
      libsolidity/StructuralGasEstimator.cpp
  7. 59
      libsolidity/StructuralGasEstimator.h
  8. 18
      solc/CommandLineInterface.cpp
  9. 98
      test/GasMeter.cpp

104
libevmasm/GasMeter.cpp

@ -0,0 +1,104 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file GasMeter.cpp
* @author Christian <c@ethdev.com>
* @date 2015
*/
#include "GasMeter.h"
#include <libevmcore/Params.h>
using namespace std;
using namespace dev;
using namespace dev::eth;
GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption const& _other)
{
isInfinite = isInfinite || _other.isInfinite;
if (isInfinite)
return *this;
bigint v = bigint(value) + _other.value;
if (v > std::numeric_limits<u256>::max())
isInfinite = true;
else
value = u256(v);
return *this;
}
GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item)
{
switch (_item.type()) {
case Push:
case PushTag:
return runGas(Instruction::PUSH1);
case Tag:
return runGas(Instruction::JUMPDEST);
case Operation:
{
GasConsumption gas = runGas(_item.instruction());
switch (_item.instruction())
{
case Instruction::SSTORE:
// @todo logic can be improved
gas += c_sstoreSetGas;
break;
case Instruction::SLOAD:
gas += c_sloadGas;
break;
case Instruction::MSTORE:
case Instruction::MSTORE8:
case Instruction::MLOAD:
case Instruction::RETURN:
case Instruction::SHA3:
case Instruction::CALLDATACOPY:
case Instruction::CODECOPY:
case Instruction::EXTCODECOPY:
case Instruction::LOG0:
case Instruction::LOG1:
case Instruction::LOG2:
case Instruction::LOG3:
case Instruction::LOG4:
case Instruction::CALL:
case Instruction::CALLCODE:
case Instruction::CREATE:
case Instruction::EXP:
// @todo logic can be improved
gas = GasConsumption::infinite();
break;
default:
break;
}
return gas;
break;
}
default:
break;
}
return GasConsumption::infinite();
}
GasMeter::GasConsumption GasMeter::runGas(Instruction _instruction)
{
if (_instruction == Instruction::JUMPDEST)
return GasConsumption(1);
int tier = instructionInfo(_instruction).gasPriceTier;
return tier == InvalidTier ? GasConsumption::infinite() : c_tierStepGas[tier];
}

67
libevmasm/GasMeter.h

@ -0,0 +1,67 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file GasMeter.cpp
* @author Christian <c@ethdev.com>
* @date 2015
*/
#pragma once
#include <ostream>
#include <libevmasm/AssemblyItem.h>
namespace dev
{
namespace eth
{
/**
* Class that helps computing the maximum gas consumption for instructions.
*/
class GasMeter
{
public:
struct GasConsumption
{
GasConsumption(u256 _value = 0, bool _infinite = false): value(_value), isInfinite(_infinite) {}
static GasConsumption infinite() { return GasConsumption(0, true); }
GasConsumption& operator+=(GasConsumption const& _otherS);
std::ostream& operator<<(std::ostream& _str) const;
u256 value;
bool isInfinite;
};
/// Returns an upper bound on the gas consumed by the given instruction.
GasConsumption estimateMax(AssemblyItem const& _item);
private:
static GasConsumption runGas(Instruction _instruction);
};
inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption const& _consumption)
{
if (_consumption.isInfinite)
return _str << "inf";
else
return _str << _consumption.value;
}
}
}

8
libsolidity/ASTPrinter.cpp

@ -30,8 +30,10 @@ namespace dev
namespace solidity
{
ASTPrinter::ASTPrinter(ASTNode const& _ast, string const& _source):
m_indentation(0), m_source(_source), m_ast(&_ast)
ASTPrinter::ASTPrinter(ASTNode const& _ast,
string const& _source,
StructuralGasEstimator::ASTGasConsumption const& _gasCosts
): m_indentation(0), m_source(_source), m_ast(&_ast), m_gasCosts(_gasCosts)
{
}
@ -503,6 +505,8 @@ void ASTPrinter::endVisit(Literal const&)
void ASTPrinter::printSourcePart(ASTNode const& _node)
{
if (m_gasCosts.count(&_node))
*m_ostream << getIndentation() << " Gas costs: " << m_gasCosts.at(&_node) << endl;
if (!m_source.empty())
{
SourceLocation const& location(_node.getLocation());

8
libsolidity/ASTPrinter.h

@ -24,6 +24,7 @@
#include <ostream>
#include <libsolidity/ASTVisitor.h>
#include <libsolidity/StructuralGasEstimator.h>
namespace dev
{
@ -38,7 +39,11 @@ class ASTPrinter: public ASTConstVisitor
public:
/// Create a printer for the given abstract syntax tree. If the source is specified,
/// the corresponding parts of the source are printed with each node.
ASTPrinter(ASTNode const& _ast, std::string const& _source = std::string());
ASTPrinter(
ASTNode const& _ast,
std::string const& _source = std::string(),
StructuralGasEstimator::ASTGasConsumption const& _gasCosts = {}
);
/// Output the string representation of the AST to _stream.
void print(std::ostream& _stream);
@ -128,6 +133,7 @@ private:
int m_indentation;
std::string m_source;
ASTNode const* m_ast;
StructuralGasEstimator::ASTGasConsumption m_gasCosts;
std::ostream* m_ostream;
};

44
libsolidity/ASTVisitor.h

@ -23,6 +23,8 @@
#pragma once
#include <string>
#include <functional>
#include <vector>
#include <libsolidity/AST.h>
namespace dev
@ -218,5 +220,47 @@ protected:
virtual void endVisitNode(ASTNode const&) { }
};
/**
* Utility class that visits the AST in depth-first order and calls a function on each node and each edge.
* Child nodes are only visited if the node callback of the parent returns true.
* The node callback of a parent is called before any edge or node callback involving the children.
* The edge callbacks of all children are called before the edge callback of the parent.
* This way, the node callback can be used as an initializing callback and the edge callbacks can be
* used to compute a "reduce" function.
*/
class ASTReduce: public ASTConstVisitor
{
public:
/**
* Constructs a new ASTReduce object with the given callback functions.
* @param _onNode called for each node, before its child edges and nodes, should return true to descend deeper
* @param _onEdge called for each edge with (parent, child)
*/
ASTReduce(
std::function<bool(ASTNode const&)> _onNode,
std::function<void(ASTNode const&, ASTNode const&)> _onEdge
): m_onNode(_onNode), m_onEdge(_onEdge)
{
}
protected:
bool visitNode(ASTNode const& _node) override
{
m_parents.push_back(&_node);
return m_onNode(_node);
}
void endVisitNode(ASTNode const& _node) override
{
m_parents.pop_back();
if (!m_parents.empty())
m_onEdge(*m_parents.back(), _node);
}
private:
std::vector<ASTNode const*> m_parents;
std::function<bool(ASTNode const&)> m_onNode;
std::function<void(ASTNode const&, ASTNode const&)> m_onEdge;
};
}
}

108
libsolidity/StructuralGasEstimator.cpp

@ -0,0 +1,108 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2015
* Gas consumption estimator working alongside the AST.
*/
#include "StructuralGasEstimator.h"
#include <map>
#include <functional>
#include <libsolidity/AST.h>
#include <libsolidity/ASTVisitor.h>
using namespace std;
using namespace dev;
using namespace dev::eth;
using namespace dev::solidity;
map<ASTNode const*, GasMeter::GasConsumption[2]> StructuralGasEstimator::performEstimation(
AssemblyItems const& _items,
vector<ASTNode const*> const& _ast
)
{
map<SourceLocation, GasMeter::GasConsumption> particularCosts;
GasMeter meter;
for (auto const& item: _items)
particularCosts[item.getLocation()] += meter.estimateMax(item);
map<ASTNode const*, GasMeter::GasConsumption[2]> gasCosts;
auto onNode = [&](ASTNode const& _node)
{
gasCosts[&_node][0] = gasCosts[&_node][1] = particularCosts[_node.getLocation()];
return true;
};
auto onEdge = [&](ASTNode const& _parent, ASTNode const& _child)
{
gasCosts[&_parent][1] += gasCosts[&_child][1];
};
ASTReduce folder(onNode, onEdge);
for (ASTNode const* ast: _ast)
ast->accept(folder);
return gasCosts;
}
map<ASTNode const*, GasMeter::GasConsumption> StructuralGasEstimator::breakToStatementLevel(
map<ASTNode const*, GasMeter::GasConsumption[2]> const& _gasCosts,
vector<ASTNode const*> const& _roots
)
{
// first pass: statementDepth[node] is the distance from the deepend statement to node
// in direction of the tree root (or undefined if not possible)
map<ASTNode const*, int> statementDepth;
auto onNodeFirstPass = [&](ASTNode const& _node)
{
if (dynamic_cast<Statement const*>(&_node))
statementDepth[&_node] = 0;
return true;
};
auto onEdgeFirstPass = [&](ASTNode const& _parent, ASTNode const& _child)
{
if (statementDepth.count(&_child))
statementDepth[&_parent] = max(statementDepth[&_parent], statementDepth[&_child] + 1);
};
ASTReduce firstPass(onNodeFirstPass, onEdgeFirstPass);
for (ASTNode const* node: _roots)
node->accept(firstPass);
// we use the location of a node if
// - its statement depth is 0 or
// - its statement depth is undefined but the parent's statement depth is at least 1
map<ASTNode const*, GasMeter::GasConsumption> gasCosts;
auto onNodeSecondPass = [&](ASTNode const& _node)
{
return statementDepth.count(&_node);
};
auto onEdgeSecondPass = [&](ASTNode const& _parent, ASTNode const& _child)
{
bool useNode = false;
if (statementDepth.count(&_child))
useNode = statementDepth[&_child] == 0;
else
useNode = statementDepth.count(&_parent) && statementDepth.at(&_parent) > 0;
if (useNode)
gasCosts[&_child] = _gasCosts.at(&_child)[1];
};
ASTReduce secondPass(onNodeSecondPass, onEdgeSecondPass);
for (ASTNode const* node: _roots)
node->accept(secondPass);
// gasCosts should only contain non-overlapping locations
return gasCosts;
}

59
libsolidity/StructuralGasEstimator.h

@ -0,0 +1,59 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2015
* Gas consumption estimator working alongside the AST.
*/
#pragma once
#include <vector>
#include <map>
#include <libsolidity/ASTForward.h>
#include <libevmasm/GasMeter.h>
#include <libevmasm/Assembly.h>
namespace dev
{
namespace solidity
{
class StructuralGasEstimator
{
public:
using ASTGasConsumption = std::map<ASTNode const*, eth::GasMeter::GasConsumption>;
/// Estimates the gas consumption for every assembly item in the given assembly and stores
/// it by source location.
/// @returns a mapping from each AST node to a pair of its particular and syntactically accumulated gas costs.
std::map<ASTNode const*, eth::GasMeter::GasConsumption[2]> performEstimation(
eth::AssemblyItems const& _items,
std::vector<ASTNode const*> const& _ast
);
/// @returns a mapping from nodes with non-overlapping source locations to gas consumptions such that
/// the following source locations are part of the mapping:
/// 1. source locations of statements that do not contain other statements
/// 2. maximal source locations that do not overlap locations coming from the first rule
ASTGasConsumption breakToStatementLevel(
std::map<ASTNode const*, eth::GasMeter::GasConsumption[2]> const& _gasCosts,
std::vector<ASTNode const*> const& _roots
);
};
}
}

18
solc/CommandLineInterface.cpp

@ -42,6 +42,7 @@
#include <libsolidity/Exceptions.h>
#include <libsolidity/CompilerStack.h>
#include <libsolidity/SourceReferenceFormatter.h>
#include <libsolidity/StructuralGasEstimator.h>
using namespace std;
namespace po = boost::program_options;
@ -464,6 +465,17 @@ void CommandLineInterface::handleAst(string const& _argStr)
// do we need AST output?
if (m_args.count(_argStr))
{
StructuralGasEstimator gasEstimator;
vector<ASTNode const*> asts;
for (auto const& sourceCode: m_sourceCodes)
asts.push_back(&m_compiler->getAST(sourceCode.first));
map<ASTNode const*, eth::GasMeter::GasConsumption> gasCosts;
if (m_compiler->getRuntimeAssemblyItems())
gasCosts = gasEstimator.breakToStatementLevel(
gasEstimator.performEstimation(*m_compiler->getRuntimeAssemblyItems(), asts),
asts
);
auto choice = m_args[_argStr].as<OutputType>();
if (outputToStdout(choice))
{
@ -473,7 +485,11 @@ void CommandLineInterface::handleAst(string const& _argStr)
cout << endl << "======= " << sourceCode.first << " =======" << endl;
if (_argStr == g_argAstStr)
{
ASTPrinter printer(m_compiler->getAST(sourceCode.first), sourceCode.second);
ASTPrinter printer(
m_compiler->getAST(sourceCode.first),
sourceCode.second,
gasCosts
);
printer.print(cout);
}
else

98
test/GasMeter.cpp

@ -0,0 +1,98 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum 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.
cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2015
* Unit tests for the gas estimator.
*/
#include <test/libsolidity/solidityExecutionFramework.h>
#include <libsolidity/AST.h>
#include <libsolidity/StructuralGasEstimator.h>
#include <libsolidity/SourceReferenceFormatter.h>
using namespace std;
using namespace dev::eth;
using namespace dev::solidity;
namespace dev
{
namespace solidity
{
namespace test
{
class GasMeterTestFramework: public ExecutionFramework
{
public:
GasMeterTestFramework() { }
void compile(string const& _sourceCode)
{
m_compiler.setSource(_sourceCode);
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed");
StructuralGasEstimator estimator;
AssemblyItems const* items = m_compiler.getRuntimeAssemblyItems("");
ASTNode const& sourceUnit = m_compiler.getAST();
BOOST_REQUIRE(items != nullptr);
m_gasCosts = estimator.breakToStatementLevel(
estimator.performEstimation(*items, vector<ASTNode const*>({&sourceUnit})),
{&sourceUnit}
);
}
protected:
dev::solidity::CompilerStack m_compiler;
map<ASTNode const*, eth::GasMeter::GasConsumption> m_gasCosts;
};
BOOST_FIXTURE_TEST_SUITE(GasMeterTests, GasMeterTestFramework)
BOOST_AUTO_TEST_CASE(non_overlapping_filtered_costs)
{
char const* sourceCode = R"(
contract test {
bytes x;
function f(uint a) returns (uint b) {
x.length = a;
for (; a < 200; ++a) {
x[a] = 9;
b = a * a;
}
return f(a - 1);
}
}
)";
compile(sourceCode);
for (auto first = m_gasCosts.cbegin(); first != m_gasCosts.cend(); ++first)
{
auto second = first;
for (++second; second != m_gasCosts.cend(); ++second)
if (first->first->getLocation().intersects(second->first->getLocation()))
{
BOOST_CHECK_MESSAGE(false, "Source locations should not overlap!");
SourceReferenceFormatter::printSourceLocation(cout, first->first->getLocation(), m_compiler.getScanner());
SourceReferenceFormatter::printSourceLocation(cout, second->first->getLocation(), m_compiler.getScanner());
}
}
}
BOOST_AUTO_TEST_SUITE_END()
}
}
}
Loading…
Cancel
Save