Browse Source

Gas estimation taking known state into account.

cl-refactor
chriseth 10 years ago
parent
commit
31c6ee40f6
  1. 1
      libevmasm/Assembly.cpp
  2. 6
      libevmasm/AssemblyItem.h
  3. 126
      libevmasm/GasMeter.cpp
  4. 27
      libevmasm/GasMeter.h
  5. 9
      libevmasm/KnownState.cpp
  6. 4
      libevmasm/KnownState.h
  7. 20
      libsolidity/ASTVisitor.h
  8. 38
      libsolidity/CompilerStack.cpp
  9. 5
      libsolidity/CompilerStack.h
  10. 39
      libsolidity/StructuralGasEstimator.cpp
  11. 4
      libsolidity/StructuralGasEstimator.h
  12. 60
      test/libsolidity/GasMeter.cpp
  13. 11
      test/libsolidity/solidityExecutionFramework.h

1
libevmasm/Assembly.cpp

@ -431,6 +431,7 @@ bytes Assembly::assemble() const
case PushSubSize:
{
auto s = m_data[i.data()].size();
i.setPushedValue(u256(s));
byte b = max<unsigned>(1, dev::bytesRequired(s));
ret.push_back((byte)Instruction::PUSH1 - 1 + b);
ret.resize(ret.size() + b);

6
libevmasm/AssemblyItem.h

@ -84,11 +84,17 @@ public:
JumpType getJumpType() const { return m_jumpType; }
std::string getJumpTypeAsString() const;
void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared<u256>(_value); }
u256 const* pushedValue() const { return m_pushedValue.get(); }
private:
AssemblyItemType m_type;
u256 m_data;
SourceLocation m_location;
JumpType m_jumpType = JumpType::Ordinary;
/// Pushed value for operations with data to be determined during assembly stage,
/// e.g. PushSubSize, PushTag, PushSub, etc.
mutable std::shared_ptr<u256> m_pushedValue;
};
using AssemblyItems = std::vector<AssemblyItem>;

126
libevmasm/GasMeter.cpp

@ -20,6 +20,7 @@
*/
#include "GasMeter.h"
#include <libevmasm/KnownState.h>
#include <libevmcore/Params.h>
using namespace std;
@ -41,55 +42,162 @@ GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption co
GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item)
{
switch (_item.type()) {
GasConsumption gas;
switch (_item.type())
{
case Push:
case PushTag:
return runGas(Instruction::PUSH1);
case PushData:
case PushString:
case PushSub:
case PushSubSize:
case PushProgramSize:
gas = runGas(Instruction::PUSH1);
break;
case Tag:
return runGas(Instruction::JUMPDEST);
gas = runGas(Instruction::JUMPDEST);
break;
case Operation:
{
GasConsumption gas = runGas(_item.instruction());
ExpressionClasses& classes = m_state->expressionClasses();
gas = runGas(_item.instruction());
switch (_item.instruction())
{
case Instruction::SSTORE:
// @todo logic can be improved
{
ExpressionClasses::Id slot = m_state->relativeStackElement(0);
ExpressionClasses::Id value = m_state->relativeStackElement(-1);
if (classes.knownZero(value) || (
m_state->storageContent().count(slot) &&
classes.knownNonZero(m_state->storageContent().at(slot))
))
gas += c_sstoreResetGas; //@todo take refunds into account
else
gas += c_sstoreSetGas;
break;
}
case Instruction::SLOAD:
gas += c_sloadGas;
break;
case Instruction::RETURN:
gas += memoryGas(0, -1);
break;
case Instruction::MLOAD:
case Instruction::MSTORE:
gas += memoryGas(classes.find(eth::Instruction::ADD, {
m_state->relativeStackElement(0),
classes.find(AssemblyItem(32))
}));
break;
case Instruction::MSTORE8:
case Instruction::MLOAD:
case Instruction::RETURN:
gas += memoryGas(classes.find(eth::Instruction::ADD, {
m_state->relativeStackElement(0),
classes.find(AssemblyItem(1))
}));
break;
case Instruction::SHA3:
gas = c_sha3Gas;
gas += wordGas(c_sha3WordGas, m_state->relativeStackElement(-1));
gas += memoryGas(0, -1);
break;
case Instruction::CALLDATACOPY:
case Instruction::CODECOPY:
gas += memoryGas(0, -2);
gas += wordGas(c_copyGas, m_state->relativeStackElement(-2));
break;
case Instruction::EXTCODECOPY:
gas += memoryGas(-1, -3);
gas += wordGas(c_copyGas, m_state->relativeStackElement(-3));
break;
case Instruction::LOG0:
case Instruction::LOG1:
case Instruction::LOG2:
case Instruction::LOG3:
case Instruction::LOG4:
{
unsigned n = unsigned(_item.instruction()) - unsigned(Instruction::LOG0);
gas = c_logGas + c_logTopicGas * n;
gas += memoryGas(0, -1);
if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1)))
gas += c_logDataGas * (*value);
else
gas = GasConsumption::infinite();
break;
}
case Instruction::CALL:
case Instruction::CALLCODE:
gas = c_callGas;
if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0)))
gas += (*value);
else
gas = GasConsumption::infinite();
if (_item.instruction() != Instruction::CALLCODE)
gas += c_callNewAccountGas; // We very rarely know whether the address exists.
if (!classes.knownZero(m_state->relativeStackElement(-2)))
gas += c_callValueTransferGas;
gas += memoryGas(-3, -4);
gas += memoryGas(-5, -6);
break;
case Instruction::CREATE:
gas = c_createGas;
gas += memoryGas(-1, -2);
break;
case Instruction::EXP:
// @todo logic can be improved
gas = c_expGas;
if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1)))
gas += c_expByteGas * (32 - (h256(*value).firstBitSet() / 8));
else
gas = GasConsumption::infinite();
break;
default:
break;
}
return gas;
break;
}
default:
gas = GasConsumption::infinite();
break;
}
m_state->feedItem(_item);
return gas;
}
GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionClasses::Id _position)
{
u256 const* value = m_state->expressionClasses().knownConstant(_position);
if (!value)
return GasConsumption::infinite();
return GasConsumption(_multiplier * ((*value + 31) / 32));
}
GasMeter::GasConsumption GasMeter::memoryGas(ExpressionClasses::Id _position)
{
u256 const* value = m_state->expressionClasses().knownConstant(_position);
if (!value)
return GasConsumption::infinite();
if (*value < m_largestMemoryAccess)
return GasConsumption(u256(0));
u256 previous = m_largestMemoryAccess;
m_largestMemoryAccess = *value;
auto memGas = [](u256 const& pos) -> u256
{
u256 size = (pos + 31) / 32;
return c_memoryGas * size + size * size / c_quadCoeffDiv;
};
return memGas(*value) - memGas(previous);
}
GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosSize)
{
ExpressionClasses& classes = m_state->expressionClasses();
if (classes.knownZero(m_state->relativeStackElement(_stackPosSize)))
return GasConsumption(0);
else
return memoryGas(classes.find(eth::Instruction::ADD, {
m_state->relativeStackElement(_stackPosOffset),
m_state->relativeStackElement(_stackPosSize)
}));
}
GasMeter::GasConsumption GasMeter::runGas(Instruction _instruction)

27
libevmasm/GasMeter.h

@ -22,6 +22,7 @@
#pragma once
#include <ostream>
#include <libevmasm/ExpressionClasses.h>
#include <libevmasm/AssemblyItem.h>
namespace dev
@ -29,8 +30,13 @@ namespace dev
namespace eth
{
class KnownState;
/**
* Class that helps computing the maximum gas consumption for instructions.
* Has to be initialized with a certain known state that will be automatically updated for
* each call to estimateMax. These calls have to supply strictly subsequent AssemblyItems.
* A new gas meter has to be constructed (with a new state) for control flow changes.
*/
class GasMeter
{
@ -47,11 +53,28 @@ public:
bool isInfinite;
};
/// Returns an upper bound on the gas consumed by the given instruction.
/// Constructs a new gas meter given the current state.
GasMeter(std::shared_ptr<KnownState> const& _state): m_state(_state) {}
/// @returns an upper bound on the gas consumed by the given instruction and updates
/// the state.
GasConsumption estimateMax(AssemblyItem const& _item);
private:
/// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise.
GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value);
/// @returns the gas needed to access the given memory position.
/// @todo this assumes that memory was never accessed before and thus over-estimates gas usage.
GasConsumption memoryGas(ExpressionClasses::Id _position);
/// @returns the memory gas for accessing the memory at a specific offset for a number of bytes
/// given as values on the stack at the given relative positions.
GasConsumption memoryGas(int _stackPosOffset, int _stackPosSize);
static GasConsumption runGas(Instruction _instruction);
std::shared_ptr<KnownState> m_state;
/// Largest point where memory was accessed since the creation of this object.
u256 m_largestMemoryAccess;
};
inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption const& _consumption)
@ -59,7 +82,7 @@ inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption con
if (_consumption.isInfinite)
return _str << "inf";
else
return _str << _consumption.value;
return _str << std::dec << _consumption.value;
}

9
libevmasm/KnownState.cpp

@ -92,6 +92,10 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
else if (_item.type() != Operation)
{
assertThrow(_item.deposit() == 1, InvalidDeposit, "");
if (_item.pushedValue())
// only available after assembly stage, should not be used for optimisation
setStackElement(++m_stackHeight, m_expressionClasses->find(*_item.pushedValue()));
else
setStackElement(++m_stackHeight, m_expressionClasses->find(_item, {}, _copyItem));
}
else
@ -233,6 +237,11 @@ ExpressionClasses::Id KnownState::stackElement(int _stackHeight, SourceLocation
m_expressionClasses->find(AssemblyItem(UndefinedItem, _stackHeight, _location));
}
KnownState::Id KnownState::relativeStackElement(int _stackOffset, SourceLocation const& _location)
{
return stackElement(m_stackHeight + _stackOffset, _location);
}
void KnownState::clearTagUnions()
{
for (auto it = m_stackElements.begin(); it != m_stackElements.end();)

4
libevmasm/KnownState.h

@ -111,6 +111,8 @@ public:
/// Retrieves the current equivalence class fo the given stack element (or generates a new
/// one if it does not exist yet).
Id stackElement(int _stackHeight, SourceLocation const& _location);
/// @returns the stackElement relative to the current stack height.
Id relativeStackElement(int _stackOffset, SourceLocation const& _location = SourceLocation());
/// @returns its set of tags if the given expression class is a known tag union; returns a set
/// containing the tag if it is a PushTag expression and the empty set otherwise.
@ -123,6 +125,8 @@ public:
std::map<int, Id> const& stackElements() const { return m_stackElements; }
ExpressionClasses& expressionClasses() const { return *m_expressionClasses; }
std::map<Id, Id> const& storageContent() const { return m_storageContent; }
private:
/// Assigns a new equivalence class to the next sequence number of the given stack element.
void setStackElement(int _stackHeight, Id _class);

20
libsolidity/ASTVisitor.h

@ -220,6 +220,26 @@ protected:
virtual void endVisitNode(ASTNode const&) { }
};
/**
* Utility class that accepts std::functions and calls them for visitNode and endVisitNode.
*/
class SimpleASTVisitor: public ASTConstVisitor
{
public:
SimpleASTVisitor(
std::function<bool(ASTNode const&)> _onVisit,
std::function<void(ASTNode const&)> _onEndVisit
): m_onVisit(_onVisit), m_onEndVisit(_onEndVisit) {}
protected:
virtual bool visitNode(ASTNode const& _n) override { return m_onVisit ? m_onVisit(_n) : true; }
virtual void endVisitNode(ASTNode const& _n) override { m_onEndVisit(_n); }
private:
std::function<bool(ASTNode const&)> m_onVisit;
std::function<void(ASTNode const&)> m_onEndVisit;
};
/**
* 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.

38
libsolidity/CompilerStack.cpp

@ -55,12 +55,29 @@ const map<string, string> StandardSources = map<string, string>{
};
CompilerStack::CompilerStack(bool _addStandardSources):
m_addStandardSources(_addStandardSources), m_parseSuccessful(false)
m_parseSuccessful(false)
{
if (m_addStandardSources)
if (_addStandardSources)
addSources(StandardSources, true); // add them as libraries
}
void CompilerStack::reset(bool _keepSources, bool _addStandardSources)
{
m_parseSuccessful = false;
if (_keepSources)
for (auto sourcePair: m_sources)
sourcePair.second.reset();
else
{
m_sources.clear();
if (_addStandardSources)
addSources(StandardSources, true);
}
m_globalContext.reset();
m_sourceOrder.clear();
m_contracts.clear();
}
bool CompilerStack::addSource(string const& _name, string const& _content, bool _isLibrary)
{
bool existed = m_sources.count(_name) != 0;
@ -269,23 +286,6 @@ tuple<int, int, int, int> CompilerStack::positionFromSourceLocation(SourceLocati
return make_tuple(++startLine, ++startColumn, ++endLine, ++endColumn);
}
void CompilerStack::reset(bool _keepSources)
{
m_parseSuccessful = false;
if (_keepSources)
for (auto sourcePair: m_sources)
sourcePair.second.reset();
else
{
m_sources.clear();
if (m_addStandardSources)
addSources(StandardSources, true);
}
m_globalContext.reset();
m_sourceOrder.clear();
m_contracts.clear();
}
void CompilerStack::resolveImports()
{
// topological sorting (depth first search) of the import graph, cutting potential cycles

5
libsolidity/CompilerStack.h

@ -72,6 +72,9 @@ public:
/// Creates a new compiler stack. Adds standard sources if @a _addStandardSources.
explicit CompilerStack(bool _addStandardSources = true);
/// Resets the compiler to a state where the sources are not parsed or even removed.
void reset(bool _keepSources = false, bool _addStandardSources = true);
/// Adds a source object (e.g. file) to the parser. After this, parse has to be called again.
/// @returns true if a source object by the name already existed and was replaced.
void addSources(StringMap const& _nameContents, bool _isLibrary = false) { for (auto const& i: _nameContents) addSource(i.first, i.second, _isLibrary); }
@ -165,13 +168,11 @@ private:
Contract();
};
void reset(bool _keepSources = false);
void resolveImports();
Contract const& getContract(std::string const& _contractName = "") const;
Source const& getSource(std::string const& _sourceName = "") const;
bool m_addStandardSources; ///< If true, standard sources are added.
bool m_parseSuccessful;
std::map<std::string const, Source> m_sources;
std::shared_ptr<GlobalContext> m_globalContext;

39
libsolidity/StructuralGasEstimator.cpp

@ -23,6 +23,9 @@
#include "StructuralGasEstimator.h"
#include <map>
#include <functional>
#include <memory>
#include <libevmasm/ControlFlowGraph.h>
#include <libevmasm/KnownState.h>
#include <libsolidity/AST.h>
#include <libsolidity/ASTVisitor.h>
@ -38,14 +41,23 @@ StructuralGasEstimator::ASTGasConsumptionSelfAccumulated StructuralGasEstimator:
{
solAssert(std::count(_ast.begin(), _ast.end(), nullptr) == 0, "");
map<SourceLocation, GasMeter::GasConsumption> particularCosts;
GasMeter meter;
for (auto const& item: _items)
particularCosts[item.getLocation()] += meter.estimateMax(item);
ControlFlowGraph cfg(_items);
for (BasicBlock const& block: cfg.optimisedBlocks())
{
assertThrow(!!block.startState, OptimizerException, "");
GasMeter meter(block.startState->copy());
auto const end = _items.begin() + block.end;
for (auto iter = _items.begin() + block.begin; iter != end; ++iter)
particularCosts[iter->getLocation()] += meter.estimateMax(*iter);
}
set<ASTNode const*> finestNodes = finestNodesAtLocation(_ast);
ASTGasConsumptionSelfAccumulated gasCosts;
auto onNode = [&](ASTNode const& _node)
{
if (!finestNodes.count(&_node))
return true;
gasCosts[&_node][0] = gasCosts[&_node][1] = particularCosts[_node.getLocation()];
return true;
};
@ -108,3 +120,24 @@ map<ASTNode const*, GasMeter::GasConsumption> StructuralGasEstimator::breakToSta
// gasCosts should only contain non-overlapping locations
return gasCosts;
}
set<ASTNode const*> StructuralGasEstimator::finestNodesAtLocation(
vector<ASTNode const*> const& _roots
)
{
map<SourceLocation, ASTNode const*> locations;
set<ASTNode const*> nodes;
SimpleASTVisitor visitor(function<bool(ASTNode const&)>(), [&](ASTNode const& _n)
{
if (!locations.count(_n.getLocation()))
{
locations[_n.getLocation()] = &_n;
nodes.insert(&_n);
}
});
for (ASTNode const* root: _roots)
root->accept(visitor);
return nodes;
}

4
libsolidity/StructuralGasEstimator.h

@ -56,6 +56,10 @@ public:
ASTGasConsumptionSelfAccumulated const& _gasCosts,
std::vector<ASTNode const*> const& _roots
);
private:
/// @returns the set of AST nodes which are the finest nodes at their location.
std::set<ASTNode const*> finestNodesAtLocation(std::vector<ASTNode const*> const& _roots);
};
}

60
test/GasMeter.cpp → test/libsolidity/GasMeter.cpp

@ -21,6 +21,8 @@
*/
#include <test/libsolidity/solidityExecutionFramework.h>
#include <libevmasm/GasMeter.h>
#include <libevmasm/KnownState.h>
#include <libsolidity/AST.h>
#include <libsolidity/StructuralGasEstimator.h>
#include <libsolidity/SourceReferenceFormatter.h>
@ -55,8 +57,21 @@ public:
);
}
void testCreationTimeGas(string const& _sourceCode, string const& _contractName = "")
{
compileAndRun(_sourceCode);
auto state = make_shared<KnownState>();
GasMeter meter(state);
GasMeter::GasConsumption gas;
for (AssemblyItem const& item: *m_compiler.getAssemblyItems(_contractName))
gas += meter.estimateMax(item);
u256 bytecodeSize(m_compiler.getRuntimeBytecode(_contractName).size());
gas += bytecodeSize * c_createDataGas;
BOOST_REQUIRE(!gas.isInfinite);
BOOST_CHECK(gas.value == m_gasUsed);
}
protected:
dev::solidity::CompilerStack m_compiler;
map<ASTNode const*, eth::GasMeter::GasConsumption> m_gasCosts;
};
@ -91,6 +106,49 @@ BOOST_AUTO_TEST_CASE(non_overlapping_filtered_costs)
}
}
BOOST_AUTO_TEST_CASE(simple_contract)
{
// Tests a simple "deploy contract" code without constructor. The actual contract is not relevant.
char const* sourceCode = R"(
contract test {
bytes32 public shaValue;
function f(uint a) {
shaValue = sha3(a);
}
}
)";
testCreationTimeGas(sourceCode);
}
BOOST_AUTO_TEST_CASE(store_sha3)
{
char const* sourceCode = R"(
contract test {
bytes32 public shaValue;
function test(uint a) {
shaValue = sha3(a);
}
}
)";
testCreationTimeGas(sourceCode);
}
BOOST_AUTO_TEST_CASE(updating_store)
{
char const* sourceCode = R"(
contract test {
uint data;
uint data2;
function test() {
data = 1;
data = 2;
data2 = 0;
}
}
)";
testCreationTimeGas(sourceCode);
}
BOOST_AUTO_TEST_SUITE_END()
}

11
test/libsolidity/solidityExecutionFramework.h

@ -44,11 +44,11 @@ public:
bytes const& compileAndRun(std::string const& _sourceCode, u256 const& _value = 0, std::string const& _contractName = "")
{
dev::solidity::CompilerStack compiler(m_addStandardSources);
compiler.addSource("", _sourceCode);
ETH_TEST_REQUIRE_NO_THROW(compiler.compile(m_optimize), "Compiling contract failed");
m_compiler.reset(false, m_addStandardSources);
m_compiler.addSource("", _sourceCode);
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(m_optimize), "Compiling contract failed");
bytes code = compiler.getBytecode(_contractName);
bytes code = m_compiler.getBytecode(_contractName);
sendMessage(code, true, _value);
BOOST_REQUIRE(!m_output.empty());
return m_output;
@ -160,12 +160,14 @@ protected:
BOOST_REQUIRE(executive.go());
m_state.noteSending(m_sender);
executive.finalize();
m_gasUsed = executive.gasUsed();
m_output = executive.out().toVector();
m_logs = executive.logs();
}
bool m_optimize = false;
bool m_addStandardSources = false;
dev::solidity::CompilerStack m_compiler;
Address m_sender;
Address m_contractAddress;
eth::State m_state;
@ -173,6 +175,7 @@ protected:
u256 const m_gas = 100000000;
bytes m_output;
eth::LogEntries m_logs;
u256 m_gasUsed;
};
}

Loading…
Cancel
Save