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
26 changes: 13 additions & 13 deletions libyul/backends/evm/ssa/CodeTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ CodeTransform::CodeTransform(
expectedStackTop.reserve(m_cfg.arguments.size() + (isFunctionGraph && m_cfg.canContinue ? 1 : 0));
if (isFunctionGraph && m_cfg.canContinue)
expectedStackTop.push_back(StackSlot::makeFunctionReturnLabel(m_graphID));
for (auto const& valueID: m_cfg.arguments | ranges::views::reverse)
expectedStackTop.push_back(StackSlot::makeValueID(_cfg, valueID));
for (auto const& arg: m_cfg.arguments | ranges::views::reverse)
expectedStackTop.push_back(StackSlot::makeValue(_cfg, arg));
assertLayoutCompatibility(m_stack.data(), expectedStackTop);
}

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

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

// Assert that the operation produced its proclaimed output.
yulAssert(m_stack.size() == baseHeight + _inst.numOutputs);
yulAssert(m_stack.size() == baseHeight + numOutputs);
for (auto const& [stackEntry, output]: ranges::views::zip(
m_stack.data() | ranges::views::take_last(_inst.numOutputs),
SSACFG::outputsOf(_instId, _inst.numOutputs)
m_stack.data() | ranges::views::take_last(numOutputs),
m_cfg.outputsOf(_instId)
))
yulAssert(stackEntry.isValueID() && stackEntry.valueID() == output);
yulAssert(stackEntry.isValue() && stackEntry.value() == output);
yulAssert(
static_cast<int>(m_stack.size()) == m_assembly.stackHeight(),
fmt::format("symbolic stack size = {} =/= {} = assembly stack height", m_stack.size(), m_assembly.stackHeight())
Expand All @@ -335,7 +335,7 @@ void CodeTransform::operator()(SSACFG::BlockId const& _currentBlock, SSACFG::Bas
{
yulAssert(static_cast<int>(m_stack.size()) == m_assembly.stackHeight());
// condition must be at the top of the stack
yulAssert(m_stack.top().isValueID() && m_stack.top().valueID() == _conditionalJump.condition);
yulAssert(m_stack.top().isValue() && m_stack.top().value() == _conditionalJump.condition);
// emit JUMPI to nonZero block
m_assembly.appendJumpToIf(m_blockLabels[_conditionalJump.nonZero.value]);
// update symbolic stack by popping the condition as it'll be consumed by JUMPI
Expand Down Expand Up @@ -391,8 +391,8 @@ void CodeTransform::operator()(SSACFG::BlockId const&, SSACFG::BasicBlock::Funct
for (std::size_t i = 0; i < _functionReturn.returnValues.size(); ++i)
{
auto const& returnValueSlot = m_stack.slot(StackOffset{i});
yulAssert(returnValueSlot.isValueID());
yulAssert(returnValueSlot.valueID() == _functionReturn.returnValues[i]);
yulAssert(returnValueSlot.isValue());
yulAssert(returnValueSlot.value() == _functionReturn.returnValues[i]);
}
m_assembly.appendJump(0, AbstractAssembly::JumpType::OutOfFunction);
}
Expand Down
6 changes: 3 additions & 3 deletions libyul/backends/evm/ssa/CodeTransform.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ struct AssemblyCallbacks
{
switch (_slot.kind())
{
case StackSlot::Kind::ValueID:
case StackSlot::Kind::Value:
{
auto const id = _slot.valueID();
auto const id = _slot.value();
yulAssert(cfg->isLiteral(id), fmt::format("Tried bringing up non-const {}", id));
assembly->appendConstant(cfg->literalPayload(id.instId()));
assembly->appendConstant(cfg->literalPayload(id));
return;
}
case StackSlot::Kind::Junk:
Expand Down
75 changes: 39 additions & 36 deletions libyul/backends/evm/ssa/InstructionStore.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@

#include <libsolutil/Numeric.h>

#include <range/v3/view/iota.hpp>
#include <range/v3/view/transform.hpp>

#include <cstdint>
#include <limits>
#include <map>
Expand All @@ -48,6 +45,8 @@ class InstructionStore
/// Sentinel value for `Inst::payloadIndex` when the opcode has no side-table payload.
static constexpr std::uint32_t NoPayload = std::numeric_limits<std::uint32_t>::max();

using NumReturnsSizeType = std::uint8_t;

struct BuiltinCall
{
BuiltinHandle builtin;
Expand All @@ -58,20 +57,21 @@ class InstructionStore
{
FunctionGraphID graphID;
bool canContinue;
std::size_t numReturns;
};
struct Inst
{
InstOpcode opcode;
ValueId::OutputSize numOutputs;
std::uint32_t payloadIndex = NoPayload;
BlockId block{};
std::vector<ValueId> inputs{};
std::vector<InstId> inputs{};

constexpr bool isPhi() const noexcept { return opcode == InstOpcode::Phi; }
constexpr bool isUpsilon() const noexcept { return opcode == InstOpcode::Upsilon; }
constexpr bool isLiteral() const noexcept { return opcode == InstOpcode::Const; }
constexpr bool isUnreachable() const noexcept { return opcode == InstOpcode::Unreachable; }
constexpr bool isFunctionArg() const noexcept { return opcode == InstOpcode::FunctionArg; }
constexpr bool isProjection() const noexcept { return opcode == InstOpcode::Projection; }
/// Operation = a Call or BuiltinCall.
constexpr bool isOperation() const noexcept
{
Expand All @@ -91,23 +91,11 @@ class InstructionStore
std::size_t numInsts() const { return m_insts.size(); }
std::vector<Inst> const& instructions() const { return m_insts; }

static auto outputsOf(InstId const _id, ValueId::OutputSize const _numOutputs)
{
return
ranges::views::iota(ValueId::OutputSize{0}, _numOutputs) |
ranges::views::transform([_id](ValueId::OutputSize const _pos) { return ValueId{_id, _pos}; });
}

auto instOutputs(InstId const _id) const
{
return outputsOf(_id, inst(_id).numOutputs);
}

/// Returns the opcode category for a given ValueId.
InstOpcode kindOf(ValueId const _v) const { return inst(_v.instId()).opcode; }
/// Returns the opcode category for a given InstId.
InstOpcode kindOf(InstId const _id) const { return inst(_id).opcode; }

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

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

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

/// Returns the projection index of a Projection Inst
NumReturnsSizeType projectionIndex(InstId const _id) const
{
Inst const& projectionInst = inst(_id);
yulAssert(projectionInst.opcode == InstOpcode::Projection);
yulAssert(projectionInst.inputs.size() == 1);
InstId const producer = projectionInst.inputs.front();
yulAssert(_id.value > producer.value);
std::size_t const offset = static_cast<std::size_t>(_id.value) - static_cast<std::size_t>(producer.value) - 1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this assuming that the projections have ids immediately following the id of the function call instruction?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah exactly. I have elevated that to be an invariant of the SSA CFG. It's not like we can swap around projections and/or only extract a couple things anyways due to the stack nature of things. See here:

InstId makeCallWithProjections(
BlockId const _block,
Call _payload,
std::vector<InstId> _inputs,
InstructionStore::NumReturnsSizeType const _numReturns,
langutil::DebugData::ConstPtr _debugData = {}
)
{
InstId const producer = makeCall(_block, std::move(_payload), std::move(_inputs), _debugData);
if (_numReturns >= 2)
for (InstructionStore::NumReturnsSizeType i = 0; i < _numReturns; ++i)
makeProjection(_block, producer, _debugData);
return producer;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hopefully. I guess it would be good to check this invariant somewhere.
We should eventually have something like a pass to check that the IR is well-formed, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah we should

yulAssert(offset <= std::numeric_limits<NumReturnsSizeType>::max());
return static_cast<NumReturnsSizeType>(offset);
}

/// Allocates a new Phi Inst defined in `_definingBlock`. Returns the new InstId.
InstId appendPhi(BlockId const _definingBlock)
{
return appendInst(Inst{InstOpcode::Phi, 1, NoPayload, _definingBlock, {}});
return appendInst(Inst{InstOpcode::Phi, NoPayload, _definingBlock, {}});
}

/// Allocates a new FunctionArg Inst defined in `_entryBlock`. Returns the new InstId.
InstId appendFunctionArg(BlockId const _entryBlock)
{
return appendInst(Inst{InstOpcode::FunctionArg, 1, NoPayload, _entryBlock, {}});
return appendInst(Inst{InstOpcode::FunctionArg, NoPayload, _entryBlock, {}});
}

/// Allocates a new Unreachable Inst. Not pinned to any block (see class comment).
InstId appendUnreachable()
{
return appendInst(Inst{InstOpcode::Unreachable, 1, NoPayload, BlockId{}, {}});
return appendInst(Inst{InstOpcode::Unreachable, NoPayload, BlockId{}, {}});
}

/// Allocates a new Const Inst with payload `_value`, pinned to `_entryBlock`.
Expand All @@ -142,7 +143,7 @@ class InstructionStore
if (auto const it = m_literalDedup.find(_value); it != m_literalDedup.end())
return it->second;
auto const payloadIdx = allocPayload(m_literalPayloads, _value);
InstId const id = appendInst(Inst{InstOpcode::Const, 1, payloadIdx, _entryBlock, {}});
InstId const id = appendInst(Inst{InstOpcode::Const, payloadIdx, _entryBlock, {}});
m_literalDedup.emplace(std::move(_value), id);
return id;
}
Expand All @@ -151,15 +152,12 @@ class InstructionStore
InstId appendBuiltinCall(
BlockId const _block,
BuiltinCall _payload,
std::vector<ValueId> _inputs,
std::size_t const _numOutputs
std::vector<InstId> _inputs
)
{
yulAssert(_block.hasValue());
yulAssert(_numOutputs <= std::numeric_limits<ValueId::OutputSize>::max());
return appendInst(Inst{
InstOpcode::BuiltinCall,
static_cast<ValueId::OutputSize>(_numOutputs),
allocPayload(m_builtinPayloads, std::move(_payload)),
_block,
std::move(_inputs)
Expand All @@ -170,26 +168,31 @@ class InstructionStore
InstId appendCall(
BlockId const _block,
Call _payload,
std::vector<ValueId> _inputs,
std::size_t const _numOutputs
std::vector<InstId> _inputs
)
{
yulAssert(_block.hasValue());
yulAssert(_numOutputs <= std::numeric_limits<ValueId::OutputSize>::max());
return appendInst(Inst{
InstOpcode::Call,
static_cast<ValueId::OutputSize>(_numOutputs),
allocPayload(m_callPayloads, std::move(_payload)),
_block,
std::move(_inputs)
});
}

/// Allocates a new Projection Inst projecting output `_index` of `_producer`.
InstId appendProjection(BlockId const _block, InstId const _producer)
{
yulAssert(_block.hasValue());
yulAssert(_producer.hasValue());
return appendInst(Inst{InstOpcode::Projection, NoPayload, _block, {_producer}});
}

/// Allocates a new Upsilon Inst targeting `_phi`, feeding `_value` from `_block`.
InstId appendUpsilon(BlockId const _block, ValueId const _value, ValueId const _phi)
InstId appendUpsilon(BlockId const _block, InstId const _value, InstId const _phi)
{
yulAssert(inst(_phi.instId()).isPhi());
return appendInst(Inst{InstOpcode::Upsilon, 0, allocPayload(m_upsilonPhis, _phi), _block, {_value}});
yulAssert(inst(_phi).isPhi());
return appendInst(Inst{InstOpcode::Upsilon, allocPayload(m_upsilonPhis, _phi), _block, {_value}});
}

private:
Expand Down Expand Up @@ -225,7 +228,7 @@ class InstructionStore
std::vector<Inst> m_insts;
std::vector<u256> m_literalPayloads;
std::map<u256, InstId> m_literalDedup;
std::vector<ValueId> m_upsilonPhis;
std::vector<InstId> m_upsilonPhis;
std::vector<BuiltinCall> m_builtinPayloads;
std::vector<Call> m_callPayloads;
};
Expand Down
16 changes: 9 additions & 7 deletions libyul/backends/evm/ssa/LivenessAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <range/v3/algorithm/count_if.hpp>
#include <range/v3/range/conversion.hpp>

#include <range/v3/view/enumerate.hpp>
#include <range/v3/view/filter.hpp>
#include <range/v3/view/reverse.hpp>

Expand Down Expand Up @@ -83,7 +84,7 @@ void LivenessAnalysis::runDagDfs()
// live <- PhiUses(B)
LivenessData live{};
m_cfg.forEachUpsilon(block, [&](InstId, SSACFG::Inst const& inst) {
SSACFG::ValueId const v = inst.inputs.at(0);
InstId const v = inst.inputs.at(0);
yulAssert(!m_cfg.isUnreachable(v));
if (!m_cfg.isLiteral(v))
live.insert(v);
Expand All @@ -97,7 +98,7 @@ void LivenessAnalysis::runDagDfs()
// LiveIn(S) - PhiDefs(S)
auto liveInWithoutPhiDefs = m_liveIns[_successor.value];
m_cfg.forEachPhi(m_cfg.block(_successor), [&](InstId const succInstId, SSACFG::Inst const&) {
liveInWithoutPhiDefs.erase(ValueId{succInstId});
liveInWithoutPhiDefs.erase(succInstId);
});
live.maxUnion(liveInWithoutPhiDefs);
}
Expand All @@ -123,15 +124,15 @@ void LivenessAnalysis::runDagDfs()
if (!inst.isOperation())
continue;
// remove variables defined at p from live
live.eraseAll(SSACFG::outputsOf(instId, inst.numOutputs) | ranges::views::filter(excludingLiteralsFilter()));
// add uses at p to live
live.eraseAll(m_cfg.projectionsOf(instId));
live.erase(instId);
live.insertAll(inst.inputs | ranges::views::filter(excludingLiteralsFilter()));
}
}

// livein(b) <- live \cup PhiDefs(B)
m_cfg.forEachPhi(block, [&](InstId const instId, SSACFG::Inst const&) {
live.insert(ValueId{instId});
live.insert(instId);
});
m_liveIns[blockId.value] = live;
}
Expand All @@ -147,7 +148,7 @@ void LivenessAnalysis::runLoopTreeDfs(SSACFG::BlockId::ValueType const _loopHead
// LiveLoop <- LiveIn(B_N) - PhiDefs(B_N)
auto liveLoop = m_liveIns[_loopHeader];
m_cfg.forEachPhi(block, [&](InstId const instId, SSACFG::Inst const&) {
liveLoop.erase(ValueId{instId});
liveLoop.erase(instId);
});
// must be live out of header if live in of children
m_liveOuts[_loopHeader].maxUnion(liveLoop);
Expand Down Expand Up @@ -186,7 +187,8 @@ void LivenessAnalysis::fillOperationsLiveOut()
if (!inst.isOperation())
continue;
*rit = live;
live.eraseAll(SSACFG::outputsOf(instId, inst.numOutputs) | ranges::views::filter(excludingLiteralsFilter()));
live.eraseAll(m_cfg.projectionsOf(instId));
live.erase(instId);
live.insertAll(inst.inputs | ranges::views::filter(excludingLiteralsFilter()));
++rit;
}
Expand Down
4 changes: 2 additions & 2 deletions libyul/backends/evm/ssa/LivenessAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class LivenessAnalysis
public:
/// Per-program-point liveness, each value's use count is the max number of times the value will be read along
/// all paths downstream of that point
using LivenessData = util::UseCountSet<SSACFG::ValueId>;
using LivenessData = util::UseCountSet<InstId>;

explicit LivenessAnalysis(SSACFG const& _cfg);

Expand All @@ -55,7 +55,7 @@ class LivenessAnalysis

auto excludingLiteralsFilter() const
{
return [this](SSACFG::ValueId _v) { return !m_cfg.isLiteral(_v); };
return [this](InstId _v) { return !m_cfg.isLiteral(_v); };
}

SSACFG const& m_cfg;
Expand Down
8 changes: 4 additions & 4 deletions libyul/backends/evm/ssa/PhiInverse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ PhiInverse::PhiInverse(SSACFG const& _cfg, SSACFG::BlockId const& _from, SSACFG:
{
_cfg.forEachUpsilon(_cfg.block(_from), [&](InstId const instId, SSACFG::Inst const& inst) {
if (
ValueId const phi = _cfg.upsilonPhi(instId);
_cfg.inst(phi.instId()).block == _to
InstId const phi = _cfg.upsilonPhi(instId);
_cfg.inst(phi).block == _to
)
m_phiToPreImage[phi] = inst.inputs.at(0);
});
Expand All @@ -36,12 +36,12 @@ bool PhiInverse::noOp() const
return m_phiToPreImage.empty();
}

SSACFG::ValueId PhiInverse::operator()(SSACFG::ValueId _valueId) const
InstId PhiInverse::operator()(InstId _valueId) const
{
return solidity::util::valueOrDefault(m_phiToPreImage, _valueId, _valueId);
}

std::map<SSACFG::ValueId, SSACFG::ValueId> const& PhiInverse::data() const
std::map<InstId, InstId> const& PhiInverse::data() const
{
return m_phiToPreImage;
}
6 changes: 3 additions & 3 deletions libyul/backends/evm/ssa/PhiInverse.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ class PhiInverse

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

std::map<SSACFG::ValueId, SSACFG::ValueId> const& data() const;
std::map<InstId, InstId> const& data() const;

private:
std::map<SSACFG::ValueId, SSACFG::ValueId> m_phiToPreImage = {};
std::map<InstId, InstId> m_phiToPreImage = {};
};

}
Loading