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.
Compiler Features:
* Low-Level Inliner: Inline ordinary jumps to small blocks and jumps to small blocks that terminate.
Bugfixes:

View File

@ -82,9 +82,7 @@ bool Inliner::isInlineCandidate(size_t _tag, ranges::span<AssemblyItem const> _i
{
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
// instructions as well in the future.
if (_items.back() != Instruction::JUMP)
if (_items.back() != Instruction::JUMP && !SemanticInformation::terminatesControlFlow(_items.back()))
return false;
// Never inline tags that reference themselves.
@ -196,19 +194,38 @@ bool Inliner::shouldInlineFullFunctionBody(size_t _tag, ranges::span<AssemblyIte
return false;
}
optional<AssemblyItem::JumpType> Inliner::shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const
optional<AssemblyItem> Inliner::shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const
{
AssemblyItem exitJump = _block.items.back();
assertThrow(_jump == Instruction::JUMP && exitJump == Instruction::JUMP, OptimizerException, "");
assertThrow(_jump == Instruction::JUMP, OptimizerException, "");
AssemblyItem blockExit = _block.items.back();
if (
_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) ?
make_optional(AssemblyItem::JumpType::Ordinary) : nullopt;
{
blockExit.setJumpType(AssemblyItem::JumpType::Ordinary);
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;
}
@ -232,10 +249,10 @@ void Inliner::optimise()
{
if (optional<size_t> tag = getLocalTag(item))
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.back().setJumpType(*exitJumpType);
newItems += inlinableBlock->items | ranges::views::drop_last(1);
newItems.emplace_back(move(*exitItem));
// We are removing one push tag to the block we inline.
--inlinableBlock->pushTagCount;

View File

@ -61,8 +61,8 @@ private:
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.
std::optional<AssemblyItem::JumpType> shouldInline(size_t _tag, AssemblyItem const& _jump, InlinableBlock const& _block) const;
/// @returns the exit item for the block to be inlined, if a particular jump to it should be inlined, otherwise nullopt.
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
/// 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.

View File

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

View File

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

View File

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

View File

@ -210,7 +210,7 @@ BOOST_AUTO_TEST_CASE(jump_type)
jumpTypes += item.getJumpTypeAsString() + "\n";
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
BOOST_CHECK_EQUAL(jumpTypes, "[in]\n[out]\n[in]\n[out]\n");
}

View File

@ -21,4 +21,4 @@ contract c {
// test() -> 0
// gas irOptimized: 312322
// 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
// gas irOptimized: 121109
// gas legacy: 125815
// gas legacyOptimized: 123615
// gas legacyOptimized: 123614
// data(uint256): 7 -> 8
// data(uint256): 15 -> 16
// data(uint256): 18 -> FAILURE

View File

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

View File

@ -19,4 +19,4 @@ contract c {
// test() -> 0
// gas irOptimized: 397892
// 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
// gas irOptimized: 472714
// gas legacy: 570900
// gas legacyOptimized: 436360
// gas legacyOptimized: 435524

View File

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

View File

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

View File

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

View File

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