solidity/libevmasm/ConstantOptimiser.cpp

318 lines
8.7 KiB
C++
Raw Normal View History

2015-06-01 10:32:59 +00:00
/*
This file is part of solidity.
2015-06-01 10:32:59 +00:00
solidity is free software: you can redistribute it and/or modify
2015-06-01 10:32:59 +00:00
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,
2015-06-01 10:32:59 +00:00
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/>.
2015-06-01 10:32:59 +00:00
*/
/** @file ConstantOptimiser.cpp
* @author Christian <c@ethdev.com>
* @date 2015
*/
2016-03-22 12:05:27 +00:00
#include <libevmasm/ConstantOptimiser.h>
#include <libevmasm/Assembly.h>
#include <libevmasm/GasMeter.h>
2015-06-01 10:32:59 +00:00
using namespace std;
using namespace dev;
using namespace dev::eth;
unsigned ConstantOptimisationMethod::optimiseConstants(
bool _isCreation,
size_t _runs,
langutil::EVMVersion _evmVersion,
Assembly& _assembly
2015-06-01 10:32:59 +00:00
)
{
// TODO: design the optimiser in a way this is not needed
AssemblyItems& _items = _assembly.items();
2015-06-01 10:32:59 +00:00
unsigned optimisations = 0;
map<AssemblyItem, size_t> pushes;
for (AssemblyItem const& item: _items)
if (item.type() == Push)
pushes[item]++;
2017-01-06 16:05:27 +00:00
map<u256, AssemblyItems> pendingReplacements;
2015-06-01 10:32:59 +00:00
for (auto it: pushes)
{
AssemblyItem const& item = it.first;
if (item.data() < 0x100)
continue;
Params params;
params.multiplicity = it.second;
params.isCreation = _isCreation;
params.runs = _runs;
params.evmVersion = _evmVersion;
2015-06-01 10:32:59 +00:00
LiteralMethod lit(params, item.data());
bigint literalGas = lit.gasNeeded();
CodeCopyMethod copy(params, item.data());
bigint copyGas = copy.gasNeeded();
ComputeMethod compute(params, item.data());
bigint computeGas = compute.gasNeeded();
2017-01-06 16:05:27 +00:00
AssemblyItems replacement;
2015-06-01 10:32:59 +00:00
if (copyGas < literalGas && copyGas < computeGas)
{
2017-01-06 16:05:27 +00:00
replacement = copy.execute(_assembly);
2015-06-01 10:32:59 +00:00
optimisations++;
}
2017-01-12 16:52:23 +00:00
else if (computeGas < literalGas && computeGas <= copyGas)
2015-06-01 10:32:59 +00:00
{
2017-01-06 16:05:27 +00:00
replacement = compute.execute(_assembly);
2015-06-01 10:32:59 +00:00
optimisations++;
}
2017-01-06 16:05:27 +00:00
if (!replacement.empty())
pendingReplacements[item.data()] = replacement;
2015-06-01 10:32:59 +00:00
}
2017-01-06 16:05:27 +00:00
if (!pendingReplacements.empty())
replaceConstants(_items, pendingReplacements);
2015-06-01 10:32:59 +00:00
return optimisations;
}
bigint ConstantOptimisationMethod::simpleRunGas(AssemblyItems const& _items)
{
bigint gas = 0;
for (AssemblyItem const& item: _items)
if (item.type() == Push)
gas += GasMeter::runGas(Instruction::PUSH1);
2015-06-01 10:32:59 +00:00
else if (item.type() == Operation)
{
if (item.instruction() == Instruction::EXP)
gas += GasCosts::expGas;
else
gas += GasMeter::runGas(item.instruction());
}
2015-06-01 10:32:59 +00:00
return gas;
}
bigint ConstantOptimisationMethod::dataGas(bytes const& _data) const
{
assertThrow(_data.size() > 0, OptimizerException, "Empty bytecode generated.");
return bigint(GasMeter::dataGas(_data, m_params.isCreation));
2015-06-01 10:32:59 +00:00
}
size_t ConstantOptimisationMethod::bytesRequired(AssemblyItems const& _items)
{
2017-06-14 17:40:53 +00:00
return eth::bytesRequired(_items, 3); // assume 3 byte addresses
2015-06-01 10:32:59 +00:00
}
void ConstantOptimisationMethod::replaceConstants(
AssemblyItems& _items,
2017-01-06 16:05:27 +00:00
map<u256, AssemblyItems> const& _replacements
)
2015-06-01 10:32:59 +00:00
{
2017-01-06 16:05:27 +00:00
AssemblyItems replaced;
for (AssemblyItem const& item: _items)
2015-06-01 10:32:59 +00:00
{
2017-01-06 16:05:27 +00:00
if (item.type() == Push)
{
auto it = _replacements.find(item.data());
if (it != _replacements.end())
{
replaced += it->second;
continue;
}
}
replaced.push_back(item);
2015-06-01 10:32:59 +00:00
}
2017-01-06 16:05:27 +00:00
_items = std::move(replaced);
2015-06-01 10:32:59 +00:00
}
bigint LiteralMethod::gasNeeded() const
2015-06-01 10:32:59 +00:00
{
return combineGas(
2015-06-05 15:34:20 +00:00
simpleRunGas({Instruction::PUSH1}),
2015-06-01 10:32:59 +00:00
// PUSHX plus data
(m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas) + dataGas(toCompactBigEndian(m_value, 1)),
2015-06-01 10:32:59 +00:00
0
);
}
bigint CodeCopyMethod::gasNeeded() const
2015-06-01 10:32:59 +00:00
{
return combineGas(
// Run gas: we ignore memory increase costs
2017-01-06 16:05:27 +00:00
simpleRunGas(copyRoutine()) + GasCosts::copyGas,
2015-06-01 10:32:59 +00:00
// Data gas for copy routines: Some bytes are zero, but we ignore them.
2017-01-06 16:05:27 +00:00
bytesRequired(copyRoutine()) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas),
2015-06-01 10:32:59 +00:00
// Data gas for data itself
dataGas(toBigEndian(m_value))
);
}
AssemblyItems CodeCopyMethod::execute(Assembly& _assembly) const
2015-06-01 10:32:59 +00:00
{
bytes data = toBigEndian(m_value);
2017-01-06 16:05:27 +00:00
AssemblyItems actualCopyRoutine = copyRoutine();
actualCopyRoutine[4] = _assembly.newData(data);
return actualCopyRoutine;
}
AssemblyItems const& CodeCopyMethod::copyRoutine()
2017-01-06 16:05:27 +00:00
{
AssemblyItems static copyRoutine{
u256(0),
Instruction::DUP1,
Instruction::MLOAD, // back up memory
u256(32),
AssemblyItem(PushData, u256(1) << 16), // has to be replaced
Instruction::DUP4,
Instruction::CODECOPY,
Instruction::DUP2,
Instruction::MLOAD,
Instruction::SWAP2,
Instruction::MSTORE
};
return copyRoutine;
2015-06-01 10:32:59 +00:00
}
AssemblyItems ComputeMethod::findRepresentation(u256 const& _value)
{
if (_value < 0x10000)
// Very small value, not worth computing
return AssemblyItems{_value};
else if (dev::bytesRequired(~_value) < dev::bytesRequired(_value))
// Negated is shorter to represent
return findRepresentation(~_value) + AssemblyItems{Instruction::NOT};
else
{
// Decompose value into a * 2**k + b where abs(b) << 2**k
// Is not always better, try literal and decomposition method.
AssemblyItems routine{u256(_value)};
bigint bestGas = gasNeeded(routine);
for (unsigned bits = 255; bits > 8 && m_maxSteps > 0; --bits)
2015-06-01 10:32:59 +00:00
{
unsigned gapDetector = unsigned((_value >> (bits - 8)) & 0x1ff);
2015-06-01 10:32:59 +00:00
if (gapDetector != 0xff && gapDetector != 0x100)
continue;
u256 powerOfTwo = u256(1) << bits;
u256 upperPart = _value >> bits;
bigint lowerPart = _value & (powerOfTwo - 1);
2017-05-02 14:56:12 +00:00
if ((powerOfTwo - lowerPart) < lowerPart)
2017-04-03 12:40:17 +00:00
{
2015-06-01 10:32:59 +00:00
lowerPart = lowerPart - powerOfTwo; // make it negative
2017-04-03 12:40:17 +00:00
upperPart++;
}
if (upperPart == 0)
continue;
2015-06-01 10:32:59 +00:00
if (abs(lowerPart) >= (powerOfTwo >> 8))
continue;
AssemblyItems newRoutine;
if (lowerPart != 0)
newRoutine += findRepresentation(u256(abs(lowerPart)));
if (m_params.evmVersion.hasBitwiseShifting())
{
newRoutine += findRepresentation(upperPart);
newRoutine += AssemblyItems{u256(bits), Instruction::SHL};
}
else
{
newRoutine += AssemblyItems{u256(bits), u256(2), Instruction::EXP};
if (upperPart != 1)
newRoutine += findRepresentation(upperPart) + AssemblyItems{Instruction::MUL};
}
2015-06-01 10:32:59 +00:00
if (lowerPart > 0)
newRoutine += AssemblyItems{Instruction::ADD};
else if (lowerPart < 0)
2015-06-05 15:34:20 +00:00
newRoutine.push_back(Instruction::SUB);
2015-06-01 10:32:59 +00:00
if (m_maxSteps > 0)
m_maxSteps--;
2015-06-01 10:32:59 +00:00
bigint newGas = gasNeeded(newRoutine);
if (newGas < bestGas)
{
bestGas = move(newGas);
routine = move(newRoutine);
}
}
return routine;
}
}
bool ComputeMethod::checkRepresentation(u256 const& _value, AssemblyItems const& _routine) const
{
// This is a tiny EVM that can only evaluate some instructions.
vector<u256> stack;
for (AssemblyItem const& item: _routine)
{
switch (item.type())
{
case Operation:
{
if (stack.size() < size_t(item.arguments()))
return false;
u256* sp = &stack.back();
switch (item.instruction())
{
case Instruction::MUL:
sp[-1] = sp[0] * sp[-1];
break;
case Instruction::EXP:
if (sp[-1] > 0xff)
return false;
sp[-1] = boost::multiprecision::pow(sp[0], unsigned(sp[-1]));
break;
case Instruction::ADD:
sp[-1] = sp[0] + sp[-1];
break;
case Instruction::SUB:
sp[-1] = sp[0] - sp[-1];
break;
case Instruction::NOT:
sp[0] = ~sp[0];
break;
case Instruction::SHL:
assertThrow(
m_params.evmVersion.hasBitwiseShifting(),
OptimizerException,
"Shift generated for invalid EVM version."
);
assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated.");
sp[-1] = u256(bigint(sp[-1]) << unsigned(sp[0]));
break;
case Instruction::SHR:
assertThrow(
m_params.evmVersion.hasBitwiseShifting(),
OptimizerException,
"Shift generated for invalid EVM version."
);
assertThrow(sp[0] <= u256(255), OptimizerException, "Invalid shift generated.");
sp[-1] = sp[-1] >> unsigned(sp[0]);
break;
default:
return false;
}
stack.resize(stack.size() + item.deposit());
break;
}
case Push:
stack.push_back(item.data());
break;
default:
return false;
}
}
return stack.size() == 1 && stack.front() == _value;
}
bigint ComputeMethod::gasNeeded(AssemblyItems const& _routine) const
2015-06-01 10:32:59 +00:00
{
2015-06-05 15:34:20 +00:00
size_t numExps = count(_routine.begin(), _routine.end(), Instruction::EXP);
2015-06-01 10:32:59 +00:00
return combineGas(
simpleRunGas(_routine) + numExps * (GasCosts::expGas + GasCosts::expByteGas(m_params.evmVersion)),
2015-06-01 10:32:59 +00:00
// Data gas for routine: Some bytes are zero, but we ignore them.
bytesRequired(_routine) * (m_params.isCreation ? GasCosts::txDataNonZeroGas : GasCosts::createDataGas),
2015-06-01 10:32:59 +00:00
0
);
}