Skip to content

Commit 9cdc2bc

Browse files
authored
Merge pull request #16668 from argotorg/ssa-cfg-extracts
SSA CFG: introduce projections for functions with multiple return values
2 parents 68d1a77 + b0ff7b9 commit 9cdc2bc

38 files changed

Lines changed: 890 additions & 773 deletions

libyul/backends/evm/ssa/CodeTransform.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ CodeTransform::CodeTransform(
161161
expectedStackTop.reserve(m_cfg.arguments.size() + (isFunctionGraph && m_cfg.canContinue ? 1 : 0));
162162
if (isFunctionGraph && m_cfg.canContinue)
163163
expectedStackTop.push_back(StackSlot::makeFunctionReturnLabel(m_graphID));
164-
for (auto const& valueID: m_cfg.arguments | ranges::views::reverse)
165-
expectedStackTop.push_back(StackSlot::makeValueID(_cfg, valueID));
164+
for (auto const& arg: m_cfg.arguments | ranges::views::reverse)
165+
expectedStackTop.push_back(StackSlot::makeValue(_cfg, arg));
166166
assertLayoutCompatibility(m_stack.data(), expectedStackTop);
167167
}
168168

@@ -234,7 +234,7 @@ void CodeTransform::operator()(InstId _instId, StackData const& _operationInputL
234234
m_stack | ranges::views::take_last(_inst.inputs.size()),
235235
_inst.inputs
236236
))
237-
yulAssert(stackEntry.isValueID() && stackEntry.valueID() == input);
237+
yulAssert(stackEntry.isValue() && stackEntry.value() == input);
238238

239239
// if the function can continue (doesn't always abort), make sure we have the correct return label slot in place
240240
if (hasReturnLabel)
@@ -309,16 +309,16 @@ void CodeTransform::operator()(InstId _instId, StackData const& _operationInputL
309309
for (size_t i = 0; i < _inst.inputs.size(); ++i)
310310
m_stack.pop<false>();
311311
// simulate that the outputs are produced
312-
for (auto value: SSACFG::outputsOf(_instId, _inst.numOutputs))
313-
m_stack.push<false>(StackSlot::makeValueID(m_cfg, value));
312+
auto const numOutputs = m_cfg.numReturnsOf(_instId);
313+
for (InstId const id: m_cfg.outputsOf(_instId))
314+
m_stack.push<false>(StackSlot::makeValue(m_cfg, id));
314315

315-
// Assert that the operation produced its proclaimed output.
316-
yulAssert(m_stack.size() == baseHeight + _inst.numOutputs);
316+
yulAssert(m_stack.size() == baseHeight + numOutputs);
317317
for (auto const& [stackEntry, output]: ranges::views::zip(
318-
m_stack.data() | ranges::views::take_last(_inst.numOutputs),
319-
SSACFG::outputsOf(_instId, _inst.numOutputs)
318+
m_stack.data() | ranges::views::take_last(numOutputs),
319+
m_cfg.outputsOf(_instId)
320320
))
321-
yulAssert(stackEntry.isValueID() && stackEntry.valueID() == output);
321+
yulAssert(stackEntry.isValue() && stackEntry.value() == output);
322322
yulAssert(
323323
static_cast<int>(m_stack.size()) == m_assembly.stackHeight(),
324324
fmt::format("symbolic stack size = {} =/= {} = assembly stack height", m_stack.size(), m_assembly.stackHeight())
@@ -335,7 +335,7 @@ void CodeTransform::operator()(SSACFG::BlockId const& _currentBlock, SSACFG::Bas
335335
{
336336
yulAssert(static_cast<int>(m_stack.size()) == m_assembly.stackHeight());
337337
// condition must be at the top of the stack
338-
yulAssert(m_stack.top().isValueID() && m_stack.top().valueID() == _conditionalJump.condition);
338+
yulAssert(m_stack.top().isValue() && m_stack.top().value() == _conditionalJump.condition);
339339
// emit JUMPI to nonZero block
340340
m_assembly.appendJumpToIf(m_blockLabels[_conditionalJump.nonZero.value]);
341341
// update symbolic stack by popping the condition as it'll be consumed by JUMPI
@@ -391,8 +391,8 @@ void CodeTransform::operator()(SSACFG::BlockId const&, SSACFG::BasicBlock::Funct
391391
for (std::size_t i = 0; i < _functionReturn.returnValues.size(); ++i)
392392
{
393393
auto const& returnValueSlot = m_stack.slot(StackOffset{i});
394-
yulAssert(returnValueSlot.isValueID());
395-
yulAssert(returnValueSlot.valueID() == _functionReturn.returnValues[i]);
394+
yulAssert(returnValueSlot.isValue());
395+
yulAssert(returnValueSlot.value() == _functionReturn.returnValues[i]);
396396
}
397397
m_assembly.appendJump(0, AbstractAssembly::JumpType::OutOfFunction);
398398
}

libyul/backends/evm/ssa/CodeTransform.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ struct AssemblyCallbacks
4949
{
5050
switch (_slot.kind())
5151
{
52-
case StackSlot::Kind::ValueID:
52+
case StackSlot::Kind::Value:
5353
{
54-
auto const id = _slot.valueID();
54+
auto const id = _slot.value();
5555
yulAssert(cfg->isLiteral(id), fmt::format("Tried bringing up non-const {}", id));
56-
assembly->appendConstant(cfg->literalPayload(id.instId()));
56+
assembly->appendConstant(cfg->literalPayload(id));
5757
return;
5858
}
5959
case StackSlot::Kind::Junk:

libyul/backends/evm/ssa/InstructionStore.h

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@
3131

3232
#include <libsolutil/Numeric.h>
3333

34-
#include <range/v3/view/iota.hpp>
35-
#include <range/v3/view/transform.hpp>
36-
3734
#include <cstdint>
3835
#include <limits>
3936
#include <map>
@@ -48,6 +45,8 @@ class InstructionStore
4845
/// Sentinel value for `Inst::payloadIndex` when the opcode has no side-table payload.
4946
static constexpr std::uint32_t NoPayload = std::numeric_limits<std::uint32_t>::max();
5047

48+
using NumReturnsSizeType = std::uint8_t;
49+
5150
struct BuiltinCall
5251
{
5352
BuiltinHandle builtin;
@@ -58,20 +57,21 @@ class InstructionStore
5857
{
5958
FunctionGraphID graphID;
6059
bool canContinue;
60+
std::size_t numReturns;
6161
};
6262
struct Inst
6363
{
6464
InstOpcode opcode;
65-
ValueId::OutputSize numOutputs;
6665
std::uint32_t payloadIndex = NoPayload;
6766
BlockId block{};
68-
std::vector<ValueId> inputs{};
67+
std::vector<InstId> inputs{};
6968

7069
constexpr bool isPhi() const noexcept { return opcode == InstOpcode::Phi; }
7170
constexpr bool isUpsilon() const noexcept { return opcode == InstOpcode::Upsilon; }
7271
constexpr bool isLiteral() const noexcept { return opcode == InstOpcode::Const; }
7372
constexpr bool isUnreachable() const noexcept { return opcode == InstOpcode::Unreachable; }
7473
constexpr bool isFunctionArg() const noexcept { return opcode == InstOpcode::FunctionArg; }
74+
constexpr bool isProjection() const noexcept { return opcode == InstOpcode::Projection; }
7575
/// Operation = a Call or BuiltinCall.
7676
constexpr bool isOperation() const noexcept
7777
{
@@ -91,23 +91,11 @@ class InstructionStore
9191
std::size_t numInsts() const { return m_insts.size(); }
9292
std::vector<Inst> const& instructions() const { return m_insts; }
9393

94-
static auto outputsOf(InstId const _id, ValueId::OutputSize const _numOutputs)
95-
{
96-
return
97-
ranges::views::iota(ValueId::OutputSize{0}, _numOutputs) |
98-
ranges::views::transform([_id](ValueId::OutputSize const _pos) { return ValueId{_id, _pos}; });
99-
}
100-
101-
auto instOutputs(InstId const _id) const
102-
{
103-
return outputsOf(_id, inst(_id).numOutputs);
104-
}
105-
106-
/// Returns the opcode category for a given ValueId.
107-
InstOpcode kindOf(ValueId const _v) const { return inst(_v.instId()).opcode; }
94+
/// Returns the opcode category for a given InstId.
95+
InstOpcode kindOf(InstId const _id) const { return inst(_id).opcode; }
10896

10997
/// Returns the phi targeted by an Upsilon Inst.
110-
ValueId upsilonPhi(InstId const _id) const { return payloadAs<InstOpcode::Upsilon>(_id, m_upsilonPhis); }
98+
InstId upsilonPhi(InstId const _id) const { return payloadAs<InstOpcode::Upsilon>(_id, m_upsilonPhis); }
11199

112100
/// Returns the u256 payload of a Const Inst.
113101
u256 const& literalPayload(InstId const _id) const { return payloadAs<InstOpcode::Const>(_id, m_literalPayloads); }
@@ -116,22 +104,35 @@ class InstructionStore
116104

117105
Call const& callPayload(InstId const _id) const { return payloadAs<InstOpcode::Call>(_id, m_callPayloads); }
118106

107+
/// Returns the projection index of a Projection Inst
108+
NumReturnsSizeType projectionIndex(InstId const _id) const
109+
{
110+
Inst const& projectionInst = inst(_id);
111+
yulAssert(projectionInst.opcode == InstOpcode::Projection);
112+
yulAssert(projectionInst.inputs.size() == 1);
113+
InstId const producer = projectionInst.inputs.front();
114+
yulAssert(_id.value > producer.value);
115+
std::size_t const offset = static_cast<std::size_t>(_id.value) - static_cast<std::size_t>(producer.value) - 1;
116+
yulAssert(offset <= std::numeric_limits<NumReturnsSizeType>::max());
117+
return static_cast<NumReturnsSizeType>(offset);
118+
}
119+
119120
/// Allocates a new Phi Inst defined in `_definingBlock`. Returns the new InstId.
120121
InstId appendPhi(BlockId const _definingBlock)
121122
{
122-
return appendInst(Inst{InstOpcode::Phi, 1, NoPayload, _definingBlock, {}});
123+
return appendInst(Inst{InstOpcode::Phi, NoPayload, _definingBlock, {}});
123124
}
124125

125126
/// Allocates a new FunctionArg Inst defined in `_entryBlock`. Returns the new InstId.
126127
InstId appendFunctionArg(BlockId const _entryBlock)
127128
{
128-
return appendInst(Inst{InstOpcode::FunctionArg, 1, NoPayload, _entryBlock, {}});
129+
return appendInst(Inst{InstOpcode::FunctionArg, NoPayload, _entryBlock, {}});
129130
}
130131

131132
/// Allocates a new Unreachable Inst. Not pinned to any block (see class comment).
132133
InstId appendUnreachable()
133134
{
134-
return appendInst(Inst{InstOpcode::Unreachable, 1, NoPayload, BlockId{}, {}});
135+
return appendInst(Inst{InstOpcode::Unreachable, NoPayload, BlockId{}, {}});
135136
}
136137

137138
/// Allocates a new Const Inst with payload `_value`, pinned to `_entryBlock`.
@@ -142,7 +143,7 @@ class InstructionStore
142143
if (auto const it = m_literalDedup.find(_value); it != m_literalDedup.end())
143144
return it->second;
144145
auto const payloadIdx = allocPayload(m_literalPayloads, _value);
145-
InstId const id = appendInst(Inst{InstOpcode::Const, 1, payloadIdx, _entryBlock, {}});
146+
InstId const id = appendInst(Inst{InstOpcode::Const, payloadIdx, _entryBlock, {}});
146147
m_literalDedup.emplace(std::move(_value), id);
147148
return id;
148149
}
@@ -151,15 +152,12 @@ class InstructionStore
151152
InstId appendBuiltinCall(
152153
BlockId const _block,
153154
BuiltinCall _payload,
154-
std::vector<ValueId> _inputs,
155-
std::size_t const _numOutputs
155+
std::vector<InstId> _inputs
156156
)
157157
{
158158
yulAssert(_block.hasValue());
159-
yulAssert(_numOutputs <= std::numeric_limits<ValueId::OutputSize>::max());
160159
return appendInst(Inst{
161160
InstOpcode::BuiltinCall,
162-
static_cast<ValueId::OutputSize>(_numOutputs),
163161
allocPayload(m_builtinPayloads, std::move(_payload)),
164162
_block,
165163
std::move(_inputs)
@@ -170,26 +168,31 @@ class InstructionStore
170168
InstId appendCall(
171169
BlockId const _block,
172170
Call _payload,
173-
std::vector<ValueId> _inputs,
174-
std::size_t const _numOutputs
171+
std::vector<InstId> _inputs
175172
)
176173
{
177174
yulAssert(_block.hasValue());
178-
yulAssert(_numOutputs <= std::numeric_limits<ValueId::OutputSize>::max());
179175
return appendInst(Inst{
180176
InstOpcode::Call,
181-
static_cast<ValueId::OutputSize>(_numOutputs),
182177
allocPayload(m_callPayloads, std::move(_payload)),
183178
_block,
184179
std::move(_inputs)
185180
});
186181
}
187182

183+
/// Allocates a new Projection Inst projecting output `_index` of `_producer`.
184+
InstId appendProjection(BlockId const _block, InstId const _producer)
185+
{
186+
yulAssert(_block.hasValue());
187+
yulAssert(_producer.hasValue());
188+
return appendInst(Inst{InstOpcode::Projection, NoPayload, _block, {_producer}});
189+
}
190+
188191
/// Allocates a new Upsilon Inst targeting `_phi`, feeding `_value` from `_block`.
189-
InstId appendUpsilon(BlockId const _block, ValueId const _value, ValueId const _phi)
192+
InstId appendUpsilon(BlockId const _block, InstId const _value, InstId const _phi)
190193
{
191-
yulAssert(inst(_phi.instId()).isPhi());
192-
return appendInst(Inst{InstOpcode::Upsilon, 0, allocPayload(m_upsilonPhis, _phi), _block, {_value}});
194+
yulAssert(inst(_phi).isPhi());
195+
return appendInst(Inst{InstOpcode::Upsilon, allocPayload(m_upsilonPhis, _phi), _block, {_value}});
193196
}
194197

195198
private:
@@ -225,7 +228,7 @@ class InstructionStore
225228
std::vector<Inst> m_insts;
226229
std::vector<u256> m_literalPayloads;
227230
std::map<u256, InstId> m_literalDedup;
228-
std::vector<ValueId> m_upsilonPhis;
231+
std::vector<InstId> m_upsilonPhis;
229232
std::vector<BuiltinCall> m_builtinPayloads;
230233
std::vector<Call> m_callPayloads;
231234
};

libyul/backends/evm/ssa/LivenessAnalysis.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <range/v3/algorithm/count_if.hpp>
2424
#include <range/v3/range/conversion.hpp>
2525

26+
#include <range/v3/view/enumerate.hpp>
2627
#include <range/v3/view/filter.hpp>
2728
#include <range/v3/view/reverse.hpp>
2829

@@ -83,7 +84,7 @@ void LivenessAnalysis::runDagDfs()
8384
// live <- PhiUses(B)
8485
LivenessData live{};
8586
m_cfg.forEachUpsilon(block, [&](InstId, SSACFG::Inst const& inst) {
86-
SSACFG::ValueId const v = inst.inputs.at(0);
87+
InstId const v = inst.inputs.at(0);
8788
yulAssert(!m_cfg.isUnreachable(v));
8889
if (!m_cfg.isLiteral(v))
8990
live.insert(v);
@@ -97,7 +98,7 @@ void LivenessAnalysis::runDagDfs()
9798
// LiveIn(S) - PhiDefs(S)
9899
auto liveInWithoutPhiDefs = m_liveIns[_successor.value];
99100
m_cfg.forEachPhi(m_cfg.block(_successor), [&](InstId const succInstId, SSACFG::Inst const&) {
100-
liveInWithoutPhiDefs.erase(ValueId{succInstId});
101+
liveInWithoutPhiDefs.erase(succInstId);
101102
});
102103
live.maxUnion(liveInWithoutPhiDefs);
103104
}
@@ -123,15 +124,15 @@ void LivenessAnalysis::runDagDfs()
123124
if (!inst.isOperation())
124125
continue;
125126
// remove variables defined at p from live
126-
live.eraseAll(SSACFG::outputsOf(instId, inst.numOutputs) | ranges::views::filter(excludingLiteralsFilter()));
127-
// add uses at p to live
127+
live.eraseAll(m_cfg.projectionsOf(instId));
128+
live.erase(instId);
128129
live.insertAll(inst.inputs | ranges::views::filter(excludingLiteralsFilter()));
129130
}
130131
}
131132

132133
// livein(b) <- live \cup PhiDefs(B)
133134
m_cfg.forEachPhi(block, [&](InstId const instId, SSACFG::Inst const&) {
134-
live.insert(ValueId{instId});
135+
live.insert(instId);
135136
});
136137
m_liveIns[blockId.value] = live;
137138
}
@@ -147,7 +148,7 @@ void LivenessAnalysis::runLoopTreeDfs(SSACFG::BlockId::ValueType const _loopHead
147148
// LiveLoop <- LiveIn(B_N) - PhiDefs(B_N)
148149
auto liveLoop = m_liveIns[_loopHeader];
149150
m_cfg.forEachPhi(block, [&](InstId const instId, SSACFG::Inst const&) {
150-
liveLoop.erase(ValueId{instId});
151+
liveLoop.erase(instId);
151152
});
152153
// must be live out of header if live in of children
153154
m_liveOuts[_loopHeader].maxUnion(liveLoop);
@@ -186,7 +187,8 @@ void LivenessAnalysis::fillOperationsLiveOut()
186187
if (!inst.isOperation())
187188
continue;
188189
*rit = live;
189-
live.eraseAll(SSACFG::outputsOf(instId, inst.numOutputs) | ranges::views::filter(excludingLiteralsFilter()));
190+
live.eraseAll(m_cfg.projectionsOf(instId));
191+
live.erase(instId);
190192
live.insertAll(inst.inputs | ranges::views::filter(excludingLiteralsFilter()));
191193
++rit;
192194
}

libyul/backends/evm/ssa/LivenessAnalysis.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class LivenessAnalysis
3636
public:
3737
/// Per-program-point liveness, each value's use count is the max number of times the value will be read along
3838
/// all paths downstream of that point
39-
using LivenessData = util::UseCountSet<SSACFG::ValueId>;
39+
using LivenessData = util::UseCountSet<InstId>;
4040

4141
explicit LivenessAnalysis(SSACFG const& _cfg);
4242

@@ -55,7 +55,7 @@ class LivenessAnalysis
5555

5656
auto excludingLiteralsFilter() const
5757
{
58-
return [this](SSACFG::ValueId _v) { return !m_cfg.isLiteral(_v); };
58+
return [this](InstId _v) { return !m_cfg.isLiteral(_v); };
5959
}
6060

6161
SSACFG const& m_cfg;

libyul/backends/evm/ssa/PhiInverse.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ PhiInverse::PhiInverse(SSACFG const& _cfg, SSACFG::BlockId const& _from, SSACFG:
2424
{
2525
_cfg.forEachUpsilon(_cfg.block(_from), [&](InstId const instId, SSACFG::Inst const& inst) {
2626
if (
27-
ValueId const phi = _cfg.upsilonPhi(instId);
28-
_cfg.inst(phi.instId()).block == _to
27+
InstId const phi = _cfg.upsilonPhi(instId);
28+
_cfg.inst(phi).block == _to
2929
)
3030
m_phiToPreImage[phi] = inst.inputs.at(0);
3131
});
@@ -36,12 +36,12 @@ bool PhiInverse::noOp() const
3636
return m_phiToPreImage.empty();
3737
}
3838

39-
SSACFG::ValueId PhiInverse::operator()(SSACFG::ValueId _valueId) const
39+
InstId PhiInverse::operator()(InstId _valueId) const
4040
{
4141
return solidity::util::valueOrDefault(m_phiToPreImage, _valueId, _valueId);
4242
}
4343

44-
std::map<SSACFG::ValueId, SSACFG::ValueId> const& PhiInverse::data() const
44+
std::map<InstId, InstId> const& PhiInverse::data() const
4545
{
4646
return m_phiToPreImage;
4747
}

libyul/backends/evm/ssa/PhiInverse.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ class PhiInverse
3535

3636
/// whether the transform is guaranteed to be a no-op, ie, there is no phi function in `_to`
3737
bool noOp() const;
38-
SSACFG::ValueId operator()(SSACFG::ValueId _valueId) const;
38+
InstId operator()(InstId _valueId) const;
3939

40-
std::map<SSACFG::ValueId, SSACFG::ValueId> const& data() const;
40+
std::map<InstId, InstId> const& data() const;
4141

4242
private:
43-
std::map<SSACFG::ValueId, SSACFG::ValueId> m_phiToPreImage = {};
43+
std::map<InstId, InstId> m_phiToPreImage = {};
4444
};
4545

4646
}

0 commit comments

Comments
 (0)