/* This file is part of solidity. solidity is free software: you can redistribute it and/or modify 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, 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 . */ /** * @date 2017 * Unit tests for the iulia function inliner. */ #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace dev; using namespace dev::julia; using namespace dev::julia::test; using namespace dev::solidity; namespace { string inlinableFunctions(string const& _source) { auto ast = disambiguate(_source); InlinableExpressionFunctionFinder funFinder; funFinder(ast); return boost::algorithm::join( funFinder.inlinableFunctions() | boost::adaptors::map_keys, "," ); } string inlineFunctions(string const& _source, bool _julia = true) { auto ast = disambiguate(_source, _julia); ExpressionInliner(ast).run(); return assembly::AsmPrinter(_julia)(ast); } string fullInline(string const& _source, bool _julia = true) { Block ast = disambiguate(_source, _julia); (FunctionHoister{})(ast); (FunctionGrouper{})(ast);\ FullInliner(ast).run(); return assembly::AsmPrinter(_julia)(ast); } } BOOST_AUTO_TEST_SUITE(IuliaInlinableFunctionFilter) BOOST_AUTO_TEST_CASE(smoke_test) { BOOST_CHECK_EQUAL(inlinableFunctions("{ }"), ""); } BOOST_AUTO_TEST_CASE(simple) { BOOST_CHECK_EQUAL(inlinableFunctions("{ function f() -> x:u256 { x := 2:u256 } }"), "f"); BOOST_CHECK_EQUAL(inlinableFunctions("{" "function g(a:u256) -> b:u256 { b := a }" "function f() -> x:u256 { x := g(2:u256) }" "}"), "f,g"); } BOOST_AUTO_TEST_CASE(simple_inside_structures) { BOOST_CHECK_EQUAL(inlinableFunctions("{" "switch 2:u256 " "case 2:u256 {" "function g(a:u256) -> b:u256 { b := a }" "function f() -> x:u256 { x := g(2:u256) }" "}" "}"), "f,g"); BOOST_CHECK_EQUAL(inlinableFunctions("{" "for {" "function g(a:u256) -> b:u256 { b := a }" "} 1:u256 {" "function f() -> x:u256 { x := g(2:u256) }" "}" "{" "function h() -> y:u256 { y := 2:u256 }" "}" "}"), "f,g,h"); } BOOST_AUTO_TEST_CASE(negative) { BOOST_CHECK_EQUAL(inlinableFunctions("{ function f() -> x:u256 { } }"), ""); BOOST_CHECK_EQUAL(inlinableFunctions("{ function f() -> x:u256 { x := 2:u256 {} } }"), ""); BOOST_CHECK_EQUAL(inlinableFunctions("{ function f() -> x:u256 { x := f() } }"), ""); BOOST_CHECK_EQUAL(inlinableFunctions("{ function f() -> x:u256 { x := x } }"), ""); BOOST_CHECK_EQUAL(inlinableFunctions("{ function f() -> x:u256, y:u256 { x := 2:u256 } }"), ""); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(IuliaFunctionInliner) BOOST_AUTO_TEST_CASE(simple) { BOOST_CHECK_EQUAL( inlineFunctions("{ function f() -> x:u256 { x := 2:u256 } let y:u256 := f() }"), format("{ function f() -> x:u256 { x := 2:u256 } let y:u256 := 2:u256 }") ); } BOOST_AUTO_TEST_CASE(with_args) { BOOST_CHECK_EQUAL( inlineFunctions("{ function f(a:u256) -> x:u256 { x := a } let y:u256 := f(7:u256) }"), format("{ function f(a:u256) -> x:u256 { x := a } let y:u256 := 7:u256 }") ); } BOOST_AUTO_TEST_CASE(no_inline_with_mload) { // Does not inline because mload could be moved out of sequence BOOST_CHECK_EQUAL( inlineFunctions("{ function f(a) -> x { x := a } let y := f(mload(2)) }", false), format("{ function f(a) -> x { x := a } let y := f(mload(2)) }", false) ); } BOOST_AUTO_TEST_CASE(no_move_with_side_effects) { // The calls to g and h cannot be moved because g and h are not movable. Therefore, the call // to f is not inlined. BOOST_CHECK_EQUAL( inlineFunctions("{" "function f(a, b) -> x { x := add(b, a) }" "function g() -> y { y := mload(0) mstore(0, 4) }" "function h() -> z { mstore(0, 4) z := mload(0) }" "let r := f(g(), h())" "}", false), format("{" "function f(a, b) -> x { x := add(b, a) }" "function g() -> y { y := mload(0) mstore(0, 4) }" "function h() -> z { mstore(0, 4) z := mload(0) }" "let r := f(g(), h())" "}", false) ); } BOOST_AUTO_TEST_CASE(complex_with_evm) { BOOST_CHECK_EQUAL( inlineFunctions("{ function f(a) -> x { x := add(a, a) } let y := f(calldatasize()) }", false), format("{ function f(a) -> x { x := add(a, a) } let y := add(calldatasize(), calldatasize()) }", false) ); } BOOST_AUTO_TEST_CASE(double_calls) { BOOST_CHECK_EQUAL( inlineFunctions("{" "function f(a) -> x { x := add(a, a) }" "function g(b, c) -> y { y := mul(mload(c), f(b)) }" "let y := g(calldatasize(), 7)" "}", false), format("{" "function f(a) -> x { x := add(a, a) }" "function g(b, c) -> y { y := mul(mload(c), add(b, b)) }" "let y_1 := mul(mload(7), add(calldatasize(), calldatasize()))" "}", false) ); } BOOST_AUTO_TEST_CASE(double_recursive_calls) { BOOST_CHECK_EQUAL( inlineFunctions("{" "function f(a, r) -> x { x := g(a, g(r, r)) }" "function g(b, s) -> y { y := f(b, f(s, s)) }" "let y := g(calldatasize(), 7)" "}", false), format("{" "function f(a, r) -> x { x := g(a, f(r, f(r, r))) }" "function g(b, s) -> y { y := f(b, g(s, f(s, f(s, s))))}" "let y_1 := f(calldatasize(), g(7, f(7, f(7, 7))))" "}", false) ); } BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(IuliaFullInliner) BOOST_AUTO_TEST_CASE(simple) { BOOST_CHECK_EQUAL( fullInline(R"({ function f(a) -> x { let r := mul(a, a) x := add(r, r) } let y := add(f(sload(mload(2))), mload(7)) })", false), format(R"({ { let _1 := mload(7) let f_a := sload(mload(2)) let f_x { let f_r := mul(f_a, f_a) f_x := add(f_r, f_r) } let y := add(f_x, _1) } function f(a) -> x { let r := mul(a, a) x := add(r, r) } })", false) ); } BOOST_AUTO_TEST_CASE(multi_fun) { BOOST_CHECK_EQUAL( fullInline(R"({ function f(a) -> x { x := add(a, a) } function g(b, c) -> y { y := mul(mload(c), f(b)) } let y := g(f(3), 7) })", false), format(R"({ { let g_c := 7 let f_a_1 := 3 let f_x_1 { f_x_1 := add(f_a_1, f_a_1) } let g_y { let g_f_a := f_x_1 let g_f_x { g_f_x := add(g_f_a, g_f_a) } g_y := mul(mload(g_c), g_f_x) } let y_1 := g_y } function f(a) -> x { x := add(a, a) } function g(b, c) -> y { let f_a := b let f_x { f_x := add(f_a, f_a) } y := mul(mload(c), f_x) } })", false) ); } BOOST_AUTO_TEST_CASE(move_up_rightwards_arguments) { BOOST_CHECK_EQUAL( fullInline(R"({ function f(a, b, c) -> x { x := add(a, b) x := mul(x, c) } let y := add(mload(1), add(f(mload(2), mload(3), mload(4)), mload(5))) })", false), format(R"({ { let _1 := mload(5) let f_c := mload(4) let f_b := mload(3) let f_a := mload(2) let f_x { f_x := add(f_a, f_b) f_x := mul(f_x, f_c) } let y := add(mload(1), add(f_x, _1)) } function f(a, b, c) -> x { x := add(a, b) x := mul(x, c) } })", false) ); } BOOST_AUTO_TEST_CASE(pop_result) { // This tests that `pop(r)` is removed. BOOST_CHECK_EQUAL( fullInline(R"({ function f(a) -> x { let r := mul(a, a) x := add(r, r) } pop(add(f(7), 2)) })", false), format(R"({ { let _1 := 2 let f_a := 7 let f_x { let f_r := mul(f_a, f_a) f_x := add(f_r, f_r) } { } } function f(a) -> x { let r := mul(a, a) x := add(r, r) } })", false) ); } BOOST_AUTO_TEST_SUITE_END()