Better source locations in Yul ControlFlowGraph and OptimizedEVMCodeTransform.

This commit is contained in:
Daniel Kirchner 2021-09-09 16:49:36 +02:00
parent 75c3286311
commit 854b8b65b5
6 changed files with 108 additions and 82 deletions

View File

@ -95,18 +95,16 @@ template <class... Args> inline langutil::SourceLocation locationOf(std::variant
return std::visit([](auto const& _arg) { return locationOf(_arg); }, _node);
}
struct DebugDataExtractor
{
template <class T> std::shared_ptr<DebugData const> const& operator()(T const& _node) const
/// Extracts the debug data from a Yul node.
template <class T> inline std::shared_ptr<DebugData const> debugDataOf(T const& _node)
{
return _node.debugData;
}
};
/// Extracts the debug data from a Yul node.
template <class T> inline std::shared_ptr<DebugData const> const& debugDataOf(T const& _node)
template <class... Args> inline std::shared_ptr<DebugData const> debugDataOf(std::variant<Args...> const& _node)
{
return std::visit(DebugDataExtractor(), _node);
return std::visit([](auto const& _arg) { return debugDataOf(_arg); }, _node);
}
}

View File

@ -172,18 +172,25 @@ struct CFG
struct MainExit {};
struct ConditionalJump
{
std::shared_ptr<DebugData const> debugData;
StackSlot condition;
BasicBlock* nonZero = nullptr;
BasicBlock* zero = nullptr;
};
struct Jump
{
std::shared_ptr<DebugData const> debugData;
BasicBlock* target = nullptr;
/// The only backwards jumps are jumps from loop post to loop condition.
bool backwards = false;
};
struct FunctionReturn { CFG::FunctionInfo* info = nullptr; };
struct FunctionReturn
{
std::shared_ptr<DebugData const> debugData;
CFG::FunctionInfo* info = nullptr;
};
struct Terminated {};
std::shared_ptr<DebugData const> debugData;
std::vector<BasicBlock*> entries;
std::vector<Operation> operations;
std::variant<MainExit, Jump, ConditionalJump, FunctionReturn, Terminated> exit = MainExit{};
@ -216,9 +223,9 @@ struct CFG
/// the switch case literals when transforming the control flow of a switch to a sequence of conditional jumps.
std::list<yul::FunctionCall> ghostCalls;
BasicBlock& makeBlock()
BasicBlock& makeBlock(std::shared_ptr<DebugData const> _debugData)
{
return blocks.emplace_back(BasicBlock{});
return blocks.emplace_back(BasicBlock{move(_debugData), {}, {}});
}
};

View File

@ -134,7 +134,7 @@ std::unique_ptr<CFG> ControlFlowGraphBuilder::build(
)
{
auto result = std::make_unique<CFG>();
result->entry = &result->makeBlock();
result->entry = &result->makeBlock(debugDataOf(_block));
ControlFlowGraphBuilder builder(*result, _analysisInfo, _dialect);
builder.m_currentBlock = result->entry;
@ -233,7 +233,7 @@ void ControlFlowGraphBuilder::operator()(ExpressionStatement const& _exprStmt)
if (builtin->controlFlowSideEffects.terminates)
{
m_currentBlock->exit = CFG::BasicBlock::Terminated{};
m_currentBlock = &m_graph.makeBlock();
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
}
}
@ -246,15 +246,19 @@ void ControlFlowGraphBuilder::operator()(Block const& _block)
void ControlFlowGraphBuilder::operator()(If const& _if)
{
auto&& [ifBranch, afterIf] = makeConditionalJump(std::visit(*this, *_if.condition));
m_currentBlock = ifBranch;
auto& ifBranch = m_graph.makeBlock(debugDataOf(_if.body));
auto& afterIf = m_graph.makeBlock(debugDataOf(*m_currentBlock));
makeConditionalJump(debugDataOf(_if), std::visit(*this, *_if.condition), ifBranch, afterIf);
m_currentBlock = &ifBranch;
(*this)(_if.body);
jump(*afterIf);
jump(debugDataOf(_if.body), afterIf);
}
void ControlFlowGraphBuilder::operator()(Switch const& _switch)
{
yulAssert(m_currentBlock, "");
shared_ptr<DebugData const> preSwitchDebugData = debugDataOf(_switch);
auto ghostVariableId = m_graph.ghostVariables.size();
YulString ghostVariableName("GHOST[" + to_string(ghostVariableId) + "]");
auto& ghostVar = m_graph.ghostVariables.emplace_back(Scope::Variable{""_yulstring, ghostVariableName});
@ -273,43 +277,46 @@ void ControlFlowGraphBuilder::operator()(Switch const& _switch)
// Artificially generate:
// eq(<literal>, <ghostVariable>)
auto makeValueCompare = [&](Literal const& _value) {
auto makeValueCompare = [&](Case const& _case) {
yul::FunctionCall const& ghostCall = m_graph.ghostCalls.emplace_back(yul::FunctionCall{
_value.debugData,
debugDataOf(_case),
yul::Identifier{{}, "eq"_yulstring},
{_value, Identifier{{}, ghostVariableName}}
{*_case.value, Identifier{{}, ghostVariableName}}
});
CFG::Operation& operation = m_currentBlock->operations.emplace_back(CFG::Operation{
Stack{ghostVarSlot, LiteralSlot{valueOfLiteral(_value), _value.debugData}},
Stack{ghostVarSlot, LiteralSlot{valueOfLiteral(*_case.value), debugDataOf(*_case.value)}},
Stack{TemporarySlot{ghostCall, 0}},
CFG::BuiltinCall{_switch.debugData, *equalityBuiltin, ghostCall, 2},
CFG::BuiltinCall{debugDataOf(_case), *equalityBuiltin, ghostCall, 2},
});
return operation.output.front();
};
CFG::BasicBlock& afterSwitch = m_graph.makeBlock();
CFG::BasicBlock& afterSwitch = m_graph.makeBlock(preSwitchDebugData);
yulAssert(!_switch.cases.empty(), "");
for (auto const& switchCase: _switch.cases | ranges::views::drop_last(1))
{
yulAssert(switchCase.value, "");
auto&& [caseBranch, elseBranch] = makeConditionalJump(makeValueCompare(*switchCase.value));
m_currentBlock = caseBranch;
auto& caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
auto& elseBranch = m_graph.makeBlock(debugDataOf(_switch));
makeConditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, elseBranch);
m_currentBlock = &caseBranch;
(*this)(switchCase.body);
jump(afterSwitch);
m_currentBlock = elseBranch;
jump(debugDataOf(switchCase.body), afterSwitch);
m_currentBlock = &elseBranch;
}
Case const& switchCase = _switch.cases.back();
if (switchCase.value)
{
CFG::BasicBlock& caseBranch = m_graph.makeBlock();
makeConditionalJump(makeValueCompare(*switchCase.value), caseBranch, afterSwitch);
CFG::BasicBlock& caseBranch = m_graph.makeBlock(debugDataOf(switchCase.body));
makeConditionalJump(debugDataOf(switchCase), makeValueCompare(switchCase), caseBranch, afterSwitch);
m_currentBlock = &caseBranch;
}
(*this)(switchCase.body);
jump(afterSwitch);
jump(debugDataOf(switchCase.body), afterSwitch);
}
void ControlFlowGraphBuilder::operator()(ForLoop const& _loop)
{
shared_ptr<DebugData const> preLoopDebugData = debugDataOf(_loop);
ScopedSaveAndRestore scopeRestore(m_scope, m_info.scopes.at(&_loop.pre).get());
(*this)(_loop.pre);
@ -317,10 +324,10 @@ void ControlFlowGraphBuilder::operator()(ForLoop const& _loop)
if (auto const* literalCondition = get_if<yul::Literal>(_loop.condition.get()))
constantCondition = valueOfLiteral(*literalCondition) != 0;
CFG::BasicBlock& loopCondition = m_graph.makeBlock();
CFG::BasicBlock& loopBody = m_graph.makeBlock();
CFG::BasicBlock& post = m_graph.makeBlock();
CFG::BasicBlock& afterLoop = m_graph.makeBlock();
CFG::BasicBlock& loopCondition = m_graph.makeBlock(debugDataOf(*_loop.condition));
CFG::BasicBlock& loopBody = m_graph.makeBlock(debugDataOf(_loop.body));
CFG::BasicBlock& post = m_graph.makeBlock(debugDataOf(_loop.post));
CFG::BasicBlock& afterLoop = m_graph.makeBlock(preLoopDebugData);
ScopedSaveAndRestore scopedSaveAndRestore(m_forLoopInfo, ForLoopInfo{afterLoop, post});
@ -328,48 +335,49 @@ void ControlFlowGraphBuilder::operator()(ForLoop const& _loop)
{
if (*constantCondition)
{
jump(loopBody);
jump(debugDataOf(_loop.pre), loopBody);
(*this)(_loop.body);
jump(post);
jump(debugDataOf(_loop.body), post);
(*this)(_loop.post);
jump(loopBody, true);
jump(debugDataOf(_loop.post), loopBody, true);
}
else
jump(afterLoop);
jump(debugDataOf(_loop.pre), afterLoop);
}
else
{
jump(loopCondition);
makeConditionalJump(std::visit(*this, *_loop.condition), loopBody, afterLoop);
jump(debugDataOf(_loop.pre), loopCondition);
makeConditionalJump(debugDataOf(*_loop.condition), std::visit(*this, *_loop.condition), loopBody, afterLoop);
m_currentBlock = &loopBody;
(*this)(_loop.body);
jump(post);
jump(debugDataOf(_loop.body), post);
(*this)(_loop.post);
jump(loopCondition, true);
jump(debugDataOf(_loop.post), loopCondition, true);
}
m_currentBlock = &afterLoop;
}
void ControlFlowGraphBuilder::operator()(Break const&)
void ControlFlowGraphBuilder::operator()(Break const& _break)
{
yulAssert(m_forLoopInfo.has_value(), "");
jump(m_forLoopInfo->afterLoop);
m_currentBlock = &m_graph.makeBlock();
jump(debugDataOf(_break), m_forLoopInfo->afterLoop);
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
}
void ControlFlowGraphBuilder::operator()(Continue const&)
void ControlFlowGraphBuilder::operator()(Continue const& _continue)
{
yulAssert(m_forLoopInfo.has_value(), "");
jump(m_forLoopInfo->post);
m_currentBlock = &m_graph.makeBlock();
jump(debugDataOf(_continue), m_forLoopInfo->post);
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
}
void ControlFlowGraphBuilder::operator()(Leave const&)
// '_leave' and '__leave' are reserved in VisualStudio
void ControlFlowGraphBuilder::operator()(Leave const& leave_)
{
yulAssert(m_currentFunctionExit.has_value(), "");
m_currentBlock->exit = *m_currentFunctionExit;
m_currentBlock = &m_graph.makeBlock();
yulAssert(m_currentFunction.has_value(), "");
m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(leave_), *m_currentFunction};
m_currentBlock = &m_graph.makeBlock(debugDataOf(*m_currentBlock));
}
void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
@ -386,7 +394,7 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
auto&& [it, inserted] = m_graph.functionInfo.emplace(std::make_pair(&function, CFG::FunctionInfo{
_function.debugData,
function,
&m_graph.makeBlock(),
&m_graph.makeBlock(debugDataOf(_function.body)),
_function.parameters | ranges::views::transform([&](auto const& _param) {
return VariableSlot{
std::get<Scope::Variable>(virtualFunctionScope->identifiers.at(_param.name)),
@ -404,10 +412,10 @@ void ControlFlowGraphBuilder::operator()(FunctionDefinition const& _function)
CFG::FunctionInfo& functionInfo = it->second;
ControlFlowGraphBuilder builder{m_graph, m_info, m_dialect};
builder.m_currentFunctionExit = CFG::BasicBlock::FunctionReturn{&functionInfo};
builder.m_currentFunction = &functionInfo;
builder.m_currentBlock = functionInfo.entry;
builder(_function.body);
builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{&functionInfo};
builder.m_currentBlock->exit = CFG::BasicBlock::FunctionReturn{debugDataOf(_function), &functionInfo};
}
@ -497,18 +505,16 @@ Scope::Variable const& ControlFlowGraphBuilder::lookupVariable(YulString _name)
yulAssert(false, "External identifier access unimplemented.");
}
std::pair<CFG::BasicBlock*, CFG::BasicBlock*> ControlFlowGraphBuilder::makeConditionalJump(StackSlot _condition)
{
CFG::BasicBlock& nonZero = m_graph.makeBlock();
CFG::BasicBlock& zero = m_graph.makeBlock();
makeConditionalJump(move(_condition), nonZero, zero);
return {&nonZero, &zero};
}
void ControlFlowGraphBuilder::makeConditionalJump(StackSlot _condition, CFG::BasicBlock& _nonZero, CFG::BasicBlock& _zero)
void ControlFlowGraphBuilder::makeConditionalJump(
shared_ptr<DebugData const> _debugData,
StackSlot _condition,
CFG::BasicBlock& _nonZero,
CFG::BasicBlock& _zero
)
{
yulAssert(m_currentBlock, "");
m_currentBlock->exit = CFG::BasicBlock::ConditionalJump{
move(_debugData),
move(_condition),
&_nonZero,
&_zero
@ -518,10 +524,14 @@ void ControlFlowGraphBuilder::makeConditionalJump(StackSlot _condition, CFG::Bas
m_currentBlock = nullptr;
}
void ControlFlowGraphBuilder::jump(CFG::BasicBlock& _target, bool backwards)
void ControlFlowGraphBuilder::jump(
shared_ptr<DebugData const> _debugData,
CFG::BasicBlock& _target,
bool backwards
)
{
yulAssert(m_currentBlock, "");
m_currentBlock->exit = CFG::BasicBlock::Jump{&_target, backwards};
m_currentBlock->exit = CFG::BasicBlock::Jump{move(_debugData), &_target, backwards};
_target.entries.emplace_back(m_currentBlock);
m_currentBlock = &_target;
}

View File

@ -62,13 +62,18 @@ private:
Scope::Function const& lookupFunction(YulString _name) const;
Scope::Variable const& lookupVariable(YulString _name) const;
/// @returns a pair of newly created blocks, the first element being the non-zero branch, the second element the
/// zero branch.
/// Resets m_currentBlock to enforce a subsequent explicit reassignment.
std::pair<CFG::BasicBlock*, CFG::BasicBlock*> makeConditionalJump(StackSlot _condition);
/// Resets m_currentBlock to enforce a subsequent explicit reassignment.
void makeConditionalJump(StackSlot _condition, CFG::BasicBlock& _nonZero, CFG::BasicBlock& _zero);
void jump(CFG::BasicBlock& _target, bool _backwards = false);
void makeConditionalJump(
std::shared_ptr<DebugData const> _debugData,
StackSlot _condition,
CFG::BasicBlock& _nonZero,
CFG::BasicBlock& _zero
);
void jump(
std::shared_ptr<DebugData const> _debugData,
CFG::BasicBlock& _target,
bool _backwards = false
);
CFG& m_graph;
AsmAnalysisInfo const& m_info;
Dialect const& m_dialect;
@ -80,7 +85,7 @@ private:
std::reference_wrapper<CFG::BasicBlock> post;
};
std::optional<ForLoopInfo> m_forLoopInfo;
std::optional<CFG::BasicBlock::FunctionReturn> m_currentFunctionExit;
std::optional<CFG::FunctionInfo*> m_currentFunction;
};
}

View File

@ -57,7 +57,7 @@ vector<StackTooDeepError> OptimizedEVMCodeTransform::run(
stackLayout
);
// Create initial entry layout.
optimizedCodeTransform.createStackLayout(stackLayout.blockInfos.at(dfg->entry).entryLayout);
optimizedCodeTransform.createStackLayout(debugDataOf(*dfg->entry), stackLayout.blockInfos.at(dfg->entry).entryLayout);
optimizedCodeTransform(*dfg->entry);
for (Scope::Function const* function: dfg->functions)
optimizedCodeTransform(dfg->functionInfo.at(function));
@ -220,7 +220,7 @@ void OptimizedEVMCodeTransform::validateSlot(StackSlot const& _slot, Expression
}, _expression);
}
void OptimizedEVMCodeTransform::createStackLayout(Stack _targetStack)
void OptimizedEVMCodeTransform::createStackLayout(std::shared_ptr<DebugData const> _debugData, Stack _targetStack)
{
static constexpr auto slotVariableName = [](StackSlot const& _slot) {
return std::visit(util::GenericVisitor{
@ -231,6 +231,8 @@ void OptimizedEVMCodeTransform::createStackLayout(Stack _targetStack)
yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), "");
// ::createStackLayout asserts that it has successfully achieved the target layout.
langutil::SourceLocation sourceLocation = _debugData ? _debugData->location : langutil::SourceLocation{};
m_assembly.setSourceLocation(sourceLocation);
::createStackLayout(
m_stack,
_targetStack | ranges::to<Stack>,
@ -299,6 +301,7 @@ void OptimizedEVMCodeTransform::createStackLayout(Stack _targetStack)
{
m_assembly.setSourceLocation(locationOf(_literal));
m_assembly.appendConstant(_literal.value);
m_assembly.setSourceLocation(sourceLocation);
},
[&](FunctionReturnLabelSlot const&)
{
@ -310,6 +313,7 @@ void OptimizedEVMCodeTransform::createStackLayout(Stack _targetStack)
m_returnLabels[&_returnLabel.call.get()] = m_assembly.newLabelId();
m_assembly.setSourceLocation(locationOf(_returnLabel.call.get()));
m_assembly.appendLabelReference(m_returnLabels.at(&_returnLabel.call.get()));
m_assembly.setSourceLocation(sourceLocation);
},
[&](VariableSlot const& _variable)
{
@ -317,6 +321,7 @@ void OptimizedEVMCodeTransform::createStackLayout(Stack _targetStack)
{
m_assembly.setSourceLocation(locationOf(_variable));
m_assembly.appendConstant(0);
m_assembly.setSourceLocation(sourceLocation);
return;
}
yulAssert(false, "Variable not found on stack.");
@ -346,6 +351,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
// Assert that this is the first visit of the block and mark as generated.
yulAssert(m_generated.insert(&_block).second, "");
m_assembly.setSourceLocation(locationOf(_block));
auto const& blockInfo = m_stackLayout.blockInfos.at(&_block);
// Assert that the stack is valid for entering the block.
@ -360,7 +366,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
for (auto const& operation: _block.operations)
{
// Create required layout for entering the operation.
createStackLayout(m_stackLayout.operationEntryLayout.at(&operation));
createStackLayout(debugDataOf(operation.operation), m_stackLayout.operationEntryLayout.at(&operation));
// Assert that we have the inputs of the operation on stack top.
yulAssert(static_cast<int>(m_stack.size()) == m_assembly.stackHeight(), "");
@ -385,6 +391,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
}
// Exit the block.
m_assembly.setSourceLocation(locationOf(_block));
std::visit(util::GenericVisitor{
[&](CFG::BasicBlock::MainExit const&)
{
@ -393,7 +400,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
[&](CFG::BasicBlock::Jump const& _jump)
{
// Create the stack expected at the jump target.
createStackLayout(m_stackLayout.blockInfos.at(_jump.target).entryLayout);
createStackLayout(debugDataOf(_jump), m_stackLayout.blockInfos.at(_jump.target).entryLayout);
// If this is the only jump to the block, we do not need a label and can directly continue with the target block.
if (!m_blockLabels.count(_jump.target) && _jump.target->entries.size() == 1)
@ -417,7 +424,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
[&](CFG::BasicBlock::ConditionalJump const& _conditionalJump)
{
// Create the shared entry layout of the jump targets, which is stored as exit layout of the current block.
createStackLayout(blockInfo.exitLayout);
createStackLayout(debugDataOf(_conditionalJump), blockInfo.exitLayout);
// Create labels for the targets, if not already present.
if (!m_blockLabels.count(_conditionalJump.nonZero))
@ -468,9 +475,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::BasicBlock const& _block)
exitStack.emplace_back(FunctionReturnLabelSlot{_functionReturn.info->function});
// Create the function return layout and jump.
m_assembly.setSourceLocation(locationOf(*m_currentFunctionInfo));
createStackLayout(exitStack);
m_assembly.setSourceLocation(locationOf(*m_currentFunctionInfo));
createStackLayout(debugDataOf(_functionReturn), exitStack);
m_assembly.appendJump(0, AbstractAssembly::JumpType::OutOfFunction);
},
[&](CFG::BasicBlock::Terminated const&)
@ -505,7 +510,7 @@ void OptimizedEVMCodeTransform::operator()(CFG::FunctionInfo const& _functionInf
m_assembly.appendLabel(getFunctionLabel(_functionInfo.function));
// Create the entry layout of the function body block and visit.
createStackLayout(m_stackLayout.blockInfos.at(_functionInfo.entry).entryLayout);
createStackLayout(debugDataOf(_functionInfo), m_stackLayout.blockInfos.at(_functionInfo.entry).entryLayout);
(*this)(*_functionInfo.entry);
m_stack.clear();

View File

@ -77,7 +77,8 @@ private:
static void validateSlot(StackSlot const& _slot, Expression const& _expression);
/// Shuffles m_stack to the desired @a _targetStack while emitting the shuffling code to m_assembly.
void createStackLayout(Stack _targetStack);
/// Sets the source locations to the one in @a _debugData.
void createStackLayout(std::shared_ptr<DebugData const> _debugData, Stack _targetStack);
/// Generate code for the given block @a _block.
/// Expects the current stack layout m_stack to be a stack layout that is compatible with the