Skip to content

Commit 47f859b

Browse files
authored
Merge pull request #16041 from ethereum/ssacfg_treat_literals_as_variables
Add option to treat literal assignments as operations in SSACFGs
2 parents 9f2ca6c + d790750 commit 47f859b

14 files changed

+400
-300
lines changed

libyul/YulControlFlowGraphExporter.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,20 @@ Json YulControlFlowGraphExporter::toJson(SSACFG const& _cfg, SSACFG::BlockId _bl
177177
Json YulControlFlowGraphExporter::toJson(Json& _ret, SSACFG const& _cfg, SSACFG::Operation const& _operation)
178178
{
179179
Json opJson = Json::object();
180-
std::visit(util::GenericVisitor{
181-
[&](SSACFG::Call const& _call) {
180+
std::visit(GenericVisitor{
181+
[&](SSACFG::Call const& _call)
182+
{
182183
_ret["type"] = "FunctionCall";
183184
opJson["op"] = _call.function.get().name.str();
184185
},
185-
[&](SSACFG::BuiltinCall const& _call) {
186+
[&](SSACFG::LiteralAssignment const&)
187+
{
188+
yulAssert(_operation.inputs.size() == 1);
189+
yulAssert(_cfg.isLiteralValue(_operation.inputs.back()));
190+
_ret["type"] = "LiteralAssignment";
191+
},
192+
[&](SSACFG::BuiltinCall const& _call)
193+
{
186194
_ret["type"] = "BuiltinCall";
187195
Json builtinArgsJson = Json::array();
188196
auto const& builtin = _call.builtin.get();

libyul/YulStack.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,11 +394,15 @@ Json YulStack::cfgJson() const
394394
yulAssert(m_parserResult->analysisInfo, "");
395395
// FIXME: we should not regenerate the cfg, but for now this is sufficient for testing purposes
396396
auto exportCFGFromObject = [&](Object const& _object) -> Json {
397+
// with this set to `true`, assignments of the type `let x := 42` are preserved and added as assignment
398+
// operations to the control flow graphs
399+
bool constexpr keepLiteralAssignments = true;
397400
// NOTE: The block Ids are reset for each object
398401
std::unique_ptr<ControlFlow> controlFlow = SSAControlFlowGraphBuilder::build(
399-
*_object.analysisInfo.get(),
402+
*_object.analysisInfo,
400403
languageToDialect(m_language, m_evmVersion, m_eofVersion),
401-
_object.code()->root()
404+
_object.code()->root(),
405+
keepLiteralAssignments
402406
);
403407
std::unique_ptr<ControlFlowLiveness> liveness = std::make_unique<ControlFlowLiveness>(*controlFlow);
404408
YulControlFlowGraphExporter exporter(*controlFlow, liveness.get());

libyul/backends/evm/SSAControlFlowGraph.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,28 @@ class SSACFGPrinter
168168
[&](SSACFG::BuiltinCall const& _call) {
169169
return _call.builtin.get().name;
170170
},
171+
[&](SSACFG::LiteralAssignment const&)
172+
{
173+
yulAssert(operation.inputs.size() == 1);
174+
return varToString(m_cfg, operation.inputs.back());
175+
}
171176
}, operation.kind);
172177
if (!operation.outputs.empty())
173178
m_result << fmt::format(
174179
"{} := ",
175180
fmt::join(operation.outputs | ranges::views::transform(valueToString), ", ")
176181
);
177-
m_result << fmt::format(
178-
"{}({})\\l\\\n",
179-
escape(label),
180-
fmt::join(operation.inputs | ranges::views::transform(valueToString), ", ")
181-
);
182+
if (std::holds_alternative<SSACFG::LiteralAssignment>(operation.kind))
183+
m_result << fmt::format(
184+
"{}\\l\\\n",
185+
escape(label)
186+
);
187+
else
188+
m_result << fmt::format(
189+
"{}({})\\l\\\n",
190+
escape(label),
191+
fmt::join(operation.inputs | ranges::views::transform(valueToString), ", ")
192+
);
182193
}
183194
m_result << "\"];\n";
184195
std::visit(GenericVisitor{

libyul/backends/evm/SSAControlFlowGraph.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,14 @@ class SSACFG
7878
std::reference_wrapper<FunctionCall const> call;
7979
bool canContinue;
8080
};
81+
struct LiteralAssignment
82+
{
83+
langutil::DebugData::ConstPtr debugData;
84+
};
8185

8286
struct Operation {
8387
std::vector<ValueId> outputs{};
84-
std::variant<BuiltinCall, Call> kind;
88+
std::variant<BuiltinCall, Call, LiteralAssignment> kind;
8589
std::vector<ValueId> inputs{};
8690
};
8791
struct BasicBlock
@@ -199,7 +203,7 @@ class SSACFG
199203
}
200204
ValueId newLiteral(langutil::DebugData::ConstPtr _debugData, u256 _value)
201205
{
202-
auto [it, inserted] = m_literals.emplace(_value, SSACFG::ValueId{m_valueInfos.size()});
206+
auto [it, inserted] = m_literals.emplace(_value, ValueId{m_valueInfos.size()});
203207
if (inserted)
204208
m_valueInfos.emplace_back(LiteralValue{std::move(_debugData), _value});
205209
else

libyul/backends/evm/SSAControlFlowGraphBuilder.cpp

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@
2020
*/
2121

2222
#include <libyul/backends/evm/SSAControlFlowGraphBuilder.h>
23-
#include <libyul/AST.h>
24-
#include <libyul/Exceptions.h>
23+
2524
#include <libyul/backends/evm/ControlFlow.h>
25+
#include <libyul/AST.h>
2626
#include <libyul/ControlFlowSideEffectsCollector.h>
27+
#include <libyul/Exceptions.h>
2728
#include <libyul/Utilities.h>
2829

2930
#include <libsolutil/Algorithms.h>
3031
#include <libsolutil/StringUtils.h>
3132
#include <libsolutil/Visitor.h>
3233

34+
#include <range/v3/algorithm/replace.hpp>
3335
#include <range/v3/range/conversion.hpp>
3436
#include <range/v3/view/drop_last.hpp>
3537
#include <range/v3/view/enumerate.hpp>
@@ -49,26 +51,29 @@ SSAControlFlowGraphBuilder::SSAControlFlowGraphBuilder(
4951
SSACFG& _graph,
5052
AsmAnalysisInfo const& _analysisInfo,
5153
ControlFlowSideEffectsCollector const& _sideEffects,
52-
Dialect const& _dialect
54+
Dialect const& _dialect,
55+
bool _keepLiteralAssignments
5356
):
5457
m_controlFlow(_controlFlow),
5558
m_graph(_graph),
5659
m_info(_analysisInfo),
5760
m_sideEffects(_sideEffects),
58-
m_dialect(_dialect)
61+
m_dialect(_dialect),
62+
m_keepLiteralAssignments(_keepLiteralAssignments)
5963
{
6064
}
6165

6266
std::unique_ptr<ControlFlow> SSAControlFlowGraphBuilder::build(
6367
AsmAnalysisInfo const& _analysisInfo,
6468
Dialect const& _dialect,
65-
Block const& _block
69+
Block const& _block,
70+
bool _keepLiteralAssignments
6671
)
6772
{
6873
ControlFlowSideEffectsCollector sideEffects(_dialect, _block);
6974

7075
auto controlFlow = std::make_unique<ControlFlow>();
71-
SSAControlFlowGraphBuilder builder(*controlFlow, *controlFlow->mainGraph, _analysisInfo, sideEffects, _dialect);
76+
SSAControlFlowGraphBuilder builder(*controlFlow, *controlFlow->mainGraph, _analysisInfo, sideEffects, _dialect, _keepLiteralAssignments);
7277
builder.m_currentBlock = controlFlow->mainGraph->makeBlock(debugDataOf(_block));
7378
builder.sealBlock(builder.m_currentBlock);
7479
builder(_block);
@@ -147,8 +152,8 @@ SSACFG::ValueId SSAControlFlowGraphBuilder::tryRemoveTrivialPhi(SSACFG::ValueId
147152
[](SSACFG::BasicBlock::Terminated&) {}
148153
}, block.exit);
149154
}
150-
for (auto& [_, currentVariableDefs]: m_currentDef)
151-
std::replace(currentVariableDefs.begin(), currentVariableDefs.end(), _phi, same);
155+
for (auto& currentVariableDefs: m_currentDef | ranges::views::values)
156+
ranges::replace(currentVariableDefs, _phi, same);
152157

153158
for (auto phiUse: phiUses)
154159
tryRemoveTrivialPhi(phiUse);
@@ -178,11 +183,6 @@ void SSAControlFlowGraphBuilder::cleanUnreachable()
178183
}, block.exit);
179184
});
180185

181-
auto isUnreachableValue = [&](SSACFG::ValueId const& _value) -> bool {
182-
auto* valueInfo = std::get_if<SSACFG::UnreachableValue>(&m_graph.valueInfo(_value));
183-
return (valueInfo) ? true : false;
184-
};
185-
186186
// Remove all entries from unreachable nodes from the graph.
187187
for (SSACFG::BlockId blockId: reachabilityCheck.visited)
188188
{
@@ -197,7 +197,7 @@ void SSAControlFlowGraphBuilder::cleanUnreachable()
197197
for (auto phi: block.phis)
198198
if (auto* phiInfo = std::get_if<SSACFG::PhiValue>(&m_graph.valueInfo(phi)))
199199
std::erase_if(phiInfo->arguments, [&](SSACFG::ValueId _arg) {
200-
if (isUnreachableValue(_arg))
200+
if (std::holds_alternative<SSACFG::UnreachableValue>(m_graph.valueInfo(_arg)))
201201
{
202202
maybeTrivialPhi.insert(phi);
203203
return true;
@@ -240,7 +240,7 @@ void SSAControlFlowGraphBuilder::buildFunctionGraph(
240240
cfg.arguments = arguments;
241241
cfg.returns = returns;
242242

243-
SSAControlFlowGraphBuilder builder(m_controlFlow, cfg, m_info, m_sideEffects, m_dialect);
243+
SSAControlFlowGraphBuilder builder(m_controlFlow, cfg, m_info, m_sideEffects, m_dialect, m_keepLiteralAssignments);
244244
builder.m_currentBlock = cfg.entry;
245245
builder.m_functionDefinitions = m_functionDefinitions;
246246
for (auto&& [var, varId]: cfg.arguments)
@@ -273,6 +273,11 @@ void SSAControlFlowGraphBuilder::operator()(Assignment const& _assignment)
273273

274274
void SSAControlFlowGraphBuilder::operator()(VariableDeclaration const& _variableDeclaration)
275275
{
276+
// if we have no value (like in `let a` without right-hand side), we can just skip this. the variable(s) will be
277+
// added when first needed
278+
if (!_variableDeclaration.value)
279+
return;
280+
276281
assign(
277282
_variableDeclaration.variables | ranges::views::transform([&](auto& _var) { return std::ref(lookupVariable(_var.name)); }) | ranges::to<std::vector>,
278283
_variableDeclaration.value.get()
@@ -533,15 +538,27 @@ void SSAControlFlowGraphBuilder::assign(std::vector<std::reference_wrapper<Scope
533538
auto rhs = [&]() -> std::vector<SSACFG::ValueId> {
534539
if (auto const* functionCall = std::get_if<FunctionCall>(_expression))
535540
return visitFunctionCall(*functionCall);
536-
else if (_expression)
541+
if (_expression)
537542
return {std::visit(*this, *_expression)};
538-
else
539-
return {_variables.size(), zero()};
543+
return {_variables.size(), zero()};
540544
}();
541545
yulAssert(rhs.size() == _variables.size());
542546

543547
for (auto const& [var, value]: ranges::zip_view(_variables, rhs))
544-
writeVariable(var, m_currentBlock, value);
548+
{
549+
if (m_keepLiteralAssignments && m_graph.isLiteralValue(value))
550+
{
551+
SSACFG::Operation assignment{
552+
.outputs = {m_graph.newVariable(m_currentBlock)},
553+
.kind = SSACFG::LiteralAssignment{},
554+
.inputs = {value}
555+
};
556+
currentBlock().operations.emplace_back(assignment);
557+
writeVariable(var, m_currentBlock, assignment.outputs.back());
558+
}
559+
else
560+
writeVariable(var, m_currentBlock, value);
561+
}
545562

546563
}
547564

@@ -610,7 +627,7 @@ SSACFG::ValueId SSAControlFlowGraphBuilder::readVariableRecursive(Scope::Variabl
610627
// incomplete block
611628
val = m_graph.newPhi(_block);
612629
block.phis.insert(val);
613-
info.incompletePhis.emplace_back(val, std::ref(_variable));
630+
info.incompletePhis.emplace_back(val, _variable);
614631
}
615632
else if (block.entries.size() == 1)
616633
// one predecessor: no phi needed
@@ -633,7 +650,7 @@ SSACFG::ValueId SSAControlFlowGraphBuilder::addPhiOperands(Scope::Variable const
633650
{
634651
yulAssert(std::holds_alternative<SSACFG::PhiValue>(m_graph.valueInfo(_phi)));
635652
auto& phi = std::get<SSACFG::PhiValue>(m_graph.valueInfo(_phi));
636-
for (auto pred: m_graph.block(phi.block).entries)
653+
for (auto const& pred: m_graph.block(phi.block).entries)
637654
phi.arguments.emplace_back(readVariable(_variable, pred));
638655
// we call tryRemoveTrivialPhi explicitly to avoid removing trivial phis in unsealed blocks
639656
return _phi;
@@ -660,14 +677,14 @@ Scope::Variable const& SSAControlFlowGraphBuilder::lookupVariable(YulName _name)
660677
yulAssert(m_scope, "");
661678
Scope::Variable const* var = nullptr;
662679
if (m_scope->lookup(_name, util::GenericVisitor{
663-
[&](Scope::Variable& _var) { var = &_var; },
664-
[](Scope::Function&)
680+
[&](Scope::Variable const& _var) { var = &_var; },
681+
[](Scope::Function const&)
665682
{
666683
yulAssert(false, "Function not removed during desugaring.");
667684
}
668685
}))
669686
{
670-
yulAssert(var, "");
687+
yulAssert(var);
671688
return *var;
672689
};
673690
yulAssert(false, "External identifier access unimplemented.");

libyul/backends/evm/SSAControlFlowGraphBuilder.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,17 @@ class SSAControlFlowGraphBuilder
4444
SSACFG& _graph,
4545
AsmAnalysisInfo const& _analysisInfo,
4646
ControlFlowSideEffectsCollector const& _sideEffects,
47-
Dialect const& _dialect
47+
Dialect const& _dialect,
48+
bool _keepLiteralAssignments
4849
);
4950
public:
5051
SSAControlFlowGraphBuilder(SSAControlFlowGraphBuilder const&) = delete;
5152
SSAControlFlowGraphBuilder& operator=(SSAControlFlowGraphBuilder const&) = delete;
5253
static std::unique_ptr<ControlFlow> build(
5354
AsmAnalysisInfo const& _analysisInfo,
5455
Dialect const& _dialect,
55-
Block const& _block
56+
Block const& _block,
57+
bool _keepLiteralAssignments
5658
);
5759

5860
void operator()(ExpressionStatement const& _statement);
@@ -92,6 +94,7 @@ class SSAControlFlowGraphBuilder
9294
AsmAnalysisInfo const& m_info;
9395
ControlFlowSideEffectsCollector const& m_sideEffects;
9496
Dialect const& m_dialect;
97+
bool const m_keepLiteralAssignments;
9598
std::vector<std::tuple<Scope::Function const*, FunctionDefinition const*>> m_functionDefinitions;
9699
SSACFG::BlockId m_currentBlock;
97100
SSACFG::BasicBlock& currentBlock() { return m_graph.block(m_currentBlock); }

0 commit comments

Comments
 (0)