Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
28 changes: 21 additions & 7 deletions libsolidity/interface/CompilerStack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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, "");

Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
32 changes: 25 additions & 7 deletions libsolidity/interface/CompilerStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -387,8 +399,8 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
std::shared_ptr<evmasm::Assembly> 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<std::string const> metadata; ///< The metadata json that will be hashed into the chain.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -515,7 +533,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
ModelCheckerSettings m_modelCheckerSettings;
std::map<std::string, std::set<std::string>> m_requestedContractNames;
bool m_generateEvmBytecode = true;
bool m_generateIR = false;
IROutputSelection m_irOutputSelection = IROutputSelection::None;
std::map<std::string, util::h160> m_libraries;
ImportRemapper m_importRemapper;
std::map<std::string const, Source> m_sources;
Expand Down
27 changes: 14 additions & 13 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<size_t, std::string> const& linkReferences)
Expand Down Expand Up @@ -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);

Expand Down
15 changes: 8 additions & 7 deletions solc/CommandLineInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_ ||
Expand Down
1 change: 1 addition & 0 deletions test/cmdlineTests/ir_optimized_with_optimize/args
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--ir-optimized --optimize --debug-info none
4 changes: 4 additions & 0 deletions test/cmdlineTests/ir_optimized_with_optimize/input.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity *;

contract C {}
19 changes: 19 additions & 0 deletions test/cmdlineTests/ir_optimized_with_optimize/output
Original file line number Diff line number Diff line change
@@ -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"<BYTECODE REMOVED>"
}
}
1 change: 1 addition & 0 deletions test/cmdlineTests/ir_unoptimized_with_optimize/args
Copy link
Collaborator Author

@cameel cameel May 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that these tests do not really test much. I added them so that this unintuitive option combination at least gets used, but the change is not really visible in compiler output. We won't detect anything unless it just crashes.

The difference is only visible in execution time. Both of these should now finish almost instantly:

time solc test/benchmarks/chains.sol --ir --optimize
time solc test/benchmarks/chains.sol --ir --optimize --via-ir

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--ir --optimize --debug-info none
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I have another one with --ir-optimized --optimize --debug-info none, so that we can at least see the difference between outputs in this PR?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. Only for this case though because I don't think such a test is overly useful. --ir-optimized --optimize is the standard thing to do and the test is not checking its correctness anyway.

4 changes: 4 additions & 0 deletions test/cmdlineTests/ir_unoptimized_with_optimize/input.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity *;

contract C {}
58 changes: 58 additions & 0 deletions test/cmdlineTests/ir_unoptimized_with_optimize/output
Original file line number Diff line number Diff line change
@@ -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"<BYTECODE REMOVED>"
}

}
1 change: 1 addition & 0 deletions test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/args
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--ir --optimize --via-ir --debug-info none
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity *;

contract C {}
58 changes: 58 additions & 0 deletions test/cmdlineTests/ir_unoptimized_with_optimize_via_ir/output
Original file line number Diff line number Diff line change
@@ -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"<BYTECODE REMOVED>"
}

}
11 changes: 11 additions & 0 deletions test/cmdlineTests/standard_ir_unoptimized_with_optimize/input.json
Original file line number Diff line number Diff line change
@@ -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": []}
}
}
Loading