mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			350 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			350 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
    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 <http://www.gnu.org/licenses/>.
 | 
						|
*/
 | 
						|
/**
 | 
						|
 * Unit tests for stack-reusing code generator.
 | 
						|
 */
 | 
						|
 | 
						|
#include <test/Options.h>
 | 
						|
 | 
						|
#include <libyul/AssemblyStack.h>
 | 
						|
#include <libevmasm/Instruction.h>
 | 
						|
 | 
						|
using namespace std;
 | 
						|
 | 
						|
namespace solidity::yul::test
 | 
						|
{
 | 
						|
 | 
						|
namespace
 | 
						|
{
 | 
						|
string assemble(string const& _input)
 | 
						|
{
 | 
						|
	solidity::frontend::OptimiserSettings settings = solidity::frontend::OptimiserSettings::full();
 | 
						|
	settings.runYulOptimiser = false;
 | 
						|
	settings.optimizeStackAllocation = true;
 | 
						|
	AssemblyStack asmStack(langutil::EVMVersion{}, AssemblyStack::Language::StrictAssembly, settings);
 | 
						|
	BOOST_REQUIRE_MESSAGE(asmStack.parseAndAnalyze("", _input), "Source did not parse: " + _input);
 | 
						|
	return evmasm::disassemble(asmStack.assemble(AssemblyStack::Machine::EVM).bytecode->bytecode);
 | 
						|
}
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_SUITE(StackReuseCodegen)
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(smoke_test)
 | 
						|
{
 | 
						|
	string out = assemble("{}");
 | 
						|
	BOOST_CHECK_EQUAL(out, "");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(single_var)
 | 
						|
{
 | 
						|
	string out = assemble("{ let x }");
 | 
						|
	BOOST_CHECK_EQUAL(out, "PUSH1 0x0 POP ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(single_var_assigned)
 | 
						|
{
 | 
						|
	string out = assemble("{ let x := 1 }");
 | 
						|
	BOOST_CHECK_EQUAL(out, "PUSH1 0x1 POP ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(single_var_assigned_plus_code)
 | 
						|
{
 | 
						|
	string out = assemble("{ let x := 1 mstore(3, 4) }");
 | 
						|
	BOOST_CHECK_EQUAL(out, "PUSH1 0x1 POP PUSH1 0x4 PUSH1 0x3 MSTORE ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(single_var_assigned_plus_code_and_reused)
 | 
						|
{
 | 
						|
	string out = assemble("{ let x := 1 mstore(3, 4) pop(mload(x)) }");
 | 
						|
	BOOST_CHECK_EQUAL(out, "PUSH1 0x1 PUSH1 0x4 PUSH1 0x3 MSTORE DUP1 MLOAD POP POP ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(multi_reuse_single_slot)
 | 
						|
{
 | 
						|
	string out = assemble("{ let x := 1 x := 6 let y := 2 y := 4 }");
 | 
						|
	BOOST_CHECK_EQUAL(out, "PUSH1 0x1 PUSH1 0x6 SWAP1 POP POP PUSH1 0x2 PUSH1 0x4 SWAP1 POP POP ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(multi_reuse_single_slot_nested)
 | 
						|
{
 | 
						|
	string out = assemble("{ let x := 1 x := 6 { let y := 2 y := 4 } }");
 | 
						|
	BOOST_CHECK_EQUAL(out, "PUSH1 0x1 PUSH1 0x6 SWAP1 POP POP PUSH1 0x2 PUSH1 0x4 SWAP1 POP POP ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(multi_reuse_same_variable_name)
 | 
						|
{
 | 
						|
	string out = assemble("{ let z := mload(0) { let x := 1 x := 6 z := x } { let x := 2 z := x x := 4 } }");
 | 
						|
	BOOST_CHECK_EQUAL(out,
 | 
						|
		"PUSH1 0x0 MLOAD "
 | 
						|
		"PUSH1 0x1 PUSH1 0x6 SWAP1 POP DUP1 SWAP2 POP POP "
 | 
						|
		"PUSH1 0x2 DUP1 SWAP2 POP PUSH1 0x4 SWAP1 POP POP "
 | 
						|
		"POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(last_use_in_nested_block)
 | 
						|
{
 | 
						|
	string out = assemble("{ let z := 0 { pop(z) } let x := 1 }");
 | 
						|
	BOOST_CHECK_EQUAL(out, "PUSH1 0x0 DUP1 POP POP PUSH1 0x1 POP ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(if_)
 | 
						|
{
 | 
						|
	// z is only removed after the if (after the jumpdest)
 | 
						|
	string out = assemble("{ let z := mload(0) if z { let x := z } let t := 3 }");
 | 
						|
	BOOST_CHECK_EQUAL(out, "PUSH1 0x0 MLOAD DUP1 ISZERO PUSH1 0xA JUMPI DUP1 POP JUMPDEST POP PUSH1 0x3 POP ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(switch_)
 | 
						|
{
 | 
						|
	string out = assemble("{ let z := 0 switch z case 0 { let x := 2 let y := 3 } default { z := 3 } let t := 9 }");
 | 
						|
	BOOST_CHECK_EQUAL(out,
 | 
						|
		"PUSH1 0x0 DUP1 "
 | 
						|
		"PUSH1 0x0 DUP2 EQ PUSH1 0x11 JUMPI "
 | 
						|
		"PUSH1 0x3 SWAP2 POP PUSH1 0x18 JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x2 POP PUSH1 0x3 POP "
 | 
						|
		"JUMPDEST POP POP " // This is where z and its copy (switch condition) can be removed.
 | 
						|
		"PUSH1 0x9 POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(reuse_slots)
 | 
						|
{
 | 
						|
	// x and y should reuse the slots of b and d
 | 
						|
	string out = assemble("{ let a, b, c, d let x := 2 let y := 3 mstore(x, a) mstore(y, c) }");
 | 
						|
	BOOST_CHECK_EQUAL(out,
 | 
						|
		"PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 "
 | 
						|
		"POP " // d is removed right away
 | 
						|
		"PUSH1 0x2 SWAP2 POP " // x is stored at b's slot
 | 
						|
		"PUSH1 0x3 DUP4 DUP4 MSTORE "
 | 
						|
		"DUP2 DUP2 MSTORE "
 | 
						|
		"POP POP POP POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(for_1)
 | 
						|
{
 | 
						|
	// Special scoping rules, but can remove z early
 | 
						|
	string out = assemble("{ for { let z := 0 } 1 { } { let x := 3 } let t := 2 }");
 | 
						|
	BOOST_CHECK_EQUAL(out,
 | 
						|
		"PUSH1 0x0 POP "
 | 
						|
		"JUMPDEST PUSH1 0x1 ISZERO PUSH1 0x11 JUMPI "
 | 
						|
		"PUSH1 0x3 POP JUMPDEST PUSH1 0x3 JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x2 POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(for_2)
 | 
						|
{
 | 
						|
	// Special scoping rules, cannot remove z until after the loop!
 | 
						|
	string out = assemble("{ for { let z := 0 } 1 { } { z := 8 let x := 3 } let t := 2 }");
 | 
						|
	BOOST_CHECK_EQUAL(out,
 | 
						|
		"PUSH1 0x0 "
 | 
						|
		"JUMPDEST PUSH1 0x1 ISZERO PUSH1 0x14 JUMPI "
 | 
						|
		"PUSH1 0x8 SWAP1 POP "
 | 
						|
		"PUSH1 0x3 POP "
 | 
						|
		"JUMPDEST PUSH1 0x2 JUMP "
 | 
						|
		"JUMPDEST POP " // z is removed
 | 
						|
		"PUSH1 0x2 POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(function_trivial)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		function f() { }
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0x6 JUMP JUMPDEST JUMPDEST JUMP JUMPDEST "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(function_retparam)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		function f() -> x, y { }
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0xC JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP1 SWAP2 JUMP "
 | 
						|
		"JUMPDEST "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(function_params)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		function f(a, b) { }
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in), "PUSH1 0x8 JUMP JUMPDEST JUMPDEST POP POP JUMP JUMPDEST ");
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(function_params_and_retparams)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		function f(a, b, c, d) -> x, y { }
 | 
						|
	})";
 | 
						|
	// This does not re-use the parameters for the return parameters
 | 
						|
	// We do not expect parameters to be fully unused, so the stack
 | 
						|
	// layout for a function is still fixed, even though parameters
 | 
						|
	// can be re-used.
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0x11 JUMP JUMPDEST PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP5 POP SWAP5 SWAP3 POP POP POP JUMP JUMPDEST "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(function_params_and_retparams_partly_unused)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		function f(a, b, c, d) -> x, y { b := 3 let s := 9 y := 2 mstore(s, y) }
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0x1F JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x0 PUSH1 0x0 "
 | 
						|
		"PUSH1 0x3 SWAP4 POP "
 | 
						|
		"PUSH1 0x9 PUSH1 0x2 SWAP2 POP "
 | 
						|
		"DUP2 DUP2 MSTORE "
 | 
						|
		"POP JUMPDEST SWAP5 POP SWAP5 SWAP3 POP POP POP JUMP "
 | 
						|
		"JUMPDEST "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(function_with_body_embedded)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		let b := 3
 | 
						|
		function f(a, r) -> t {
 | 
						|
			// r could be removed right away, but a cannot - this is not implemented, though
 | 
						|
			let x := a a := 3 t := a
 | 
						|
		}
 | 
						|
		b := 7
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0x3 PUSH1 "
 | 
						|
		"0x17 JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x0 " // start of f, initialize t
 | 
						|
		"DUP2 POP " // let x := a
 | 
						|
		"PUSH1 0x3 SWAP2 POP "
 | 
						|
		"DUP2 SWAP1 POP "
 | 
						|
		"JUMPDEST SWAP3 SWAP2 POP POP JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x7 SWAP1 "
 | 
						|
		"POP POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(function_call)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		let b := f(1, 2)
 | 
						|
		function f(a, r) -> t { }
 | 
						|
		b := f(3, 4)
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0x9 PUSH1 0x2 PUSH1 0x1 PUSH1 0xD JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x16 JUMP " // jump over f
 | 
						|
		"JUMPDEST PUSH1 0x0 JUMPDEST SWAP3 SWAP2 POP POP JUMP " // f
 | 
						|
		"JUMPDEST PUSH1 0x20 PUSH1 0x4 PUSH1 0x3 PUSH1 0xD JUMP "
 | 
						|
		"JUMPDEST SWAP1 POP POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(functions_multi_return)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		function f(a, b) -> t { }
 | 
						|
		function g() -> r, s { }
 | 
						|
		let x := f(1, 2)
 | 
						|
		x := f(3, 4)
 | 
						|
		let y, z := g()
 | 
						|
		y, z := g()
 | 
						|
		let unused := 7
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0x15 JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x0 JUMPDEST SWAP3 SWAP2 POP POP JUMP " // f
 | 
						|
		"JUMPDEST PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP1 SWAP2 JUMP " // g
 | 
						|
		"JUMPDEST PUSH1 0x1F PUSH1 0x2 PUSH1 0x1 PUSH1 0x3 JUMP " // f(1, 2)
 | 
						|
		"JUMPDEST PUSH1 0x29 PUSH1 0x4 PUSH1 0x3 PUSH1 0x3 JUMP " // f(3, 4)
 | 
						|
		"JUMPDEST SWAP1 POP " // assignment to x
 | 
						|
		"POP " // remove x
 | 
						|
		"PUSH1 0x32 PUSH1 0xC JUMP " // g()
 | 
						|
		"JUMPDEST PUSH1 0x38 PUSH1 0xC JUMP " // g()
 | 
						|
		"JUMPDEST SWAP2 POP SWAP2 POP " // assignments
 | 
						|
		"POP POP " // removal of y and z
 | 
						|
		"PUSH1 0x7 POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(reuse_slots_function)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		function f() -> x, y, z, t {}
 | 
						|
		let a, b, c, d := f() let x1 := 2 let y1 := 3 mstore(x1, a) mstore(y1, c)
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0x12 JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x18 PUSH1 0x3 JUMP "
 | 
						|
		// Stack: a b c d
 | 
						|
		"JUMPDEST POP " // d is unused
 | 
						|
		// Stack: a b c
 | 
						|
		"PUSH1 0x2 SWAP2 POP " // x1 reuses b's slot
 | 
						|
		"PUSH1 0x3 "
 | 
						|
		// Stack: a x1 c y1
 | 
						|
		"DUP4 DUP4 MSTORE "
 | 
						|
		"DUP2 DUP2 MSTORE "
 | 
						|
		"POP POP POP POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
BOOST_AUTO_TEST_CASE(reuse_slots_function_with_gaps)
 | 
						|
{
 | 
						|
	string in = R"({
 | 
						|
		// Only x3 is actually used, the slots of
 | 
						|
		// x1 and x2 will be reused right away.
 | 
						|
		let x1 := 5 let x2 := 6 let x3 := 7
 | 
						|
		mstore(x1, x2)
 | 
						|
		function f() -> x, y, z, t {}
 | 
						|
		let a, b, c, d := f() mstore(x3, a) mstore(c, d)
 | 
						|
	})";
 | 
						|
	BOOST_CHECK_EQUAL(assemble(in),
 | 
						|
		"PUSH1 0x5 PUSH1 0x6 PUSH1 0x7 "
 | 
						|
		"DUP2 DUP4 MSTORE "
 | 
						|
		"PUSH1 0x1B JUMP " // jump across function
 | 
						|
		"JUMPDEST PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 PUSH1 0x0 JUMPDEST SWAP1 SWAP2 SWAP3 SWAP4 JUMP "
 | 
						|
		"JUMPDEST PUSH1 0x21 PUSH1 0xC JUMP "
 | 
						|
		// stack: x1 x2 x3 a b c d
 | 
						|
		"JUMPDEST SWAP6 POP " // move d into x1
 | 
						|
		// stack: d x2 x3 a b c
 | 
						|
		"SWAP4 POP "
 | 
						|
		// stack: d c x3 a b
 | 
						|
		"POP "
 | 
						|
		// stack: d c x3 a
 | 
						|
		"DUP1 DUP3 MSTORE "
 | 
						|
		"POP POP "
 | 
						|
		// stack: d c
 | 
						|
		"DUP2 DUP2 MSTORE "
 | 
						|
		"POP POP "
 | 
						|
	);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
BOOST_AUTO_TEST_SUITE_END()
 | 
						|
 | 
						|
}
 |