diff --git a/Changelog.md b/Changelog.md index 7a5adcc4b08d..e67cf0f07d91 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,10 +7,12 @@ Language Features: Compiler Features: + * Command Line Interface: Do not perform IR optimization when only unoptimized IR is requested. * Error Reporting: Unimplemented features are now properly reported as errors instead of being handled as if they were bugs. * EVM: Support for the EVM version "Prague". * SMTChecker: Add CHC engine check for underflow and overflow in unary minus operation. * SMTChecker: Replace CVC4 as a possible BMC backend with cvc5. + * Standard JSON Interface: Do not perform IR optimization when only unoptimized IR is requested. * Yul Optimizer: The optimizer now treats some previously unrecognized identical literals as identical. diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 566feb21b171..f2c37fa68523 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -313,7 +313,7 @@ void CompilerStack::reset(bool _keepSettings) m_viaIR = false; m_evmVersion = langutil::EVMVersion(); m_modelCheckerSettings = ModelCheckerSettings{}; - m_generateIR = false; + m_irOutputSelection = IROutputSelection::None; m_revertStrings = RevertStrings::Default; m_optimiserSettings = OptimiserSettings::minimal(); m_metadataLiteralSources = false; @@ -728,8 +728,17 @@ bool CompilerStack::compile(State _stopAfter) { try { - if ((m_generateEvmBytecode && m_viaIR) || m_generateIR) - generateIR(*contract); + // NOTE: Bytecode generation via IR always uses Contract::yulIROptimized. + // When optimization is not enabled, that member simply contains unoptimized code. + bool needIROutput = + (m_generateEvmBytecode && m_viaIR) || + m_irOutputSelection != IROutputSelection::None; + bool needUnoptimizedIROutputOnly = + !(m_generateEvmBytecode && m_viaIR) && + m_irOutputSelection != IROutputSelection::UnoptimizedAndOptimized; + + if (needIROutput) + generateIR(*contract, needUnoptimizedIROutputOnly); if (m_generateEvmBytecode) { if (m_viaIR) @@ -1402,7 +1411,7 @@ void CompilerStack::compileContract( assembleYul(_contract, compiler->assemblyPtr(), compiler->runtimeAssemblyPtr()); } -void CompilerStack::generateIR(ContractDefinition const& _contract) +void CompilerStack::generateIR(ContractDefinition const& _contract, bool _unoptimizedOnly) { solAssert(m_stackState >= AnalysisSuccessful, ""); @@ -1420,7 +1429,7 @@ void CompilerStack::generateIR(ContractDefinition const& _contract) std::string dependenciesSource; for (auto const& [dependency, referencee]: _contract.annotation().contractDependencies) - generateIR(*dependency); + generateIR(*dependency, _unoptimizedOnly); if (!_contract.canBeDeployed()) return; @@ -1485,9 +1494,14 @@ void CompilerStack::generateIR(ContractDefinition const& _contract) { YulStack stack = parseYul(compiledContract.yulIR); compiledContract.yulIRAst = stack.astJson(); - stack.optimize(); - compiledContract.yulIROptimized = stack.print(this); + if (!_unoptimizedOnly) + { + stack.optimize(); + compiledContract.yulIROptimized = stack.print(this); + } } + + if (!_unoptimizedOnly) { // Optimizer does not maintain correct native source locations in the AST. // We can work around it by regenerating the AST from scratch from optimized IR. diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 672067d81cc7..2c5ecb132b94 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -126,6 +126,12 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac SolidityAST, }; + enum class IROutputSelection { + None, + UnoptimizedOnly, + UnoptimizedAndOptimized, + }; + /// Creates a new compiler stack. /// @param _readFile callback used to read files for import statements. Must return /// and must not emit exceptions. @@ -192,8 +198,14 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac /// Enable EVM Bytecode generation. This is enabled by default. void enableEvmBytecodeGeneration(bool _enable = true) { m_generateEvmBytecode = _enable; } - /// Enable generation of Yul IR code. - void enableIRGeneration(bool _enable = true) { m_generateIR = _enable; } + /// Enable generation of Yul IR code so that IR output can be safely requested for all contracts. + /// Note that IR may also be implicitly generated when not requested. In particular + /// @a setViaIR(true) requires access to the IR outputs for bytecode generation. + void requestIROutputs(IROutputSelection _selection = IROutputSelection::UnoptimizedAndOptimized) + { + solAssert(m_stackState < ParsedAndImported); + m_irOutputSelection = _selection; + } /// @arg _metadataLiteralSources When true, store sources as literals in the contract metadata. /// Must be set before parsing. @@ -387,8 +399,8 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac std::shared_ptr evmRuntimeAssembly; evmasm::LinkerObject object; ///< Deployment object (includes the runtime sub-object). evmasm::LinkerObject runtimeObject; ///< Runtime object. - std::string yulIR; ///< Yul IR code. - std::string yulIROptimized; ///< Optimized Yul IR code. + std::string yulIR; ///< Yul IR code straight from the code generator. + std::string yulIROptimized; ///< Reparsed and possibly optimized Yul IR code. Json yulIRAst; ///< JSON AST of Yul IR code. Json yulIROptimizedAst; ///< JSON AST of optimized Yul IR code. util::LazyInit metadata; ///< The metadata json that will be hashed into the chain. @@ -449,8 +461,14 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac ); /// Generate Yul IR for a single contract. - /// The IR is stored but otherwise unused. - void generateIR(ContractDefinition const& _contract); + /// Unoptimized IR is stored but otherwise unused, while optimized IR may be used for code + /// generation if compilation via IR is enabled. Note that whether "optimized IR" is actually + /// optimized depends on the optimizer settings. + /// @param _contract Contract to generate IR for. + /// @param _unoptimizedOnly If true, only the IR coming directly from the codegen is stored. + /// Optimizer is not invoked and optimized IR output is not available, which means that + /// optimized IR, its AST or compilation via IR must not be requested. + void generateIR(ContractDefinition const& _contract, bool _unoptimizedOnly); /// Generate EVM representation for a single contract. /// Depends on output generated by generateIR. @@ -515,7 +533,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac ModelCheckerSettings m_modelCheckerSettings; std::map> m_requestedContractNames; bool m_generateEvmBytecode = true; - bool m_generateIR = false; + IROutputSelection m_irOutputSelection = IROutputSelection::None; std::map m_libraries; ImportRemapper m_importRemapper; std::map m_sources; diff --git a/libsolidity/interface/StandardCompiler.cpp b/libsolidity/interface/StandardCompiler.cpp index b908ab0cc6fd..cb0c8126820e 100644 --- a/libsolidity/interface/StandardCompiler.cpp +++ b/libsolidity/interface/StandardCompiler.cpp @@ -295,25 +295,26 @@ bool isEvmBytecodeRequested(Json const& _outputSelection) return false; } -/// @returns true if any Yul IR was requested. Note that as an exception, '*' does not -/// yet match "ir", "irAst", "irOptimized" or "irOptimizedAst" -bool isIRRequested(Json const& _outputSelection) +/// @returns The IR output selection for CompilerStack, based on outputs requested in the JSON. +/// Note that as an exception, '*' does not yet match "ir", "irAst", "irOptimized" or "irOptimizedAst". +CompilerStack::IROutputSelection irOutputSelection(Json const& _outputSelection) { if (!_outputSelection.is_object()) - return false; + return CompilerStack::IROutputSelection::None; + CompilerStack::IROutputSelection selection = CompilerStack::IROutputSelection::None; for (auto const& fileRequests: _outputSelection) for (auto const& requests: fileRequests) for (auto const& request: requests) - if ( - request == "ir" || - request == "irAst" || - request == "irOptimized" || - request == "irOptimizedAst" - ) - return true; + { + if (request == "irOptimized" || request == "irOptimizedAst") + return CompilerStack::IROutputSelection::UnoptimizedAndOptimized; - return false; + if (request == "ir" || request == "irAst") + selection = CompilerStack::IROutputSelection::UnoptimizedOnly; + } + + return selection; } Json formatLinkReferences(std::map const& linkReferences) @@ -1320,7 +1321,7 @@ Json StandardCompiler::compileSolidity(StandardCompiler::InputsAndSettings _inpu compilerStack.setModelCheckerSettings(_inputsAndSettings.modelCheckerSettings); compilerStack.enableEvmBytecodeGeneration(isEvmBytecodeRequested(_inputsAndSettings.outputSelection)); - compilerStack.enableIRGeneration(isIRRequested(_inputsAndSettings.outputSelection)); + compilerStack.requestIROutputs(irOutputSelection(_inputsAndSettings.outputSelection)); Json errors = std::move(_inputsAndSettings.errors); diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 367434c93f2a..5d0272dfa930 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -849,13 +849,14 @@ void CommandLineInterface::compile() m_compiler->setRevertStringBehaviour(m_options.output.revertStrings); if (m_options.output.debugInfoSelection.has_value()) m_compiler->selectDebugInfo(m_options.output.debugInfoSelection.value()); - // TODO: Perhaps we should not compile unless requested - m_compiler->enableIRGeneration( - m_options.compiler.outputs.ir || - m_options.compiler.outputs.irOptimized || - m_options.compiler.outputs.irAstJson || - m_options.compiler.outputs.irOptimizedAstJson - ); + + CompilerStack::IROutputSelection irOutputSelection = CompilerStack::IROutputSelection::None; + if (m_options.compiler.outputs.irOptimized || m_options.compiler.outputs.irOptimizedAstJson) + irOutputSelection = CompilerStack::IROutputSelection::UnoptimizedAndOptimized; + else if (m_options.compiler.outputs.ir || m_options.compiler.outputs.irAstJson) + irOutputSelection = CompilerStack::IROutputSelection::UnoptimizedOnly; + + m_compiler->requestIROutputs(irOutputSelection); m_compiler->enableEvmBytecodeGeneration( m_options.compiler.estimateGas || m_options.compiler.outputs.asm_ || diff --git a/test/cmdlineTests/ir_optimized_with_optimize/args b/test/cmdlineTests/ir_optimized_with_optimize/args new file mode 100644 index 000000000000..a536442c5ce2 --- /dev/null +++ b/test/cmdlineTests/ir_optimized_with_optimize/args @@ -0,0 +1 @@ +--ir-optimized --optimize --debug-info none diff --git a/test/cmdlineTests/ir_optimized_with_optimize/input.sol b/test/cmdlineTests/ir_optimized_with_optimize/input.sol new file mode 100644 index 000000000000..a3a86cc8d317 --- /dev/null +++ b/test/cmdlineTests/ir_optimized_with_optimize/input.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity *; + +contract C {} diff --git a/test/cmdlineTests/ir_optimized_with_optimize/output b/test/cmdlineTests/ir_optimized_with_optimize/output new file mode 100644 index 000000000000..8790e45d353d --- /dev/null +++ b/test/cmdlineTests/ir_optimized_with_optimize/output @@ -0,0 +1,19 @@ +Optimized IR: +/// @use-src 0:"ir_optimized_with_optimize/input.sol" +object "C_2" { + code { + { + let _1 := memoryguard(0x80) + mstore(64, _1) + if callvalue() { revert(0, 0) } + let _2 := datasize("C_2_deployed") + codecopy(_1, dataoffset("C_2_deployed"), _2) + return(_1, _2) + } + } + /// @use-src 0:"ir_optimized_with_optimize/input.sol" + object "C_2_deployed" { + code { { revert(0, 0) } } + data ".metadata" hex"" + } +} diff --git a/test/cmdlineTests/ir_unoptimized_with_optimize/args b/test/cmdlineTests/ir_unoptimized_with_optimize/args new file mode 100644 index 000000000000..cdfef46500af --- /dev/null +++ b/test/cmdlineTests/ir_unoptimized_with_optimize/args @@ -0,0 +1 @@ +--ir --optimize --debug-info none diff --git a/test/cmdlineTests/ir_unoptimized_with_optimize/input.sol b/test/cmdlineTests/ir_unoptimized_with_optimize/input.sol new file mode 100644 index 000000000000..a3a86cc8d317 --- /dev/null +++ b/test/cmdlineTests/ir_unoptimized_with_optimize/input.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity *; + +contract C {} diff --git a/test/cmdlineTests/ir_unoptimized_with_optimize/output b/test/cmdlineTests/ir_unoptimized_with_optimize/output new file mode 100644 index 000000000000..c5307123f0f5 --- /dev/null +++ b/test/cmdlineTests/ir_unoptimized_with_optimize/output @@ -0,0 +1,58 @@ +IR: + +/// @use-src 0:"ir_unoptimized_with_optimize/input.sol" +object "C_2" { + code { + + mstore(64, memoryguard(128)) + if callvalue() { revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() } + + constructor_C_2() + + let _1 := allocate_unbounded() + codecopy(_1, dataoffset("C_2_deployed"), datasize("C_2_deployed")) + + return(_1, datasize("C_2_deployed")) + + function allocate_unbounded() -> memPtr { + memPtr := mload(64) + } + + function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() { + revert(0, 0) + } + + function constructor_C_2() { + + } + + } + /// @use-src 0:"ir_unoptimized_with_optimize/input.sol" + object "C_2_deployed" { + code { + + mstore(64, memoryguard(128)) + + revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + function allocate_unbounded() -> memPtr { + memPtr := mload(64) + } + + function revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() { + revert(0, 0) + } + + } + + data ".metadata" hex"" + } + +} diff --git a/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/args b/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/args new file mode 100644 index 000000000000..c8c9fa85e744 --- /dev/null +++ b/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/args @@ -0,0 +1 @@ +--ir --optimize --via-ir --debug-info none diff --git a/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/input.sol b/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/input.sol new file mode 100644 index 000000000000..a3a86cc8d317 --- /dev/null +++ b/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/input.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity *; + +contract C {} diff --git a/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/output b/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/output new file mode 100644 index 000000000000..f5ecdd8bf5f0 --- /dev/null +++ b/test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/output @@ -0,0 +1,58 @@ +IR: + +/// @use-src 0:"ir_unoptimized_with_optimize_via_ir/input.sol" +object "C_2" { + code { + + mstore(64, memoryguard(128)) + if callvalue() { revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() } + + constructor_C_2() + + let _1 := allocate_unbounded() + codecopy(_1, dataoffset("C_2_deployed"), datasize("C_2_deployed")) + + return(_1, datasize("C_2_deployed")) + + function allocate_unbounded() -> memPtr { + memPtr := mload(64) + } + + function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() { + revert(0, 0) + } + + function constructor_C_2() { + + } + + } + /// @use-src 0:"ir_unoptimized_with_optimize_via_ir/input.sol" + object "C_2_deployed" { + code { + + mstore(64, memoryguard(128)) + + revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + function allocate_unbounded() -> memPtr { + memPtr := mload(64) + } + + function revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() { + revert(0, 0) + } + + } + + data ".metadata" hex"" + } + +} diff --git a/test/cmdlineTests/standard_ir_unoptimized_with_optimize/input.json b/test/cmdlineTests/standard_ir_unoptimized_with_optimize/input.json new file mode 100644 index 000000000000..d12265a3869e --- /dev/null +++ b/test/cmdlineTests/standard_ir_unoptimized_with_optimize/input.json @@ -0,0 +1,11 @@ +{ + "language": "Solidity", + "sources": { + "C.sol": {"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity *; contract C {}"} + }, + "settings": { + "outputSelection": {"*": {"*": ["ir"]}}, + "optimizer": {"enabled": true}, + "debug": {"debugInfo": []} + } +} diff --git a/test/cmdlineTests/standard_ir_unoptimized_with_optimize/output.json b/test/cmdlineTests/standard_ir_unoptimized_with_optimize/output.json new file mode 100644 index 000000000000..d197231268dd --- /dev/null +++ b/test/cmdlineTests/standard_ir_unoptimized_with_optimize/output.json @@ -0,0 +1,72 @@ +{ + "contracts": { + "C.sol": { + "C": { + "ir": " +/// @use-src 0:\"C.sol\" +object \"C_2\" { + code { + + mstore(64, memoryguard(128)) + if callvalue() { revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() } + + constructor_C_2() + + let _1 := allocate_unbounded() + codecopy(_1, dataoffset(\"C_2_deployed\"), datasize(\"C_2_deployed\")) + + return(_1, datasize(\"C_2_deployed\")) + + function allocate_unbounded() -> memPtr { + memPtr := mload(64) + } + + function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() { + revert(0, 0) + } + + function constructor_C_2() { + + } + + } + /// @use-src 0:\"C.sol\" + object \"C_2_deployed\" { + code { + + mstore(64, memoryguard(128)) + + revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + function allocate_unbounded() -> memPtr { + memPtr := mload(64) + } + + function revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() { + revert(0, 0) + } + + } + + data \".metadata\" hex\"\" + } + +} + +" + } + } + }, + "sources": { + "C.sol": { + "id": 0 + } + } +} diff --git a/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json b/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json new file mode 100644 index 000000000000..06b89d0e01bf --- /dev/null +++ b/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/input.json @@ -0,0 +1,12 @@ +{ + "language": "Solidity", + "sources": { + "C.sol": {"content": "// SPDX-License-Identifier: GPL-3.0\npragma solidity *; contract C {}"} + }, + "settings": { + "outputSelection": {"*": {"*": ["ir"]}}, + "optimizer": {"enabled": true}, + "viaIR": true, + "debug": {"debugInfo": []} + } +} diff --git a/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/output.json b/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/output.json new file mode 100644 index 000000000000..d197231268dd --- /dev/null +++ b/test/cmdlineTests/standard_ir_unoptimized_with_optimize_via_ir/output.json @@ -0,0 +1,72 @@ +{ + "contracts": { + "C.sol": { + "C": { + "ir": " +/// @use-src 0:\"C.sol\" +object \"C_2\" { + code { + + mstore(64, memoryguard(128)) + if callvalue() { revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() } + + constructor_C_2() + + let _1 := allocate_unbounded() + codecopy(_1, dataoffset(\"C_2_deployed\"), datasize(\"C_2_deployed\")) + + return(_1, datasize(\"C_2_deployed\")) + + function allocate_unbounded() -> memPtr { + memPtr := mload(64) + } + + function revert_error_ca66f745a3ce8ff40e2ccaf1ad45db7774001b90d25810abd9040049be7bf4bb() { + revert(0, 0) + } + + function constructor_C_2() { + + } + + } + /// @use-src 0:\"C.sol\" + object \"C_2_deployed\" { + code { + + mstore(64, memoryguard(128)) + + revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() + + function shift_right_224_unsigned(value) -> newValue { + newValue := + + shr(224, value) + + } + + function allocate_unbounded() -> memPtr { + memPtr := mload(64) + } + + function revert_error_42b3090547df1d2001c96683413b8cf91c1b902ef5e3cb8d9f6f304cf7446f74() { + revert(0, 0) + } + + } + + data \".metadata\" hex\"\" + } + +} + +" + } + } + }, + "sources": { + "C.sol": { + "id": 0 + } + } +}