diff --git a/Changelog.md b/Changelog.md index 30d1bf2e1..7e422b8e7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,7 @@ Language Features: Compiler Features: + * Yul Optimizer: Enable stack allocation optimization by default if yul optimizer is active (disable in yulDetails). Bugfixes: diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index f4b5aceec..ca773fa72 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -216,8 +216,12 @@ Input Description // It can only be activated through the details here. // This feature is still considered experimental. "yul": false, - // Future tuning options, currently unused. - "yulDetails": {} + // Tuning options for the Yul optimizer. + "yulDetails": { + // Improve allocation of stack slots for variables, can free up stack slots early. + // Activated by default if the Yul optimizer is activated. + "stackAllocation": true + } } }, "evmVersion": "byzantium", // Version of the EVM to compile for. Affects type checking and code generation. Can be homestead, tangerineWhistle, spuriousDragon, byzantium, constantinople or petersburg diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3053e3f7c..e106a4839 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -331,7 +331,7 @@ void CompilerContext::appendInlineAssembly( vector const& _localVariables, set const& _externallyUsedFunctions, bool _system, - bool _optimise + OptimiserSettings const& _optimiserSettings ) { int startStackHeight = stackHeight(); @@ -422,7 +422,7 @@ void CompilerContext::appendInlineAssembly( // Several optimizer steps cannot handle externally supplied stack variables, // so we essentially only optimize the ABI functions. - if (_optimise && _localVariables.empty()) + if (_optimiserSettings.runYulOptimiser && _localVariables.empty()) { yul::OptimiserSuite::run( yul::EVMDialect::strictAssemblyForEVM(m_evmVersion), @@ -445,7 +445,15 @@ void CompilerContext::appendInlineAssembly( reportError("Failed to analyze inline assembly block."); solAssert(errorReporter.errors().empty(), "Failed to analyze inline assembly block."); - yul::CodeGenerator::assemble(*parserResult, analysisInfo, *m_asm, m_evmVersion, identifierAccess, _system, _optimise); + yul::CodeGenerator::assemble( + *parserResult, + analysisInfo, + *m_asm, + m_evmVersion, + identifierAccess, + _system, + _optimiserSettings.optimizeStackAllocation + ); // Reset the source location to the one of the node (instead of the CODEGEN source location) updateSourceLocation(); diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 44f96f5dd..3744913c3 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -217,7 +217,7 @@ public: std::vector const& _localVariables = std::vector(), std::set const& _externallyUsedFunctions = std::set(), bool _system = false, - bool _optimise = false + OptimiserSettings const& _optimiserSettings = OptimiserSettings::none() ); /// Appends arbitrary data to the end of the bytecode. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index b10882749..2e5338942 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -720,7 +720,9 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) *_inlineAssembly.annotation().analysisInfo, *m_context.assemblyPtr(), m_context.evmVersion(), - identifierAccess + identifierAccess, + false, + m_optimiserSettings.optimizeStackAllocation ); m_context.setStackOffset(startStackHeight); return false; @@ -983,7 +985,7 @@ void ContractCompiler::appendMissingFunctions() {}, abiFunctions.second, true, - m_optimiserSettings.runYulOptimiser + m_optimiserSettings ); } diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index c0dc2ffd7..3c753c25b 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -993,7 +993,11 @@ string CompilerStack::createMetadata(Contract const& _contract) const details["cse"] = m_optimiserSettings.runCSE; details["constantOptimizer"] = m_optimiserSettings.runConstantOptimiser; details["yul"] = m_optimiserSettings.runYulOptimiser; - details["yulDetails"] = Json::objectValue; + if (m_optimiserSettings.runYulOptimiser) + { + details["yulDetails"] = Json::objectValue; + details["yulDetails"]["stackAllocation"] = m_optimiserSettings.optimizeStackAllocation; + } meta["settings"]["optimizer"]["details"] = std::move(details); } diff --git a/libsolidity/interface/OptimiserSettings.h b/libsolidity/interface/OptimiserSettings.h index aae5fa1ce..c88778949 100644 --- a/libsolidity/interface/OptimiserSettings.h +++ b/libsolidity/interface/OptimiserSettings.h @@ -54,15 +54,17 @@ struct OptimiserSettings s.runDeduplicate = true; s.runCSE = true; s.runConstantOptimiser = true; - // The only disabled one + // The only disabled ones + s.optimizeStackAllocation = false; s.runYulOptimiser = false; s.expectedExecutionsPerDeployment = 200; return s; } - /// Standard optimisations plus yul optimiser. + /// Standard optimisations plus yul and stack optimiser. static OptimiserSettings full() { OptimiserSettings s = enabled(); + s.optimizeStackAllocation = true; s.runYulOptimiser = true; return s; } @@ -76,6 +78,7 @@ struct OptimiserSettings runDeduplicate == _other.runDeduplicate && runCSE == _other.runCSE && runConstantOptimiser == _other.runConstantOptimiser && + optimizeStackAllocation == _other.optimizeStackAllocation && runYulOptimiser == _other.runYulOptimiser && expectedExecutionsPerDeployment == _other.expectedExecutionsPerDeployment; } @@ -94,6 +97,8 @@ struct OptimiserSettings /// Constant optimizer, which tries to find better representations that satisfy the given /// size/cost-trade-off. bool runConstantOptimiser = false; + /// Perform more efficient stack allocation for variables during code generation from Yul to bytecode. + bool optimizeStackAllocation = false; /// Yul optimiser with default settings. Will only run on certain parts of the code for now. bool runYulOptimiser = false; /// This specifies an estimate on how often each opcode in this assembly will be executed, diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index c17fdf39a..326dac601 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -399,12 +399,17 @@ boost::variant parseOptimizerSettings(Json::Valu return *error; if (auto error = checkOptimizerDetail(details, "yul", settings.runYulOptimiser)) return *error; + if (settings.runYulOptimiser) + settings.optimizeStackAllocation = true; if (details.isMember("yulDetails")) { - if (!_jsonInput["yulDetails"].isObject()) - return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting has to be a JSON object."); - if (!_jsonInput["yulDetails"].getMemberNames().empty()) - return formatFatalError("JSONError", "The \"yulDetails\" optimizer setting cannot have any settings yet."); + if (!settings.runYulOptimiser) + return formatFatalError("JSONError", "\"Providing yulDetails requires Yul optimizer to be enabled."); + + if (auto result = checkKeys(details["yulDetails"], {"stackAllocation"}, "settings.optimizer.details.yulDetails")) + return *result; + if (auto error = checkOptimizerDetail(details["yulDetails"], "stackAllocation", settings.optimizeStackAllocation)) + return *error; } } return std::move(settings); diff --git a/libyul/AssemblyStack.h b/libyul/AssemblyStack.h index 4484c546c..8a30ebfcb 100644 --- a/libyul/AssemblyStack.h +++ b/libyul/AssemblyStack.h @@ -74,7 +74,7 @@ public: /// Run the assembly step (should only be called after parseAndAnalyze). /// @param _optimize does not run the optimizer but performs optimized code generation. - MachineAssemblyObject assemble(Machine _machine, bool _optimize = false) const; + MachineAssemblyObject assemble(Machine _machine, bool _optimize) const; /// @returns the errors generated during parsing, analysis (and potentially assembly). langutil::ErrorList const& errors() const { return m_errors; } diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index 19c87b77d..489bfdc19 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -180,7 +180,7 @@ void CodeGenerator::assemble( langutil::EVMVersion _evmVersion, ExternalIdentifierAccess const& _identifierAccess, bool _useNamedLabelsForFunctions, - bool _optimize + bool _optimizeStackAllocation ) { EthAssemblyAdapter assemblyAdapter(_assembly); @@ -190,7 +190,7 @@ void CodeGenerator::assemble( _analysisInfo, _parsedData, *dialect, - _optimize, + _optimizeStackAllocation, false, _identifierAccess, _useNamedLabelsForFunctions diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index 2c09e2556..9647014ce 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -82,7 +82,7 @@ public: langutil::EVMVersion _evmVersion, ExternalIdentifierAccess const& _identifierAccess = ExternalIdentifierAccess(), bool _useNamedLabelsForFunctions = false, - bool _optimize = false + bool _optimizeStackAllocation = false ); }; diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 2eb01c47b..ac512d4e4 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -907,6 +907,7 @@ bool CommandLineInterface::processInput() OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::enabled() : OptimiserSettings::minimal(); settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as(); settings.runYulOptimiser = m_args.count(g_strOptimizeYul); + settings.optimizeStackAllocation = settings.runYulOptimiser; m_compiler->setOptimiserSettings(settings); bool successful = m_compiler->compile(); @@ -1294,7 +1295,7 @@ bool CommandLineInterface::assemble( yul::MachineAssemblyObject object; try { - object = stack.assemble(_targetMachine); + object = stack.assemble(_targetMachine, _optimize); } catch (Exception const& _exception) { diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh index 9a3e53111..389aed579 100755 --- a/test/cmdlineTests.sh +++ b/test/cmdlineTests.sh @@ -125,6 +125,8 @@ function test_solc_behaviour() sed -i -e '/^Warning: This is a pre-release compiler version, please do not use it in production./d' "$stderr_path" sed -i -e 's/ Consider adding "pragma .*$//' "$stderr_path" fi + # Remove path to cpp file + sed -i -e 's/^\(Exception while assembling:\).*/\1/' "$stderr_path" if [[ $exitCode -ne "$exit_code_expected" ]] then diff --git a/test/cmdlineTests/object_compiler/output b/test/cmdlineTests/object_compiler/output index 496ac4193..51830a0c0 100644 --- a/test/cmdlineTests/object_compiler/output +++ b/test/cmdlineTests/object_compiler/output @@ -42,7 +42,6 @@ Text representation: 0x00 /* "object_compiler/input.sol":265:295 */ return - /* "object_compiler/input.sol":29:299 */ pop stop diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json index 056aee91b..18d3852db 100644 --- a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json +++ b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/input.json @@ -10,7 +10,7 @@ "settings": { "optimizer": { - "details": { "yulDetails": 7 } + "details": { "yul": true, "yulDetails": 7 } } } } diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json index b71a8a61a..35638adf0 100644 --- a/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json +++ b/test/cmdlineTests/standard_optimizer_yulDetails_no_object/output.json @@ -1 +1 @@ -{"errors":[{"component":"general","formattedMessage":"The \"yulDetails\" optimizer setting has to be a JSON object.","message":"The \"yulDetails\" optimizer setting has to be a JSON object.","severity":"error","type":"JSONError"}]} +{"errors":[{"component":"general","formattedMessage":"\"settings.optimizer.details.yulDetails\" must be an object","message":"\"settings.optimizer.details.yulDetails\" must be an object","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/input.json b/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/input.json new file mode 100644 index 000000000..056aee91b --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/input.json @@ -0,0 +1,16 @@ +{ + "language": "Solidity", + "sources": + { + "A": + { + "content": "pragma solidity >=0.0; contract C { function f() public pure {} }" + } + }, + "settings": + { + "optimizer": { + "details": { "yulDetails": 7 } + } + } +} diff --git a/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/output.json b/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/output.json new file mode 100644 index 000000000..c44794cc1 --- /dev/null +++ b/test/cmdlineTests/standard_optimizer_yulDetails_without_yul/output.json @@ -0,0 +1 @@ +{"errors":[{"component":"general","formattedMessage":"\"Providing yulDetails requires Yul optimizer to be enabled.","message":"\"Providing yulDetails requires Yul optimizer to be enabled.","severity":"error","type":"JSONError"}]} diff --git a/test/cmdlineTests/yul_stack_opt/args b/test/cmdlineTests/yul_stack_opt/args new file mode 100644 index 000000000..20fe41eb5 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt/args @@ -0,0 +1 @@ +--strict-assembly --optimize diff --git a/test/cmdlineTests/yul_stack_opt/input.sol b/test/cmdlineTests/yul_stack_opt/input.sol new file mode 100644 index 000000000..772a6d4df --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt/input.sol @@ -0,0 +1,24 @@ +{ + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + let b := 1 + let z3 := 1 + sstore(a, b) + sstore(add(a, 1), b) + sstore(add(a, 2), b) + sstore(add(a, 3), b) + sstore(add(a, 4), b) + sstore(add(a, 5), b) + sstore(add(a, 6), b) + sstore(add(a, 7), b) + sstore(add(a, 8), b) + sstore(add(a, 9), b) + sstore(add(a, 10), b) + sstore(add(a, 11), b) + sstore(add(a, 12), b) + } + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) +} diff --git a/test/cmdlineTests/yul_stack_opt/output b/test/cmdlineTests/yul_stack_opt/output new file mode 100644 index 000000000..c8e10fe86 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt/output @@ -0,0 +1,202 @@ + +======= yul_stack_opt/input.sol (EVM) ======= + +Pretty printed source: +object "object" { + code { + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + sstore(a, a) + sstore(2, a) + sstore(3, a) + sstore(4, a) + sstore(5, a) + sstore(6, a) + sstore(7, a) + sstore(8, a) + sstore(9, a) + sstore(10, a) + sstore(11, a) + sstore(12, a) + sstore(13, a) + } + } +} + + +Binary representation: +60056032565b505050505050505050505050505050601a6032565b5050505050505050505050505050508082555050609a565b60006000600060006000600060006000600060006000600060006000600060006001808155806002558060035580600455806005558060065580600755806008558060095580600a5580600b5580600c5580600d5550909192939495969798999a9b9c9d9e9f565b + +Text representation: + /* "yul_stack_opt/input.sol":495:500 */ + tag_1 + jump(tag_2) +tag_1: + /* "yul_stack_opt/input.sol":425:500 */ + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + /* "yul_stack_opt/input.sol":572:577 */ + tag_3 + jump(tag_2) +tag_3: + /* "yul_stack_opt/input.sol":502:577 */ + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + pop + /* "yul_stack_opt/input.sol":590:592 */ + dup1 + /* "yul_stack_opt/input.sol":586:588 */ + dup3 + /* "yul_stack_opt/input.sol":579:593 */ + sstore + pop + pop + /* "yul_stack_opt/input.sol":3:423 */ + jump(tag_4) +tag_2: + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + 0x00 + /* "yul_stack_opt/input.sol":98:99 */ + 0x01 + /* "yul_stack_opt/input.sol":139:140 */ + dup1 + /* "yul_stack_opt/input.sol":136:137 */ + dup2 + /* "yul_stack_opt/input.sol":129:141 */ + sstore + /* "yul_stack_opt/input.sol":162:163 */ + dup1 + /* "yul_stack_opt/input.sol":151:160 */ + 0x02 + /* "yul_stack_opt/input.sol":144:164 */ + sstore + /* "yul_stack_opt/input.sol":185:186 */ + dup1 + /* "yul_stack_opt/input.sol":174:183 */ + 0x03 + /* "yul_stack_opt/input.sol":167:187 */ + sstore + /* "yul_stack_opt/input.sol":208:209 */ + dup1 + /* "yul_stack_opt/input.sol":197:206 */ + 0x04 + /* "yul_stack_opt/input.sol":190:210 */ + sstore + /* "yul_stack_opt/input.sol":231:232 */ + dup1 + /* "yul_stack_opt/input.sol":220:229 */ + 0x05 + /* "yul_stack_opt/input.sol":213:233 */ + sstore + /* "yul_stack_opt/input.sol":254:255 */ + dup1 + /* "yul_stack_opt/input.sol":243:252 */ + 0x06 + /* "yul_stack_opt/input.sol":236:256 */ + sstore + /* "yul_stack_opt/input.sol":277:278 */ + dup1 + /* "yul_stack_opt/input.sol":266:275 */ + 0x07 + /* "yul_stack_opt/input.sol":259:279 */ + sstore + /* "yul_stack_opt/input.sol":300:301 */ + dup1 + /* "yul_stack_opt/input.sol":289:298 */ + 0x08 + /* "yul_stack_opt/input.sol":282:302 */ + sstore + /* "yul_stack_opt/input.sol":323:324 */ + dup1 + /* "yul_stack_opt/input.sol":312:321 */ + 0x09 + /* "yul_stack_opt/input.sol":305:325 */ + sstore + /* "yul_stack_opt/input.sol":346:347 */ + dup1 + /* "yul_stack_opt/input.sol":335:344 */ + 0x0a + /* "yul_stack_opt/input.sol":328:348 */ + sstore + /* "yul_stack_opt/input.sol":370:371 */ + dup1 + /* "yul_stack_opt/input.sol":358:368 */ + 0x0b + /* "yul_stack_opt/input.sol":351:372 */ + sstore + /* "yul_stack_opt/input.sol":394:395 */ + dup1 + /* "yul_stack_opt/input.sol":382:392 */ + 0x0c + /* "yul_stack_opt/input.sol":375:396 */ + sstore + /* "yul_stack_opt/input.sol":418:419 */ + dup1 + /* "yul_stack_opt/input.sol":406:416 */ + 0x0d + /* "yul_stack_opt/input.sol":399:420 */ + sstore + pop + /* "yul_stack_opt/input.sol":85:423 */ + swap1 + swap2 + swap3 + swap4 + swap5 + swap6 + swap7 + swap8 + swap9 + swap10 + swap11 + swap12 + swap13 + swap14 + swap15 + swap16 + jump +tag_4: + diff --git a/test/cmdlineTests/yul_stack_opt_disabled/args b/test/cmdlineTests/yul_stack_opt_disabled/args new file mode 100644 index 000000000..2c89c24e0 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/args @@ -0,0 +1 @@ +--strict-assembly diff --git a/test/cmdlineTests/yul_stack_opt_disabled/err b/test/cmdlineTests/yul_stack_opt_disabled/err new file mode 100644 index 000000000..6959bbf41 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/err @@ -0,0 +1,5 @@ +Exception while assembling: +Dynamic exception type: boost::exception_detail::clone_impl +std::exception::what: Variable a1 is 17 slot(s) too deep inside the stack. +[dev::tag_comment*] = Variable a1 is 17 slot(s) too deep inside the stack. + diff --git a/test/cmdlineTests/yul_stack_opt_disabled/exit b/test/cmdlineTests/yul_stack_opt_disabled/exit new file mode 100644 index 000000000..d00491fd7 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/exit @@ -0,0 +1 @@ +1 diff --git a/test/cmdlineTests/yul_stack_opt_disabled/input.sol b/test/cmdlineTests/yul_stack_opt_disabled/input.sol new file mode 100644 index 000000000..772a6d4df --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/input.sol @@ -0,0 +1,24 @@ +{ + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + let b := 1 + let z3 := 1 + sstore(a, b) + sstore(add(a, 1), b) + sstore(add(a, 2), b) + sstore(add(a, 3), b) + sstore(add(a, 4), b) + sstore(add(a, 5), b) + sstore(add(a, 6), b) + sstore(add(a, 7), b) + sstore(add(a, 8), b) + sstore(add(a, 9), b) + sstore(add(a, 10), b) + sstore(add(a, 11), b) + sstore(add(a, 12), b) + } + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) +} diff --git a/test/cmdlineTests/yul_stack_opt_disabled/output b/test/cmdlineTests/yul_stack_opt_disabled/output new file mode 100644 index 000000000..c9e3d5078 --- /dev/null +++ b/test/cmdlineTests/yul_stack_opt_disabled/output @@ -0,0 +1,31 @@ + +======= yul_stack_opt_disabled/input.sol (EVM) ======= + +Pretty printed source: +object "object" { + code { + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + let b := 1 + let z3 := 1 + sstore(a, b) + sstore(add(a, 1), b) + sstore(add(a, 2), b) + sstore(add(a, 3), b) + sstore(add(a, 4), b) + sstore(add(a, 5), b) + sstore(add(a, 6), b) + sstore(add(a, 7), b) + sstore(add(a, 8), b) + sstore(add(a, 9), b) + sstore(add(a, 10), b) + sstore(add(a, 11), b) + sstore(add(a, 12), b) + } + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) + } +} + diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 7e5c47cf2..149f7679c 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -66,8 +66,9 @@ boost::optional parseAndReturnFirstError( try { success = stack.parseAndAnalyze("", _source); + bool const optimize = false; if (success && _assemble) - stack.assemble(_machine); + stack.assemble(_machine, optimize); } catch (FatalError const&) { diff --git a/test/libsolidity/StandardCompiler.cpp b/test/libsolidity/StandardCompiler.cpp index f2c8f9582..1a539bc8f 100644 --- a/test/libsolidity/StandardCompiler.cpp +++ b/test/libsolidity/StandardCompiler.cpp @@ -1010,7 +1010,10 @@ BOOST_AUTO_TEST_CASE(optimizer_settings_details_different) BOOST_CHECK(optimizer["details"]["jumpdestRemover"].asBool() == true); BOOST_CHECK(optimizer["details"]["orderLiterals"].asBool() == false); BOOST_CHECK(optimizer["details"]["peephole"].asBool() == true); + BOOST_CHECK(optimizer["details"]["yul"].asBool() == true); BOOST_CHECK(optimizer["details"]["yulDetails"].isObject()); + BOOST_CHECK(optimizer["details"]["yulDetails"].getMemberNames() == vector{"stackAllocation"}); + BOOST_CHECK(optimizer["details"]["yulDetails"]["stackAllocation"].asBool() == true); BOOST_CHECK_EQUAL(optimizer["details"].getMemberNames().size(), 8); BOOST_CHECK(optimizer["runs"].asUInt() == 600); } @@ -1077,6 +1080,75 @@ BOOST_AUTO_TEST_CASE(common_pattern) BOOST_CHECK(contract["evm"]["bytecode"]["object"].isString()); } +BOOST_AUTO_TEST_CASE(use_stack_optimization) +{ + // NOTE: the contract code here should fail to compile due to "out of stack" + // If we enable stack optimization, though, it will compile. + char const* input = R"( + { + "language": "Solidity", + "settings": { + "optimizer": { "enabled": true, "details": { "yul": true } }, + "outputSelection": { + "fileA": { "A": [ "evm.bytecode.object" ] } + } + }, + "sources": { + "fileA": { + "content": "contract A { + function y() public { + assembly { + function fun() -> a3, b3, c3, d3, e3, f3, g3, h3, i3, j3, k3, l3, m3, n3, o3, p3 + { + let a := 1 + let b := 1 + let z3 := 1 + sstore(a, b) + sstore(add(a, 1), b) + sstore(add(a, 2), b) + sstore(add(a, 3), b) + sstore(add(a, 4), b) + sstore(add(a, 5), b) + sstore(add(a, 6), b) + sstore(add(a, 7), b) + sstore(add(a, 8), b) + sstore(add(a, 9), b) + sstore(add(a, 10), b) + sstore(add(a, 11), b) + sstore(add(a, 12), b) + } + let a1, b1, c1, d1, e1, f1, g1, h1, i1, j1, k1, l1, m1, n1, o1, p1 := fun() + let a2, b2, c2, d2, e2, f2, g2, h2, i2, j2, k2, l2, m2, n2, o2, p2 := fun() + sstore(a1, a2) + } + } + }" + } + } + } + )"; + + Json::Value parsedInput; + BOOST_REQUIRE(jsonParseStrict(input, parsedInput)); + + dev::solidity::StandardCompiler compiler; + Json::Value result = compiler.compile(parsedInput); + + BOOST_CHECK(containsAtMostWarnings(result)); + Json::Value contract = getContractResult(result, "fileA", "A"); + BOOST_REQUIRE(contract.isObject()); + BOOST_REQUIRE(contract["evm"]["bytecode"]["object"].isString()); + BOOST_CHECK(contract["evm"]["bytecode"]["object"].asString().length() > 20); + + // Now disable stack optimizations + // results in "stack too deep" + parsedInput["settings"]["optimizer"]["details"]["yulDetails"]["stackAllocation"] = false; + result = compiler.compile(parsedInput); + BOOST_REQUIRE(result["errors"].isArray()); + BOOST_CHECK(result["errors"][0]["severity"] == "error"); + BOOST_CHECK(result["errors"][0]["type"] == "InternalCompilerError"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libyul/ObjectCompilerTest.cpp b/test/libyul/ObjectCompilerTest.cpp index c710cfd4b..4c4315733 100644 --- a/test/libyul/ObjectCompilerTest.cpp +++ b/test/libyul/ObjectCompilerTest.cpp @@ -74,7 +74,7 @@ bool ObjectCompilerTest::run(ostream& _stream, string const& _linePrefix, bool c if (m_optimize) stack.optimize(); - MachineAssemblyObject obj = stack.assemble(AssemblyStack::Machine::EVM); + MachineAssemblyObject obj = stack.assemble(AssemblyStack::Machine::EVM, m_optimize); solAssert(obj.bytecode, ""); m_obtainedResult = "Assembly:\n" + obj.assembly;