Merge pull request #11107 from ethereum/extendedLowlevelInliner

Inline ordinary jumps to small blocks and jumps to terminating control flow.
This commit is contained in:
chriseth 2021-03-24 18:06:15 +01:00 committed by GitHub
commit 6eac77aea2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 165 additions and 56 deletions

View File

@ -4,6 +4,7 @@ Language Features:
* Possibility to use ``bytes.concat`` with variable number of ``bytes`` and ``bytesNN`` arguments which behaves as a restricted version of `abi.encodePacked` with a more descriptive name. * Possibility to use ``bytes.concat`` with variable number of ``bytes`` and ``bytesNN`` arguments which behaves as a restricted version of `abi.encodePacked` with a more descriptive name.
Compiler Features: Compiler Features:
* Low-Level Inliner: Inline ordinary jumps to small blocks and jumps to small blocks that terminate.
Bugfixes: Bugfixes:

View File

@ -82,9 +82,7 @@ bool Inliner::isInlineCandidate(size_t _tag, ranges::span<AssemblyItem const> _i
{ {
assertThrow(_items.size() > 0, OptimizerException, ""); assertThrow(_items.size() > 0, OptimizerException, "");
// Only consider blocks that end in a JUMP for now. This can e.g. be extended to include transaction terminating if (_items.back() != Instruction::JUMP && !SemanticInformation::terminatesControlFlow(_items.back()))
// instructions as well in the future.
if (_items.back() != Instruction::JUMP)
return false; return false;
// Never inline tags that reference themselves. // Never inline tags that reference themselves.
@ -196,19 +194,38 @@ bool Inliner::shouldInlineFullFunctionBody(size_t _tag, ranges::span<AssemblyIte
return false; return false;
} }
optional<AssemblyItem> Inliner::shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const
optional<AssemblyItem::JumpType> Inliner::shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const
{ {
AssemblyItem exitJump = _block.items.back(); assertThrow(_jump == Instruction::JUMP, OptimizerException, "");
assertThrow(_jump == Instruction::JUMP && exitJump == Instruction::JUMP, OptimizerException, ""); AssemblyItem blockExit = _block.items.back();
if ( if (
_jump.getJumpType() == AssemblyItem::JumpType::IntoFunction && _jump.getJumpType() == AssemblyItem::JumpType::IntoFunction &&
exitJump.getJumpType() == AssemblyItem::JumpType::OutOfFunction blockExit == Instruction::JUMP &&
blockExit.getJumpType() == AssemblyItem::JumpType::OutOfFunction &&
shouldInlineFullFunctionBody(_tag, _block.items, _block.pushTagCount)
) )
return {
shouldInlineFullFunctionBody(_tag, _block.items, _block.pushTagCount) ? blockExit.setJumpType(AssemblyItem::JumpType::Ordinary);
make_optional(AssemblyItem::JumpType::Ordinary) : nullopt; return blockExit;
}
// Inline small blocks, if the jump to it is ordinary or the blockExit is a terminating instruction.
if (
_jump.getJumpType() == AssemblyItem::JumpType::Ordinary ||
SemanticInformation::terminatesControlFlow(blockExit)
)
{
static AssemblyItems const jumpPattern = {
AssemblyItem{PushTag},
AssemblyItem{Instruction::JUMP},
};
if (
GasMeter::dataGas(codeSize(_block.items), m_isCreation, m_evmVersion) <=
GasMeter::dataGas(codeSize(jumpPattern), m_isCreation, m_evmVersion)
)
return blockExit;
}
return nullopt; return nullopt;
} }
@ -232,10 +249,10 @@ void Inliner::optimise()
{ {
if (optional<size_t> tag = getLocalTag(item)) if (optional<size_t> tag = getLocalTag(item))
if (auto* inlinableBlock = util::valueOrNullptr(inlinableBlocks, *tag)) if (auto* inlinableBlock = util::valueOrNullptr(inlinableBlocks, *tag))
if (auto exitJumpType = shouldInline(*tag, nextItem, *inlinableBlock)) if (auto exitItem = shouldInline(*tag, nextItem, *inlinableBlock))
{ {
newItems += inlinableBlock->items; newItems += inlinableBlock->items | ranges::views::drop_last(1);
newItems.back().setJumpType(*exitJumpType); newItems.emplace_back(move(*exitItem));
// We are removing one push tag to the block we inline. // We are removing one push tag to the block we inline.
--inlinableBlock->pushTagCount; --inlinableBlock->pushTagCount;

View File

@ -61,8 +61,8 @@ private:
uint64_t pushTagCount = 0; uint64_t pushTagCount = 0;
}; };
/// @returns the exit jump type for the block to be inlined, if a particular jump to it should be inlined, otherwise nullopt. /// @returns the exit item for the block to be inlined, if a particular jump to it should be inlined, otherwise nullopt.
std::optional<AssemblyItem::JumpType> shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const; std::optional<AssemblyItem> shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const;
/// @returns true, if the full function at tag @a _tag with body @a _block that is referenced @a _pushTagCount times /// @returns true, if the full function at tag @a _tag with body @a _block that is referenced @a _pushTagCount times
/// should be inlined, false otherwise. @a _block should start at the first instruction after the function entry tag /// should be inlined, false otherwise. @a _block should start at the first instruction after the function entry tag
/// up to and including the return jump. /// up to and including the return jump.

View File

@ -10,7 +10,7 @@ EVM assembly:
not(sub(shl(0x40, 0x01), 0x01)) not(sub(shl(0x40, 0x01), 0x01))
and and
/* "optimizer_BlockDeDuplicator/input.sol":201:206 fun_x */ /* "optimizer_BlockDeDuplicator/input.sol":201:206 fun_x */
or(tag_0_7, shl(0x20, tag_4)) or(tag_0_7, shl(0x20, tag_2))
sub(shl(0x40, 0x01), 0x01) sub(shl(0x40, 0x01), 0x01)
/* "optimizer_BlockDeDuplicator/input.sol":179:210 function() r = true ? fun_x : f */ /* "optimizer_BlockDeDuplicator/input.sol":179:210 function() r = true ? fun_x : f */
and and
@ -29,8 +29,8 @@ EVM assembly:
tag_5: tag_5:
pop pop
jump(tag_6) jump(tag_6)
/* "optimizer_BlockDeDuplicator/input.sol":77:103 function fun_x() public {} */ /* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */
tag_4: tag_2:
jump // out jump // out
/* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */ /* "optimizer_BlockDeDuplicator/input.sol":60:213 contract C {... */
tag_6: tag_6:
@ -66,12 +66,12 @@ sub_0: assembly {
dup1 dup1
0x2e1fb2bc 0x2e1fb2bc
eq eq
tag_4 tag_3
jumpi jumpi
dup1 dup1
0x4753a67d 0x4753a67d
eq eq
tag_4 tag_3
jumpi jumpi
tag_2: tag_2:
0x00 0x00
@ -80,10 +80,6 @@ sub_0: assembly {
/* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */ /* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */
tag_3: tag_3:
stop stop
/* "optimizer_BlockDeDuplicator/input.sol":108:133 function fun_() public {} */
tag_4:
jump(tag_3)
/* "optimizer_BlockDeDuplicator/input.sol":138:174 function f() public { true ? 1 : 3;} */
tag_7: tag_7:
jump // out jump // out

View File

@ -58,9 +58,9 @@ sub_0: assembly {
revert revert
/* "optimizer_inliner_dynamic_reference/input.sol":160:215 function a() public pure returns (uint) { return f(); } */ /* "optimizer_inliner_dynamic_reference/input.sol":160:215 function a() public pure returns (uint) { return f(); } */
tag_3: tag_3:
tag_6 /* "optimizer_inliner_dynamic_reference/input.sol":361:362 6 */
tag_7 0x06
jump // in /* "optimizer_inliner_dynamic_reference/input.sol":160:215 function a() public pure returns (uint) { return f(); } */
tag_6: tag_6:
mload(0x40) mload(0x40)
/* "#utility.yul":160:185 */ /* "#utility.yul":160:185 */
@ -100,12 +100,6 @@ sub_0: assembly {
sstore sstore
/* "optimizer_inliner_dynamic_reference/input.sol":125:155 function g() public { x = f; } */ /* "optimizer_inliner_dynamic_reference/input.sol":125:155 function g() public { x = f; } */
stop stop
/* "optimizer_inliner_dynamic_reference/input.sol":160:215 function a() public pure returns (uint) { return f(); } */
tag_7:
/* "optimizer_inliner_dynamic_reference/input.sol":194:198 uint */
0x00
/* "optimizer_inliner_dynamic_reference/input.sol":361:362 6 */
0x06
/* "optimizer_inliner_dynamic_reference/input.sol":209:212 f() */ /* "optimizer_inliner_dynamic_reference/input.sol":209:212 f() */
tag_16: tag_16:
/* "optimizer_inliner_dynamic_reference/input.sol":202:212 return f() */ /* "optimizer_inliner_dynamic_reference/input.sol":202:212 return f() */
@ -147,8 +141,8 @@ sub_0: assembly {
tag_17: tag_17:
/* "optimizer_inliner_dynamic_reference/input.sol":361:362 6 */ /* "optimizer_inliner_dynamic_reference/input.sol":361:362 6 */
0x06 0x06
/* "optimizer_inliner_dynamic_reference/input.sol":310:365 function f() internal pure returns (uint) { return 6; } */
swap1 swap1
/* "optimizer_inliner_dynamic_reference/input.sol":310:365 function f() internal pure returns (uint) { return 6; } */
jump // out jump // out
tag_20: tag_20:
mstore(0x00, shl(0xe0, 0x4e487b71)) mstore(0x00, shl(0xe0, 0x4e487b71))

View File

@ -35,8 +35,8 @@ tag_1:
tag_4: tag_4:
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */ /* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */
0x06 0x06
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":304:359 function f() internal pure returns (uint) { return 6; } */
swap1 swap1
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":304:359 function f() internal pure returns (uint) { return 6; } */
jump // out jump // out
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":60:361 contract C {... */ /* "optimizer_inliner_dynamic_reference_constructor/input.sol":60:361 contract C {... */
tag_5: tag_5:
@ -80,9 +80,9 @@ sub_0: assembly {
revert revert
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":154:209 function a() public pure returns (uint) { return f(); } */ /* "optimizer_inliner_dynamic_reference_constructor/input.sol":154:209 function a() public pure returns (uint) { return f(); } */
tag_3: tag_3:
tag_5 /* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */
tag_6 0x06
jump // in /* "optimizer_inliner_dynamic_reference_constructor/input.sol":154:209 function a() public pure returns (uint) { return f(); } */
tag_5: tag_5:
mload(0x40) mload(0x40)
/* "#utility.yul":160:185 */ /* "#utility.yul":160:185 */
@ -105,12 +105,6 @@ sub_0: assembly {
tag_5 tag_5
tag_10 tag_10
jump // in jump // in
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":154:209 function a() public pure returns (uint) { return f(); } */
tag_6:
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":188:192 uint */
0x00
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */
0x06
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":203:206 f() */ /* "optimizer_inliner_dynamic_reference_constructor/input.sol":203:206 f() */
tag_14: tag_14:
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":196:206 return f() */ /* "optimizer_inliner_dynamic_reference_constructor/input.sol":196:206 return f() */
@ -152,8 +146,8 @@ sub_0: assembly {
tag_12: tag_12:
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */ /* "optimizer_inliner_dynamic_reference_constructor/input.sol":355:356 6 */
0x06 0x06
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":304:359 function f() internal pure returns (uint) { return 6; } */
swap1 swap1
/* "optimizer_inliner_dynamic_reference_constructor/input.sol":304:359 function f() internal pure returns (uint) { return 6; } */
jump // out jump // out
tag_17: tag_17:
mstore(0x00, shl(0xe0, 0x4e487b71)) mstore(0x00, shl(0xe0, 0x4e487b71))

View File

@ -1637,6 +1637,113 @@ BOOST_AUTO_TEST_CASE(inliner_cse_break)
); );
} }
BOOST_AUTO_TEST_CASE(inliner_stop)
{
AssemblyItems items{
AssemblyItem(PushTag, 1),
Instruction::JUMP,
AssemblyItem(Tag, 1),
Instruction::STOP
};
AssemblyItems expectation{
Instruction::STOP,
AssemblyItem(Tag, 1),
Instruction::STOP
};
Inliner{items, {}, 200, false, {}}.optimise();
BOOST_CHECK_EQUAL_COLLECTIONS(
items.begin(), items.end(),
expectation.begin(), expectation.end()
);
}
BOOST_AUTO_TEST_CASE(inliner_stop_jumpi)
{
// Because of `jumpi`, will not be inlined.
AssemblyItems items{
u256(1),
AssemblyItem(PushTag, 1),
Instruction::JUMPI,
AssemblyItem(Tag, 1),
Instruction::STOP
};
AssemblyItems expectation = items;
Inliner{items, {}, 200, false, {}}.optimise();
BOOST_CHECK_EQUAL_COLLECTIONS(
items.begin(), items.end(),
expectation.begin(), expectation.end()
);
}
BOOST_AUTO_TEST_CASE(inliner_revert)
{
AssemblyItems items{
AssemblyItem(PushTag, 1),
Instruction::JUMP,
AssemblyItem(Tag, 1),
u256(0),
Instruction::DUP1,
Instruction::REVERT
};
AssemblyItems expectation{
u256(0),
Instruction::DUP1,
Instruction::REVERT,
AssemblyItem(Tag, 1),
u256(0),
Instruction::DUP1,
Instruction::REVERT
};
Inliner{items, {}, 200, false, {}}.optimise();
BOOST_CHECK_EQUAL_COLLECTIONS(
items.begin(), items.end(),
expectation.begin(), expectation.end()
);
}
BOOST_AUTO_TEST_CASE(inliner_revert_increased_datagas)
{
// Inlining this would increase data gas (5 bytes v/s 4 bytes), therefore, skipped.
AssemblyItems items{
AssemblyItem(PushTag, 1),
Instruction::JUMP,
AssemblyItem(Tag, 1),
u256(0),
u256(0),
Instruction::REVERT
};
AssemblyItems expectation = items;
Inliner{items, {}, 200, false, {}}.optimise();
BOOST_CHECK_EQUAL_COLLECTIONS(
items.begin(), items.end(),
expectation.begin(), expectation.end()
);
}
BOOST_AUTO_TEST_CASE(inliner_invalid)
{
AssemblyItems items{
AssemblyItem(PushTag, 1),
Instruction::JUMP,
AssemblyItem(Tag, 1),
Instruction::INVALID
};
AssemblyItems expectation = {
Instruction::INVALID,
AssemblyItem(Tag, 1),
Instruction::INVALID
};
Inliner{items, {}, 200, false, {}}.optimise();
BOOST_CHECK_EQUAL_COLLECTIONS(
items.begin(), items.end(),
expectation.begin(), expectation.end()
);
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
} // end namespaces } // end namespaces

View File

@ -210,7 +210,7 @@ BOOST_AUTO_TEST_CASE(jump_type)
jumpTypes += item.getJumpTypeAsString() + "\n"; jumpTypes += item.getJumpTypeAsString() + "\n";
if (solidity::test::CommonOptions::get().optimize) if (solidity::test::CommonOptions::get().optimize)
BOOST_CHECK_EQUAL(jumpTypes, "[in]\n[out]\n[in]\n[out]\n"); BOOST_CHECK_EQUAL(jumpTypes, "[in]\n[out]\n[out]\n[in]\n[out]\n");
else else
BOOST_CHECK_EQUAL(jumpTypes, "[in]\n[out]\n[in]\n[out]\n"); BOOST_CHECK_EQUAL(jumpTypes, "[in]\n[out]\n[in]\n[out]\n");
} }

View File

@ -21,4 +21,4 @@ contract c {
// test() -> 0 // test() -> 0
// gas irOptimized: 312322 // gas irOptimized: 312322
// gas legacy: 483915 // gas legacy: 483915
// gas legacyOptimized: 478673 // gas legacyOptimized: 478672

View File

@ -14,7 +14,7 @@ contract Test {
// set(uint24[]): 0x20, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 -> 18 // set(uint24[]): 0x20, 18, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 -> 18
// gas irOptimized: 121109 // gas irOptimized: 121109
// gas legacy: 125815 // gas legacy: 125815
// gas legacyOptimized: 123615 // gas legacyOptimized: 123614
// data(uint256): 7 -> 8 // data(uint256): 7 -> 8
// data(uint256): 15 -> 16 // data(uint256): 15 -> 16
// data(uint256): 18 -> FAILURE // data(uint256): 18 -> FAILURE

View File

@ -50,4 +50,4 @@ contract C {
// f() -> 0xff // f() -> 0xff
// gas irOptimized: 137415 // gas irOptimized: 137415
// gas legacy: 137645 // gas legacy: 137645
// gas legacyOptimized: 134377 // gas legacyOptimized: 134376

View File

@ -19,4 +19,4 @@ contract c {
// test() -> 0 // test() -> 0
// gas irOptimized: 397892 // gas irOptimized: 397892
// gas legacy: 565428 // gas legacy: 565428
// gas legacyOptimized: 552525 // gas legacyOptimized: 552524

View File

@ -28,4 +28,4 @@ contract Creator {
// f(uint256,address[]): 7, 0x40, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -> 7, 8 // f(uint256,address[]): 7, 0x40, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 -> 7, 8
// gas irOptimized: 472714 // gas irOptimized: 472714
// gas legacy: 570900 // gas legacy: 570900
// gas legacyOptimized: 436360 // gas legacyOptimized: 435524

View File

@ -298,4 +298,4 @@ contract Test {
// verifyTx() -> true // verifyTx() -> true
// gas irOptimized: 145824 // gas irOptimized: 145824
// gas legacy: 130571 // gas legacy: 130571
// gas legacyOptimized: 100187 // gas legacyOptimized: 100147

View File

@ -24,4 +24,4 @@ contract A {
// same_salt() -> true // same_salt() -> true
// gas irOptimized: 98439083 // gas irOptimized: 98439083
// gas legacy: 98439116 // gas legacy: 98439116
// gas legacyOptimized: 98438982 // gas legacyOptimized: 98438970

View File

@ -38,4 +38,4 @@ contract C {
// f(bytes): 0x20, 0x5, "abcde" -> 0 // f(bytes): 0x20, 0x5, "abcde" -> 0
// gas irOptimized: 248997 // gas irOptimized: 248997
// gas legacy: 239258 // gas legacy: 239258
// gas legacyOptimized: 238578 // gas legacyOptimized: 238577

View File

@ -30,7 +30,7 @@ contract C {
// index(uint256): 0xFF -> true // index(uint256): 0xFF -> true
// gas irOptimized: 167533 // gas irOptimized: 167533
// gas legacy: 248854 // gas legacy: 248854
// gas legacyOptimized: 152640 // gas legacyOptimized: 152638
// accessIndex(uint256,int256): 10, 1 -> 2 // accessIndex(uint256,int256): 10, 1 -> 2
// accessIndex(uint256,int256): 10, 0 -> 1 // accessIndex(uint256,int256): 10, 0 -> 1
// accessIndex(uint256,int256): 10, 11 -> FAILURE, hex"4e487b71", 0x32 // accessIndex(uint256,int256): 10, 11 -> FAILURE, hex"4e487b71", 0x32