Skip to content

Commit 61baed5

Browse files
authored
Merge pull request #16498 from argotorg/ssa_cfg_codegen
Add SSA Yul CFG codegen
2 parents c0225a0 + 436ccd5 commit 61baed5

26 files changed

Lines changed: 827 additions & 139 deletions

libsolidity/interface/CompilerStack.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,12 @@ void CompilerStack::setExperimental(bool _experimental)
235235
m_experimental = _experimental;
236236
}
237237

238+
void CompilerStack::setViaSSACFG(bool _viaSSACFG)
239+
{
240+
solAssert(m_stackState < CompilationSuccessful, "Must set SSA CFG codegen before compilation.");
241+
m_viaSSACFG = _viaSSACFG;
242+
}
243+
238244
void CompilerStack::setEVMVersion(langutil::EVMVersion _version)
239245
{
240246
solAssert(m_stackState < ParsedAndImported, "Must set EVM version before parsing.");
@@ -325,6 +331,7 @@ void CompilerStack::reset(bool _keepSettings)
325331
m_importRemapper.clear();
326332
m_libraries.clear();
327333
m_viaIR = false;
334+
m_viaSSACFG = false;
328335
m_evmVersion = langutil::EVMVersion();
329336
m_eofVersion.reset();
330337
m_modelCheckerSettings = ModelCheckerSettings{};
@@ -773,6 +780,9 @@ CompilerStack::PipelineConfig CompilerStack::requestedPipelineConfig(ContractDef
773780
bool CompilerStack::compile(State _stopAfter)
774781
{
775782
m_stopAfter = _stopAfter;
783+
784+
solAssert(!m_viaSSACFG || m_experimental, "SSA CFG code generation is an experimental feature. It requires experimental mode to be enabled.");
785+
776786
if (m_stackState < AnalysisSuccessful)
777787
if (!parseAndAnalyze(_stopAfter))
778788
return false;
@@ -1672,7 +1682,7 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
16721682

16731683
std::string deployedName = IRNames::deployedObject(_contract);
16741684
solAssert(!deployedName.empty(), "");
1675-
tie(compiledContract.evmAssembly, compiledContract.evmRuntimeAssembly) = stack.assembleEVMWithDeployed(deployedName);
1685+
tie(compiledContract.evmAssembly, compiledContract.evmRuntimeAssembly) = stack.assembleEVMWithDeployed(deployedName, m_viaSSACFG);
16761686

16771687
if (stack.hasErrors())
16781688
{

libsolidity/interface/CompilerStack.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
223223
/// Must be set before parsing.
224224
void setViaIR(bool _viaIR);
225225

226+
/// Sets the pipeline to use the SSA CFG code generator instead of OptimizedEVMCodeTransform.
227+
/// Must be set before compilation.
228+
void setViaSSACFG(bool _viaSSACFG);
229+
226230
/// Sets the experimental toggle to allow usage of experimental features.
227231
void setExperimental(bool _experimental);
228232

@@ -610,6 +614,7 @@ class CompilerStack: public langutil::CharStreamProvider, public evmasm::Abstrac
610614
RevertStrings m_revertStrings = RevertStrings::Default;
611615
State m_stopAfter = State::CompilationSuccessful;
612616
bool m_viaIR = false;
617+
bool m_viaSSACFG = false;
613618
bool m_experimental = false;
614619
langutil::EVMVersion m_evmVersion;
615620
std::optional<uint8_t> m_eofVersion;

libyul/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ add_library(yul
7474
backends/evm/VariableReferenceCounter.h
7575
backends/evm/VariableReferenceCounter.cpp
7676
backends/evm/ssa/BridgeFinder.h
77+
backends/evm/ssa/CodeTransform.cpp
78+
backends/evm/ssa/CodeTransform.h
7779
backends/evm/ssa/ControlFlow.cpp
7880
backends/evm/ssa/ControlFlow.h
7981
backends/evm/ssa/JunkAdmittingBlocksFinder.cpp

libyul/YulStack.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,9 @@ bool YulStack::analyzeParsed(Object& _object)
192192
return success;
193193
}
194194

195-
void YulStack::compileEVM(AbstractAssembly& _assembly, bool _optimize) const
195+
void YulStack::compileEVM(AbstractAssembly& _assembly, bool _optimize, bool _viaSSACFG) const
196196
{
197-
EVMObjectCompiler::compile(*m_parserResult, _assembly, _optimize);
197+
EVMObjectCompiler::compile(*m_parserResult, _assembly, _optimize, _viaSSACFG);
198198
}
199199

200200
void YulStack::reparse()
@@ -232,7 +232,7 @@ void YulStack::reparse()
232232
// locations and fewer warnings.
233233
}
234234

235-
MachineAssemblyObject YulStack::assemble(Machine _machine)
235+
MachineAssemblyObject YulStack::assemble(Machine _machine, bool _viaSSACFG)
236236
{
237237
yulAssert(m_stackState >= AnalysisSuccessful);
238238
yulAssert(m_parserResult, "");
@@ -242,17 +242,17 @@ MachineAssemblyObject YulStack::assemble(Machine _machine)
242242
switch (_machine)
243243
{
244244
case Machine::EVM:
245-
return assembleWithDeployed().first;
245+
return assembleWithDeployed({}, _viaSSACFG).first;
246246
}
247247
unreachable();
248248
}
249249

250250
std::pair<MachineAssemblyObject, MachineAssemblyObject>
251-
YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
251+
YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName, bool _viaSSACFG)
252252
{
253253
yulAssert(m_charStream);
254254

255-
auto [creationAssembly, deployedAssembly] = assembleEVMWithDeployed(_deployName);
255+
auto [creationAssembly, deployedAssembly] = assembleEVMWithDeployed(_deployName, _viaSSACFG);
256256
if (!creationAssembly)
257257
{
258258
yulAssert(!deployedAssembly);
@@ -307,7 +307,7 @@ YulStack::assembleWithDeployed(std::optional<std::string_view> _deployName)
307307
}
308308

309309
std::pair<std::shared_ptr<evmasm::Assembly>, std::shared_ptr<evmasm::Assembly>>
310-
YulStack::assembleEVMWithDeployed(std::optional<std::string_view> _deployName)
310+
YulStack::assembleEVMWithDeployed(std::optional<std::string_view> _deployName, bool _viaSSACFG)
311311
{
312312
yulAssert(m_stackState >= AnalysisSuccessful);
313313
yulAssert(m_parserResult, "");
@@ -326,7 +326,7 @@ YulStack::assembleEVMWithDeployed(std::optional<std::string_view> _deployName)
326326
);
327327
try
328328
{
329-
compileEVM(adapter, optimize);
329+
compileEVM(adapter, optimize, _viaSSACFG);
330330

331331
assembly.optimise(evmasm::Assembly::OptimiserSettings::translateSettings(m_optimiserSettings));
332332

libyul/YulStack.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,23 +118,25 @@ class YulStack: public langutil::CharStreamProvider
118118
void optimize();
119119

120120
/// Run the assembly step (should only be called after parseAndAnalyze).
121-
MachineAssemblyObject assemble(Machine _machine);
121+
MachineAssemblyObject assemble(Machine _machine, bool _viaSSACFG = false);
122122

123123
/// Run the assembly step (should only be called after parseAndAnalyze).
124124
/// In addition to the value returned by @a assemble, returns
125125
/// a second object that is the runtime code.
126126
/// Only available for EVM.
127127
std::pair<MachineAssemblyObject, MachineAssemblyObject>
128128
assembleWithDeployed(
129-
std::optional<std::string_view> _deployName = {}
129+
std::optional<std::string_view> _deployName = {},
130+
bool _viaSSACFG = false
130131
);
131132

132133
/// Run the assembly step (should only be called after parseAndAnalyze).
133134
/// Similar to @a assemblyWithDeployed, but returns EVM assembly objects.
134135
/// Only available for EVM.
135136
std::pair<std::shared_ptr<evmasm::Assembly>, std::shared_ptr<evmasm::Assembly>>
136137
assembleEVMWithDeployed(
137-
std::optional<std::string_view> _deployName = {}
138+
std::optional<std::string_view> _deployName = {},
139+
bool _viaSSACFG = false
138140
);
139141

140142
/// @returns the errors generated during parsing, analysis (and potentially assembly).
@@ -161,7 +163,7 @@ class YulStack: public langutil::CharStreamProvider
161163
bool analyzeParsed();
162164
bool analyzeParsed(yul::Object& _object);
163165

164-
void compileEVM(yul::AbstractAssembly& _assembly, bool _optimize) const;
166+
void compileEVM(yul::AbstractAssembly& _assembly, bool _optimize, bool _viaSSACFG) const;
165167

166168
/// Prints the Yul object stored internally and parses it again.
167169
/// This ensures that the debug info in the AST matches the source that printing would produce

libyul/backends/evm/EVMObjectCompiler.cpp

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121

2222
#include <libyul/backends/evm/EVMObjectCompiler.h>
2323

24+
#include <libyul/backends/evm/ssa/CodeTransform.h>
25+
#include <libyul/backends/evm/ssa/SSACFGBuilder.h>
26+
#include <libyul/backends/evm/ssa/ControlFlow.h>
27+
2428
#include <libyul/backends/evm/EVMCodeTransform.h>
2529
#include <libyul/backends/evm/EVMDialect.h>
2630
#include <libyul/backends/evm/OptimizedEVMCodeTransform.h>
@@ -37,14 +41,15 @@ using namespace solidity::yul;
3741
void EVMObjectCompiler::compile(
3842
Object const& _object,
3943
AbstractAssembly& _assembly,
40-
bool _optimize
44+
bool _optimize,
45+
bool _viaSSACFG
4146
)
4247
{
4348
EVMObjectCompiler compiler(_assembly);
44-
compiler.run(_object, _optimize);
49+
compiler.run(_object, _optimize, _viaSSACFG);
4550
}
4651

47-
void EVMObjectCompiler::run(Object const& _object, bool _optimize)
52+
void EVMObjectCompiler::run(Object const& _object, bool _optimize, bool _viaSSACFG)
4853
{
4954
yulAssert(_object.dialect());
5055
auto const* evmDialect = dynamic_cast<EVMDialect const*>(_object.dialect());
@@ -61,7 +66,7 @@ void EVMObjectCompiler::run(Object const& _object, bool _optimize)
6166
auto subAssemblyAndID = m_assembly.createSubAssembly(isCreation, subObject->name);
6267
context.subIDs[subObject->name] = subAssemblyAndID.second;
6368
subObject->subId = subAssemblyAndID.second;
64-
compile(*subObject, *subAssemblyAndID.first, _optimize);
69+
compile(*subObject, *subAssemblyAndID.first, _optimize, _viaSSACFG);
6570
}
6671
else
6772
{
@@ -82,33 +87,51 @@ void EVMObjectCompiler::run(Object const& _object, bool _optimize)
8287
}
8388
if (_optimize && evmDialect->evmVersion().canOverchargeGasForCall())
8489
{
85-
auto stackErrors = OptimizedEVMCodeTransform::run(
86-
m_assembly,
87-
*_object.analysisInfo,
88-
_object.code()->root(),
89-
*evmDialect,
90-
context,
91-
OptimizedEVMCodeTransform::UseNamedLabels::ForFirstFunctionOfEachName
92-
);
93-
if (!stackErrors.empty())
90+
if (_viaSSACFG)
9491
{
95-
yulAssert(evmDialect->providesObjectAccess());
96-
auto const memoryGuardHandle = evmDialect->findBuiltin("memoryguard");
97-
yulAssert(memoryGuardHandle, "Compiling with object access, memoryguard should be available as builtin.");
98-
std::vector<FunctionCall const*> memoryGuardCalls = findFunctionCalls(
92+
std::unique_ptr<ssa::ControlFlow> controlFlow = ssa::SSACFGBuilder::build(
93+
*_object.analysisInfo,
94+
*_object.dialect(),
9995
_object.code()->root(),
100-
*memoryGuardHandle
96+
false
10197
);
102-
auto stackError = stackErrors.front();
103-
std::string msg = stackError.comment() ? *stackError.comment() : "";
104-
if (memoryGuardCalls.empty())
105-
msg += "\nNo memoryguard was present. "
106-
"Consider using memory-safe assembly only and annotating it via "
107-
"'assembly (\"memory-safe\") { ... }'.";
108-
else
109-
msg += "\nmemoryguard was present.";
110-
stackError << util::errinfo_comment(msg);
111-
BOOST_THROW_EXCEPTION(stackError);
98+
ssa::ControlFlowLiveness const liveness(*controlFlow);
99+
ssa::CodeTransform::run(
100+
m_assembly,
101+
liveness,
102+
context
103+
);
104+
}
105+
else
106+
{
107+
auto stackErrors = OptimizedEVMCodeTransform::run(
108+
m_assembly,
109+
*_object.analysisInfo,
110+
_object.code()->root(),
111+
*evmDialect,
112+
context,
113+
OptimizedEVMCodeTransform::UseNamedLabels::ForFirstFunctionOfEachName
114+
);
115+
if (!stackErrors.empty())
116+
{
117+
yulAssert(evmDialect->providesObjectAccess());
118+
auto const memoryGuardHandle = evmDialect->findBuiltin("memoryguard");
119+
yulAssert(memoryGuardHandle, "Compiling with object access, memoryguard should be available as builtin.");
120+
std::vector<FunctionCall const*> memoryGuardCalls = findFunctionCalls(
121+
_object.code()->root(),
122+
*memoryGuardHandle
123+
);
124+
auto stackError = stackErrors.front();
125+
std::string msg = stackError.comment() ? *stackError.comment() : "";
126+
if (memoryGuardCalls.empty())
127+
msg += "\nNo memoryguard was present. "
128+
"Consider using memory-safe assembly only and annotating it via "
129+
"'assembly (\"memory-safe\") { ... }'.";
130+
else
131+
msg += "\nmemoryguard was present.";
132+
stackError << util::errinfo_comment(msg);
133+
BOOST_THROW_EXCEPTION(stackError);
134+
}
112135
}
113136
}
114137
else

libyul/backends/evm/EVMObjectCompiler.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@ class EVMObjectCompiler
3636
static void compile(
3737
Object const& _object,
3838
AbstractAssembly& _assembly,
39-
bool _optimize
39+
bool _optimize,
40+
bool _viaSSACFG = false
4041
);
4142
private:
4243
EVMObjectCompiler(AbstractAssembly& _assembly): m_assembly(_assembly) {}
4344

44-
void run(Object const& _object, bool _optimize);
45+
void run(Object const& _object, bool _optimize, bool _viaSSACFG);
4546

4647
AbstractAssembly& m_assembly;
4748
};

0 commit comments

Comments
 (0)