mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #4936 from ethereum/binSelect
Binary search for dispatch.
This commit is contained in:
commit
6a9e8a6fe3
@ -5,6 +5,7 @@ Language Features:
|
||||
|
||||
Compiler Features:
|
||||
* Inline Assembly: Improve error messages around invalid function argument count.
|
||||
* Code Generator: Use binary search for dispatch function if more efficient. The size/speed tradeoff can be tuned using ``--optimize-runs``.
|
||||
|
||||
|
||||
Bugfixes:
|
||||
|
@ -34,13 +34,13 @@ void Compiler::compileContract(
|
||||
bytes const& _metadata
|
||||
)
|
||||
{
|
||||
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);
|
||||
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns);
|
||||
runtimeCompiler.compileContract(_contract, _contracts);
|
||||
m_runtimeContext.appendAuxiliaryData(_metadata);
|
||||
|
||||
// This might modify m_runtimeContext because it can access runtime functions at
|
||||
// creation time.
|
||||
ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize);
|
||||
ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1);
|
||||
m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts);
|
||||
|
||||
m_context.optimise(m_optimize, m_optimizeRuns);
|
||||
|
@ -268,6 +268,70 @@ void ContractCompiler::appendDelegatecallCheck()
|
||||
// "We have not been called via DELEGATECALL".
|
||||
}
|
||||
|
||||
void ContractCompiler::appendInternalSelector(
|
||||
map<FixedHash<4>, eth::AssemblyItem const> const& _entryPoints,
|
||||
vector<FixedHash<4>> const& _ids,
|
||||
eth::AssemblyItem const& _notFoundTag,
|
||||
size_t _runs
|
||||
)
|
||||
{
|
||||
// Code for selecting from n functions without split:
|
||||
// n times: dup1, push4 <id_i>, eq, push2/3 <tag_i>, jumpi
|
||||
// push2/3 <notfound> jump
|
||||
// (called SELECT[n])
|
||||
// Code for selecting from n functions with split:
|
||||
// dup1, push4 <pivot>, gt, push2/3<tag_less>, jumpi
|
||||
// SELECT[n/2]
|
||||
// tag_less:
|
||||
// SELECT[n/2]
|
||||
//
|
||||
// This means each split adds 16-18 bytes of additional code (note the additional jump out!)
|
||||
// The average execution cost if we do not split at all are:
|
||||
// (3 + 3 + 3 + 3 + 10) * n/2 = 24 * n/2 = 12 * n
|
||||
// If we split once:
|
||||
// (3 + 3 + 3 + 3 + 10) + 24 * n/4 = 24 * (n/4 + 1) = 6 * n + 24;
|
||||
//
|
||||
// We should split if
|
||||
// _runs * 12 * n > _runs * (6 * n + 24) + 17 * createDataGas
|
||||
// <=> _runs * 6 * (n - 4) > 17 * createDataGas
|
||||
//
|
||||
// Which also means that the execution itself is not profitable
|
||||
// unless we have at least 5 functions.
|
||||
|
||||
// Start with some comparisons to avoid overflow, then do the actual comparison.
|
||||
bool split = false;
|
||||
if (_ids.size() <= 4)
|
||||
split = false;
|
||||
else if (_runs > (17 * eth::GasCosts::createDataGas) / 6)
|
||||
split = true;
|
||||
else
|
||||
split = (_runs * 6 * (_ids.size() - 4) > 17 * eth::GasCosts::createDataGas);
|
||||
|
||||
if (split)
|
||||
{
|
||||
size_t pivotIndex = _ids.size() / 2;
|
||||
FixedHash<4> pivot{_ids.at(pivotIndex)};
|
||||
m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(pivot)) << Instruction::GT;
|
||||
eth::AssemblyItem lessTag{m_context.appendConditionalJump()};
|
||||
// Here, we have funid >= pivot
|
||||
vector<FixedHash<4>> larger{_ids.begin() + pivotIndex, _ids.end()};
|
||||
appendInternalSelector(_entryPoints, larger, _notFoundTag, _runs);
|
||||
m_context << lessTag;
|
||||
// Here, we have funid < pivot
|
||||
vector<FixedHash<4>> smaller{_ids.begin(), _ids.begin() + pivotIndex};
|
||||
appendInternalSelector(_entryPoints, smaller, _notFoundTag, _runs);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const& id: _ids)
|
||||
{
|
||||
m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(id)) << Instruction::EQ;
|
||||
m_context.appendConditionalJumpTo(_entryPoints.at(id));
|
||||
}
|
||||
m_context.appendJumpTo(_notFoundTag);
|
||||
}
|
||||
}
|
||||
|
||||
void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract)
|
||||
{
|
||||
map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions();
|
||||
@ -290,13 +354,14 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
|
||||
CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true);
|
||||
|
||||
// stack now is: <can-call-non-view-functions>? <funhash>
|
||||
vector<FixedHash<4>> sortedIDs;
|
||||
for (auto const& it: interfaceFunctions)
|
||||
{
|
||||
callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag()));
|
||||
m_context << dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << Instruction::EQ;
|
||||
m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first));
|
||||
sortedIDs.emplace_back(it.first);
|
||||
}
|
||||
m_context.appendJumpTo(notFound);
|
||||
std::sort(sortedIDs.begin(), sortedIDs.end());
|
||||
appendInternalSelector(callDataUnpackerEntryPoints, sortedIDs, notFound, m_optimise_runs);
|
||||
|
||||
m_context << notFound;
|
||||
if (fallback)
|
||||
|
@ -38,8 +38,9 @@ namespace solidity {
|
||||
class ContractCompiler: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise):
|
||||
explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise, size_t _optimise_runs = 200):
|
||||
m_optimise(_optimise),
|
||||
m_optimise_runs(_optimise_runs),
|
||||
m_runtimeCompiler(_runtimeCompiler),
|
||||
m_context(_context)
|
||||
{
|
||||
@ -81,6 +82,14 @@ private:
|
||||
/// This is done by inserting a specific push constant as the first instruction
|
||||
/// whose data will be modified in memory at deploy time.
|
||||
void appendDelegatecallCheck();
|
||||
/// Appends the function selector. Is called recursively to create a binary search tree.
|
||||
/// @a _runs the number of intended executions of the contract to tune the split point.
|
||||
void appendInternalSelector(
|
||||
std::map<FixedHash<4>, eth::AssemblyItem const> const& _entryPoints,
|
||||
std::vector<FixedHash<4>> const& _ids,
|
||||
eth::AssemblyItem const& _notFoundTag,
|
||||
size_t _runs
|
||||
);
|
||||
void appendFunctionSelector(ContractDefinition const& _contract);
|
||||
void appendCallValueCheck();
|
||||
void appendReturnValuePacker(TypePointers const& _typeParameters, bool _isLibrary);
|
||||
@ -122,6 +131,7 @@ private:
|
||||
void storeStackHeight(ASTNode const* _node);
|
||||
|
||||
bool const m_optimise;
|
||||
size_t const m_optimise_runs = 200;
|
||||
/// Pointer to the runtime compiler in case this is a creation compiler.
|
||||
ContractCompiler* m_runtimeCompiler = nullptr;
|
||||
CompilerContext& m_context;
|
||||
|
43
test/cmdlineTests/gas_test_dispatch.sol
Normal file
43
test/cmdlineTests/gas_test_dispatch.sol
Normal file
@ -0,0 +1,43 @@
|
||||
pragma solidity >=0.0;
|
||||
|
||||
contract Large {
|
||||
uint public a;
|
||||
uint[] public b;
|
||||
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
|
||||
function f2(uint x) public returns (uint) { b[uint8(msg.data[1])] = x; }
|
||||
function f3(uint x) public returns (uint) { b[uint8(msg.data[2])] = x; }
|
||||
function f4(uint x) public returns (uint) { b[uint8(msg.data[3])] = x; }
|
||||
function f5(uint x) public returns (uint) { b[uint8(msg.data[4])] = x; }
|
||||
function f6(uint x) public returns (uint) { b[uint8(msg.data[5])] = x; }
|
||||
function f7(uint x) public returns (uint) { b[uint8(msg.data[6])] = x; }
|
||||
function f8(uint x) public returns (uint) { b[uint8(msg.data[7])] = x; }
|
||||
function f9(uint x) public returns (uint) { b[uint8(msg.data[8])] = x; }
|
||||
function f0(uint x) public pure returns (uint) { require(x > 10); }
|
||||
function g1(uint x) public payable returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
|
||||
function g2(uint x) public payable returns (uint) { b[uint8(msg.data[1])] = x; }
|
||||
function g3(uint x) public payable returns (uint) { b[uint8(msg.data[2])] = x; }
|
||||
function g4(uint x) public payable returns (uint) { b[uint8(msg.data[3])] = x; }
|
||||
function g5(uint x) public payable returns (uint) { b[uint8(msg.data[4])] = x; }
|
||||
function g6(uint x) public payable returns (uint) { b[uint8(msg.data[5])] = x; }
|
||||
function g7(uint x) public payable returns (uint) { b[uint8(msg.data[6])] = x; }
|
||||
function g8(uint x) public payable returns (uint) { b[uint8(msg.data[7])] = x; }
|
||||
function g9(uint x) public payable returns (uint) { b[uint8(msg.data[8])] = x; }
|
||||
function g0(uint x) public payable returns (uint) { require(x > 10); }
|
||||
}
|
||||
contract Medium {
|
||||
uint public a;
|
||||
uint[] public b;
|
||||
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
|
||||
function f2(uint x) public returns (uint) { b[uint8(msg.data[1])] = x; }
|
||||
function f3(uint x) public returns (uint) { b[uint8(msg.data[2])] = x; }
|
||||
function g7(uint x) public payable returns (uint) { b[uint8(msg.data[6])] = x; }
|
||||
function g8(uint x) public payable returns (uint) { b[uint8(msg.data[7])] = x; }
|
||||
function g9(uint x) public payable returns (uint) { b[uint8(msg.data[8])] = x; }
|
||||
function g0(uint x) public payable returns (uint) { require(x > 10); }
|
||||
}
|
||||
contract Small {
|
||||
uint public a;
|
||||
uint[] public b;
|
||||
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
|
||||
function () external payable {}
|
||||
}
|
1
test/cmdlineTests/gas_test_dispatch.sol.args
Normal file
1
test/cmdlineTests/gas_test_dispatch.sol.args
Normal file
@ -0,0 +1 @@
|
||||
--gas
|
53
test/cmdlineTests/gas_test_dispatch.sol.stdout
Normal file
53
test/cmdlineTests/gas_test_dispatch.sol.stdout
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
======= gas_test_dispatch.sol:Large =======
|
||||
Gas estimation:
|
||||
construction:
|
||||
1034 + 998400 = 999434
|
||||
external:
|
||||
a(): 456
|
||||
b(uint256): 857
|
||||
f0(uint256): 438
|
||||
f1(uint256): 40781
|
||||
f2(uint256): 20722
|
||||
f3(uint256): 20810
|
||||
f4(uint256): 20788
|
||||
f5(uint256): 20766
|
||||
f6(uint256): 20789
|
||||
f7(uint256): 20701
|
||||
f8(uint256): 20701
|
||||
f9(uint256): 20723
|
||||
g0(uint256): 324
|
||||
g1(uint256): 40736
|
||||
g2(uint256): 20699
|
||||
g3(uint256): 20787
|
||||
g4(uint256): 20765
|
||||
g5(uint256): 20721
|
||||
g6(uint256): 20744
|
||||
g7(uint256): 20743
|
||||
g8(uint256): 20721
|
||||
g9(uint256): 20678
|
||||
|
||||
======= gas_test_dispatch.sol:Medium =======
|
||||
Gas estimation:
|
||||
construction:
|
||||
411 + 376600 = 377011
|
||||
external:
|
||||
a(): 433
|
||||
b(uint256): 857
|
||||
f1(uint256): 40692
|
||||
f2(uint256): 20722
|
||||
f3(uint256): 20766
|
||||
g0(uint256): 324
|
||||
g7(uint256): 20721
|
||||
g8(uint256): 20699
|
||||
g9(uint256): 20655
|
||||
|
||||
======= gas_test_dispatch.sol:Small =======
|
||||
Gas estimation:
|
||||
construction:
|
||||
153 + 107800 = 107953
|
||||
external:
|
||||
fallback: 123
|
||||
a(): 388
|
||||
b(uint256): 813
|
||||
f1(uint256): 40692
|
43
test/cmdlineTests/gas_test_dispatch_optimize.sol
Normal file
43
test/cmdlineTests/gas_test_dispatch_optimize.sol
Normal file
@ -0,0 +1,43 @@
|
||||
pragma solidity >=0.0;
|
||||
|
||||
contract Large {
|
||||
uint public a;
|
||||
uint[] public b;
|
||||
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
|
||||
function f2(uint x) public returns (uint) { b[uint8(msg.data[1])] = x; }
|
||||
function f3(uint x) public returns (uint) { b[uint8(msg.data[2])] = x; }
|
||||
function f4(uint x) public returns (uint) { b[uint8(msg.data[3])] = x; }
|
||||
function f5(uint x) public returns (uint) { b[uint8(msg.data[4])] = x; }
|
||||
function f6(uint x) public returns (uint) { b[uint8(msg.data[5])] = x; }
|
||||
function f7(uint x) public returns (uint) { b[uint8(msg.data[6])] = x; }
|
||||
function f8(uint x) public returns (uint) { b[uint8(msg.data[7])] = x; }
|
||||
function f9(uint x) public returns (uint) { b[uint8(msg.data[8])] = x; }
|
||||
function f0(uint x) public pure returns (uint) { require(x > 10); }
|
||||
function g1(uint x) public payable returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
|
||||
function g2(uint x) public payable returns (uint) { b[uint8(msg.data[1])] = x; }
|
||||
function g3(uint x) public payable returns (uint) { b[uint8(msg.data[2])] = x; }
|
||||
function g4(uint x) public payable returns (uint) { b[uint8(msg.data[3])] = x; }
|
||||
function g5(uint x) public payable returns (uint) { b[uint8(msg.data[4])] = x; }
|
||||
function g6(uint x) public payable returns (uint) { b[uint8(msg.data[5])] = x; }
|
||||
function g7(uint x) public payable returns (uint) { b[uint8(msg.data[6])] = x; }
|
||||
function g8(uint x) public payable returns (uint) { b[uint8(msg.data[7])] = x; }
|
||||
function g9(uint x) public payable returns (uint) { b[uint8(msg.data[8])] = x; }
|
||||
function g0(uint x) public payable returns (uint) { require(x > 10); }
|
||||
}
|
||||
contract Medium {
|
||||
uint public a;
|
||||
uint[] public b;
|
||||
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
|
||||
function f2(uint x) public returns (uint) { b[uint8(msg.data[1])] = x; }
|
||||
function f3(uint x) public returns (uint) { b[uint8(msg.data[2])] = x; }
|
||||
function g7(uint x) public payable returns (uint) { b[uint8(msg.data[6])] = x; }
|
||||
function g8(uint x) public payable returns (uint) { b[uint8(msg.data[7])] = x; }
|
||||
function g9(uint x) public payable returns (uint) { b[uint8(msg.data[8])] = x; }
|
||||
function g0(uint x) public payable returns (uint) { require(x > 10); }
|
||||
}
|
||||
contract Small {
|
||||
uint public a;
|
||||
uint[] public b;
|
||||
function f1(uint x) public returns (uint) { a = x; b[uint8(msg.data[0])] = x; }
|
||||
function () external payable {}
|
||||
}
|
1
test/cmdlineTests/gas_test_dispatch_optimize.sol.args
Normal file
1
test/cmdlineTests/gas_test_dispatch_optimize.sol.args
Normal file
@ -0,0 +1 @@
|
||||
--optimize --optimize-runs 2 --gas
|
53
test/cmdlineTests/gas_test_dispatch_optimize.sol.stdout
Normal file
53
test/cmdlineTests/gas_test_dispatch_optimize.sol.stdout
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
======= gas_test_dispatch_optimize.sol:Large =======
|
||||
Gas estimation:
|
||||
construction:
|
||||
300 + 262000 = 262300
|
||||
external:
|
||||
a(): 463
|
||||
b(uint256): 1173
|
||||
f0(uint256): 399
|
||||
f1(uint256): 41164
|
||||
f2(uint256): 21224
|
||||
f3(uint256): 21312
|
||||
f4(uint256): 21290
|
||||
f5(uint256): 21268
|
||||
f6(uint256): 21180
|
||||
f7(uint256): 20960
|
||||
f8(uint256): 21092
|
||||
f9(uint256): 21114
|
||||
g0(uint256): 639
|
||||
g1(uint256): 40876
|
||||
g2(uint256): 20958
|
||||
g3(uint256): 21046
|
||||
g4(uint256): 21024
|
||||
g5(uint256): 21112
|
||||
g6(uint256): 20892
|
||||
g7(uint256): 21002
|
||||
g8(uint256): 20980
|
||||
g9(uint256): 20826
|
||||
|
||||
======= gas_test_dispatch_optimize.sol:Medium =======
|
||||
Gas estimation:
|
||||
construction:
|
||||
190 + 143000 = 143190
|
||||
external:
|
||||
a(): 463
|
||||
b(uint256): 931
|
||||
f1(uint256): 40944
|
||||
f2(uint256): 20982
|
||||
f3(uint256): 21026
|
||||
g0(uint256): 397
|
||||
g7(uint256): 20892
|
||||
g8(uint256): 20870
|
||||
g9(uint256): 20826
|
||||
|
||||
======= gas_test_dispatch_optimize.sol:Small =======
|
||||
Gas estimation:
|
||||
construction:
|
||||
117 + 67400 = 67517
|
||||
external:
|
||||
fallback: 183
|
||||
a(): 441
|
||||
b(uint256): 821
|
||||
f1(uint256): 40878
|
Loading…
Reference in New Issue
Block a user