Skip to content

Commit af0afae

Browse files
committed
ssa cfg: remove FunctionCall yul AST pointers from (Builtin)Calls and drop ghost calls
1 parent 1bfa8dd commit af0afae

10 files changed

Lines changed: 54 additions & 71 deletions

File tree

libyul/backends/evm/ssa/CodeTransform.cpp

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ void CodeTransform::operator()(SSACFG::OperationId _opId, StackData const& _oper
208208

209209
if (hasReturnLabel)
210210
{
211-
auto const [it, inserted] = m_returnLabels.try_emplace(&std::get<SSACFG::Call>(_operation.kind).call.get(), 0);
211+
auto const [it, inserted] = m_returnLabels.try_emplace(_opId, 0);
212212
yulAssert(inserted, "Call sites should be unique.");
213213
it->second = m_assembly.newLabelId();
214214
}
@@ -244,7 +244,7 @@ void CodeTransform::operator()(SSACFG::OperationId _opId, StackData const& _oper
244244
yulAssert(std::holds_alternative<SSACFG::Call>(_operation.kind));
245245
yulAssert(
246246
returnLabelSlot.isFunctionCallReturnLabel() &&
247-
&m_callSites.functionCall(returnLabelSlot.functionCallReturnLabel()) == &std::get<SSACFG::Call>(_operation.kind).call.get()
247+
m_callSites.operationId(returnLabelSlot.functionCallReturnLabel()) == _opId
248248
);
249249
}
250250

@@ -262,14 +262,21 @@ void CodeTransform::operator()(SSACFG::OperationId _opId, StackData const& _oper
262262
std::visit(util::GenericVisitor{
263263
[&](SSACFG::BuiltinCall const& _builtin) {
264264
m_assembly.setSourceLocation(opOriginLocation);
265-
static_cast<BuiltinFunctionForEVM const&>(_builtin.builtin.get()).generateCode(
266-
_builtin.call,
267-
m_assembly,
268-
m_builtinContext
269-
);
265+
auto const& builtin = m_cfg.evmDialect.builtin(_builtin.builtin);
266+
// build up the call with transient args to handle literal arguments as needed
267+
std::vector<Expression> transientArgs;
268+
transientArgs.reserve(builtin.literalArguments.size());
269+
auto litIt = _builtin.literalArguments.begin();
270+
for (size_t i = 0; i < builtin.literalArguments.size(); ++i)
271+
if (builtin.literalArgument(i).has_value())
272+
transientArgs.emplace_back(*litIt++);
273+
else
274+
transientArgs.emplace_back(Identifier{});
275+
FunctionCall const transient{{}, BuiltinName{{}, _builtin.builtin}, std::move(transientArgs)};
276+
builtin.generateCode(transient, m_assembly, m_builtinContext);
270277
},
271278
[&](SSACFG::Call const& _call) {
272-
auto const* returnLabel = util::valueOrNullptr(m_returnLabels, &_call.call.get());
279+
auto const* returnLabel = util::valueOrNullptr(m_returnLabels, _opId);
273280
// check that if we have a return label, the call can continue
274281
yulAssert(!!returnLabel == _call.canContinue);
275282
m_assembly.setSourceLocation(opOriginLocation);
@@ -388,8 +395,8 @@ void CodeTransform::operator()(SSACFG::BlockId const& _blockId, SSACFG::BasicBlo
388395
auto const& block = m_cfg.block(_blockId);
389396
yulAssert(!block.operations.empty(), "Terminated block must have at least one operation.");
390397
std::visit(util::GenericVisitor{
391-
[](SSACFG::BuiltinCall const& _builtin) {
392-
yulAssert(_builtin.builtin.get().controlFlowSideEffects.terminatesOrReverts(), "Last operation of Terminated block must terminate or revert.");
398+
[&](SSACFG::BuiltinCall const& _builtin) {
399+
yulAssert(m_cfg.evmDialect.builtin(_builtin.builtin).controlFlowSideEffects.terminatesOrReverts(), "Last operation of Terminated block must terminate or revert.");
393400
},
394401
[](SSACFG::Call const& _call) {
395402
yulAssert(!_call.canContinue, "Last operation of Terminated block must be a non-continuable call.");

libyul/backends/evm/ssa/CodeTransform.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ struct AssemblyCallbacks
6666
}
6767
case StackSlot::Kind::FunctionCallReturnLabel:
6868
{
69-
auto const& call = callSites->functionCall(_slot.functionCallReturnLabel());
70-
yulAssert(returnLabels->count(&call), "FunctionCallReturnLabel not pre-registered before shuffle.");
71-
assembly->appendLabelReference(returnLabels->at(&call));
69+
auto const opId = callSites->operationId(_slot.functionCallReturnLabel());
70+
yulAssert(returnLabels->count(opId), "FunctionCallReturnLabel not pre-registered before shuffle.");
71+
assembly->appendLabelReference(returnLabels->at(opId));
7272
return;
7373
}
7474
case StackSlot::Kind::FunctionReturnLabel:
@@ -86,7 +86,7 @@ struct AssemblyCallbacks
8686
SSACFG const* cfg{};
8787
AbstractAssembly* assembly{};
8888
CallSites const* callSites{};
89-
std::map<FunctionCall const*, AbstractAssembly::LabelID> const* returnLabels{};
89+
std::map<SSACFG::OperationId, AbstractAssembly::LabelID> const* returnLabels{};
9090
};
9191
static_assert(StackManipulationCallbackConcept<AssemblyCallbacks>);
9292

@@ -141,7 +141,7 @@ class CodeTransform
141141
AssemblyCallbacks m_assemblyCallbacks;
142142
StackData m_stackData;
143143
Stack<AssemblyCallbacks> m_stack;
144-
std::map<FunctionCall const*, AbstractAssembly::LabelID> m_returnLabels;
144+
std::map<SSACFG::OperationId, AbstractAssembly::LabelID> m_returnLabels;
145145
};
146146

147147
}

libyul/backends/evm/ssa/SSACFG.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class SSACFGDotExporter: public io::DotExporterBase
111111
return fmt::format("func{}", _call.graphID);
112112
},
113113
[&](SSACFG::BuiltinCall const& _call) -> std::string {
114-
return _call.builtin.get().name;
114+
return m_cfg.evmDialect.builtin(_call.builtin).name;
115115
}
116116
}, operation.kind);
117117
if (!operation.outputs.empty())

libyul/backends/evm/ssa/SSACFG.h

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,6 @@
3434
#include <libsolutil/Numeric.h>
3535

3636
#include <concepts>
37-
#include <deque>
38-
#include <functional>
39-
#include <list>
4037
#include <string>
4138
#include <vector>
4239

@@ -70,13 +67,13 @@ class SSACFG
7067

7168
struct BuiltinCall
7269
{
73-
std::reference_wrapper<BuiltinFunction const> builtin;
74-
std::reference_wrapper<FunctionCall const> call;
70+
BuiltinHandle builtin;
71+
/// Literal-kind arguments
72+
std::vector<Literal> literalArguments;
7573
};
7674
struct Call
7775
{
7876
FunctionGraphID graphID;
79-
std::reference_wrapper<FunctionCall const> call;
8077
bool canContinue;
8178
};
8279

@@ -281,8 +278,6 @@ class SSACFG
281278
bool canContinue = true;
282279
std::vector<ValueId> arguments;
283280
std::size_t numReturns = 0;
284-
// Container for artificial calls generated for switch statements.
285-
std::list<FunctionCall> ghostCalls;
286281

287282
bool isMainGraph() const { return name.empty(); }
288283
};

libyul/backends/evm/ssa/SSACFGBuilder.cpp

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -234,18 +234,10 @@ void SSACFGBuilder::operator()(Switch const& _switch)
234234
yulAssert(equalityBuiltinHandle);
235235

236236
auto makeValueCompare = [&](Case const& _case) {
237-
FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(FunctionCall{
238-
debugDataOf(_case),
239-
BuiltinName{{}, *equalityBuiltinHandle},
240-
{*_case.value /* skip second argument */ }
241-
});
242237
auto outputValue = m_graph.newVariable(m_currentBlock);
243238
auto opId = m_graph.makeOperation(SSACFG::Operation{
244239
{outputValue},
245-
SSACFG::BuiltinCall{
246-
m_dialect.builtin(*equalityBuiltinHandle),
247-
ghostCall
248-
},
240+
SSACFG::BuiltinCall{*equalityBuiltinHandle, {}},
249241
{m_graph.newLiteral(debugDataOf(_case), _case.value->value.value()), expression}
250242
}, debugDataOf(_case));
251243
currentBlock().operations.emplace_back(opId);
@@ -452,7 +444,15 @@ std::vector<SSACFG::ValueId> SSACFGBuilder::visitFunctionCall(FunctionCall const
452444
[&](BuiltinName const& _builtinName)
453445
{
454446
auto const& builtin = m_dialect.builtin(_builtinName.handle);
455-
SSACFG::Operation result{{}, SSACFG::BuiltinCall{builtin, _call}, {}};
447+
yulAssert(_call.arguments.size() == builtin.numParameters);
448+
std::vector<Literal> literalArguments;
449+
for (auto&& [kind, arg]: ranges::views::zip(builtin.literalArguments, _call.arguments))
450+
if (kind.has_value())
451+
{
452+
yulAssert(std::holds_alternative<Literal>(arg));
453+
literalArguments.emplace_back(std::get<Literal>(arg));
454+
}
455+
SSACFG::Operation result{{}, SSACFG::BuiltinCall{_builtinName.handle, std::move(literalArguments)}, {}};
456456
for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse)
457457
if (!builtin.literalArgument(idx).has_value())
458458
result.inputs.emplace_back(std::visit(*this, arg));
@@ -470,7 +470,7 @@ std::vector<SSACFG::ValueId> SSACFGBuilder::visitFunctionCall(FunctionCall const
470470
canContinue = m_sideEffects.functionSideEffects().at(definition).canContinue;
471471
auto const calleeIt = m_functionScopeToID.find(&function);
472472
yulAssert(calleeIt != m_functionScopeToID.end(), "Called function has no registered graph id.");
473-
SSACFG::Operation result{{}, SSACFG::Call{calleeIt->second, _call, canContinue}, {}};
473+
SSACFG::Operation result{{}, SSACFG::Call{calleeIt->second, canContinue}, {}};
474474
for (auto const& arg: _call.arguments | ranges::views::reverse)
475475
result.inputs.emplace_back(std::visit(*this, arg));
476476
for (size_t i = 0; i < function.numReturns; ++i)

libyul/backends/evm/ssa/Stack.h

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,46 +27,41 @@
2727
#include <cstdint>
2828
#include <type_traits>
2929

30-
namespace solidity::yul
31-
{
32-
33-
struct FunctionCall;
34-
35-
namespace ssa
30+
namespace solidity::yul::ssa
3631
{
3732

3833
/// Registry for tracking function call sites.
3934
///
40-
/// Maps FunctionCall AST nodes to unique numeric IDs. These IDs are used
35+
/// Maps user-function-call operations to unique numeric IDs. These IDs are used
4136
/// to generate return labels for function calls in the EVM bytecode.
4237
class CallSites
4338
{
4439
public:
4540
using CallSiteID = std::uint32_t;
4641

47-
std::optional<CallSiteID> callSiteID(FunctionCall const* _functionCall) const
42+
std::optional<CallSiteID> callSiteID(OperationId _op) const
4843
{
49-
if (auto const it = ranges::find(m_data, _functionCall); it != m_data.end())
44+
if (auto const it = ranges::find(m_data, _op); it != m_data.end())
5045
return static_cast<CallSiteID>(std::distance(m_data.begin(), it));
5146
return std::nullopt;
5247
}
5348

54-
FunctionCall const& functionCall(CallSiteID _callSite) const
49+
OperationId operationId(CallSiteID _callSite) const
5550
{
5651
yulAssert(_callSite < m_data.size());
57-
return *m_data[_callSite];
52+
return m_data[_callSite];
5853
}
5954

60-
CallSiteID addCallSite(FunctionCall const* _functionCall)
55+
CallSiteID addCallSite(OperationId _op)
6156
{
62-
if (auto const id = callSiteID(_functionCall))
57+
if (auto const id = callSiteID(_op))
6358
return *id;
64-
yulAssert(_functionCall);
65-
m_data.emplace_back(_functionCall);
59+
yulAssert(_op.hasValue());
60+
m_data.emplace_back(_op);
6661
return static_cast<CallSiteID>(m_data.size() - 1);
6762
}
6863
private:
69-
std::vector<FunctionCall const*> m_data;
64+
std::vector<OperationId> m_data;
7065
};
7166

7267
/// A discriminated union corresponding to a single EVM stack slot.
@@ -309,4 +304,3 @@ class Stack
309304
};
310305

311306
}
312-
}

libyul/backends/evm/ssa/StackLayoutGenerator.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ void StackLayoutGenerator::visitBlock(SSACFG::BlockId const& _blockId)
239239
if (auto const* call = std::get_if<SSACFG::Call>(&operation.kind))
240240
if (call->canContinue)
241241
{
242-
auto const callSiteID = m_callSites.callSiteID(&call->call.get());
242+
auto const callSiteID = m_callSites.callSiteID(block.operations[operationIndex]);
243243
yulAssert(callSiteID.has_value());
244244
requiredStackTop.emplace_back(Slot::makeFunctionCallReturnLabel(*callSiteID));
245245
}

libyul/backends/evm/ssa/StackUtils.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ CallSites solidity::yul::ssa::gatherCallSites(SSACFG const& _cfg)
206206
for (auto const opId: block.operations)
207207
if (auto const* call = std::get_if<SSACFG::Call>(&_cfg.operation(opId).kind))
208208
if (call->canContinue)
209-
result.addCallSite(&call->call.get());
209+
result.addCallSite(opId);
210210
}
211211
return result;
212212
}

libyul/backends/evm/ssa/io/JSONExporter.cpp

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -54,26 +54,13 @@ Json toJson(Json& _ret, SSACFG const& _cfg, SSACFG::Operation const& _operation,
5454
[&](SSACFG::BuiltinCall const& _call) {
5555
_ret["type"] = "BuiltinCall";
5656
Json builtinArgsJson = Json::array();
57-
auto const& builtin = _call.builtin.get();
58-
if (!builtin.literalArguments.empty())
59-
{
60-
auto const& functionCallArgs = _call.call.get().arguments;
61-
for (size_t i = 0; i < builtin.literalArguments.size(); ++i)
62-
{
63-
std::optional<LiteralKind> const& argument = builtin.literalArguments[i];
64-
if (argument.has_value() && i < functionCallArgs.size())
65-
{
66-
// The function call argument at index i must be a literal if builtin.literalArguments[i] is not nullopt
67-
yulAssert(std::holds_alternative<Literal>(functionCallArgs[i]));
68-
builtinArgsJson.push_back(formatLiteral(std::get<Literal>(functionCallArgs[i])));
69-
}
70-
}
71-
}
57+
for (auto const& literal: _call.literalArguments)
58+
builtinArgsJson.push_back(formatLiteral(literal));
7259

7360
if (!builtinArgsJson.empty())
7461
opJson["literalArgs"] = builtinArgsJson;
7562

76-
opJson["op"] = _call.builtin.get().name;
63+
opJson["op"] = _cfg.evmDialect.builtin(_call.builtin).name;
7764
},
7865
}, _operation.kind);
7966

test/libyul/ssa/StackLayoutGeneratorTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class StackLayoutDotExporter: public io::DotExporterBase
9292
_out << escapeLabel(m_controlFlow.functionGraph(_call.graphID)->name);
9393
},
9494
[&](SSACFG::BuiltinCall const& _call) {
95-
_out << escapeLabel(_call.builtin.get().name);
95+
_out << escapeLabel(m_cfg.evmDialect.builtin(_call.builtin).name);
9696
}
9797
}, operation.kind);
9898
_out << "\\l\\\n";

0 commit comments

Comments
 (0)