diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index e512a1b9b..6cfd7f988 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -71,6 +71,8 @@ add_library(yul backends/evm/EVMObjectCompiler.h backends/evm/EVMMetrics.cpp backends/evm/EVMMetrics.h + backends/evm/DirectEVMCodeTransform.cpp + backends/evm/DirectEVMCodeTransform.h backends/evm/DirectStackLayoutGenerator.cpp backends/evm/DirectStackLayoutGenerator.h backends/evm/NoOutputAssembly.h diff --git a/libyul/backends/evm/DirectEVMCodeTransform.cpp b/libyul/backends/evm/DirectEVMCodeTransform.cpp new file mode 100644 index 000000000..3e9b7f8f2 --- /dev/null +++ b/libyul/backends/evm/DirectEVMCodeTransform.cpp @@ -0,0 +1,233 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +#include + +#include + +#include + +#include + +using namespace solidity; +using namespace solidity::yul; +using namespace std; + +void DirectEVMCodeTransform::run( + AbstractAssembly& _assembly, + BuiltinContext& _builtinContext, + UseNamedLabels _useNamedLabelsForFunctions, + AsmAnalysisInfo const& _analysisInfo, + Dialect const& _dialect, + Block const& _ast +) +{ + DirectStackLayoutGenerator::Context context = DirectStackLayoutGenerator::run(_analysisInfo, _dialect, _ast); + DirectEVMCodeTransform codeTransform{ + _assembly, + _builtinContext, + _useNamedLabelsForFunctions, + context, + _analysisInfo, + _dialect + }; + codeTransform(_ast); +} + +void DirectEVMCodeTransform::operator()(Block const& _block) +{ + auto const& blockInfo = m_context.layout.blockInfos.at(&_block); + createStackLayout(debugDataOf(_block), blockInfo.entry); + for(auto const& _statement: _block.statements) + { + auto const& statementInfo = m_context.layout.statementInfos.at(&_statement); + createStackLayout(debugDataOf(_statement), statementInfo); + visit(_statement); + } +} + +void DirectEVMCodeTransform::operator()(FunctionCall const&) +{ + +} +void DirectEVMCodeTransform::operator()(ExpressionStatement const&) +{ + +} +void DirectEVMCodeTransform::operator()(Assignment const&) +{ + +} +void DirectEVMCodeTransform::operator()(VariableDeclaration const&) +{ + +} +void DirectEVMCodeTransform::operator()(If const&) +{ + yulAssert(false, ""); +} +void DirectEVMCodeTransform::operator()(Switch const&) +{ + yulAssert(false, ""); +} +void DirectEVMCodeTransform::operator()(ForLoop const&) +{ + yulAssert(false, ""); +} +void DirectEVMCodeTransform::operator()(FunctionDefinition const&) +{ + +} +void DirectEVMCodeTransform::operator()(Break const&) +{ + yulAssert(false, ""); +} +void DirectEVMCodeTransform::operator()(Continue const&) +{ + yulAssert(false, ""); +} +void DirectEVMCodeTransform::operator()(Leave const&) +{ + yulAssert(false, ""); +} + + +void DirectEVMCodeTransform::createStackLayout(std::shared_ptr _debugData, Stack _targetStack) +{ + static constexpr auto slotVariableName = [](StackSlot const& _slot) { + return std::visit(util::GenericVisitor{ + [](VariableSlot const& _var) { return _var.variable.get().name; }, + [](auto const&) { return YulString{}; } + }, _slot); + }; + + yulAssert(m_assembly.stackHeight() == static_cast(m_stack.size()), ""); + // ::createStackLayout asserts that it has successfully achieved the target layout. + langutil::SourceLocation sourceLocation = _debugData ? _debugData->originLocation : langutil::SourceLocation{}; + m_assembly.setSourceLocation(sourceLocation); + ::createStackLayout( + m_stack, + _targetStack | ranges::to, + // Swap callback. + [&](unsigned _i) + { + yulAssert(static_cast(m_stack.size()) == m_assembly.stackHeight(), ""); + yulAssert(_i > 0 && _i < m_stack.size(), ""); + if (_i <= 16) + m_assembly.appendInstruction(evmasm::swapInstruction(_i)); + else + { + int deficit = static_cast(_i) - 16; + StackSlot const& deepSlot = m_stack.at(m_stack.size() - _i - 1); + YulString varNameDeep = slotVariableName(deepSlot); + YulString varNameTop = slotVariableName(m_stack.back()); + string msg = + "Cannot swap " + (varNameDeep.empty() ? "Slot " + stackSlotToString(deepSlot) : "Variable " + varNameDeep.str()) + + " with " + (varNameTop.empty() ? "Slot " + stackSlotToString(m_stack.back()) : "Variable " + varNameTop.str()) + + ": too deep in the stack by " + to_string(deficit) + " slots in " + stackToString(m_stack); + m_stackErrors.emplace_back(StackTooDeepError( + m_currentFunctionInfo ? m_currentFunctionInfo->function.name : YulString{}, + varNameDeep.empty() ? varNameTop : varNameDeep, + deficit, + msg + )); + m_assembly.markAsInvalid(); + } + }, + // Push or dup callback. + [&](StackSlot const& _slot) + { + yulAssert(static_cast(m_stack.size()) == m_assembly.stackHeight(), ""); + + // Dup the slot, if already on stack and reachable. + if (auto depth = util::findOffset(m_stack | ranges::views::reverse, _slot)) + { + if (*depth < 16) + { + m_assembly.appendInstruction(evmasm::dupInstruction(static_cast(*depth + 1))); + return; + } + else if (!canBeFreelyGenerated(_slot)) + { + int deficit = static_cast(*depth - 15); + YulString varName = slotVariableName(_slot); + string msg = + (varName.empty() ? "Slot " + stackSlotToString(_slot) : "Variable " + varName.str()) + + " is " + to_string(*depth - 15) + " too deep in the stack " + stackToString(m_stack); + m_stackErrors.emplace_back(StackTooDeepError( + m_currentFunctionInfo ? m_currentFunctionInfo->function.name : YulString{}, + varName, + deficit, + msg + )); + m_assembly.markAsInvalid(); + m_assembly.appendConstant(u256(0xCAFFEE)); + return; + } + // else: the slot is too deep in stack, but can be freely generated, we fall through to push it again. + } + + // The slot can be freely generated or is an unassigned return variable. Push it. + std::visit(util::GenericVisitor{ + [&](LiteralSlot const& _literal) + { + m_assembly.setSourceLocation(originLocationOf(_literal)); + m_assembly.appendConstant(_literal.value); + m_assembly.setSourceLocation(sourceLocation); + }, + [&](FunctionReturnLabelSlot const&) + { + yulAssert(false, "Cannot produce function return label."); + }, + [&](FunctionCallReturnLabelSlot const& _returnLabel) + { + if (!m_returnLabels.count(&_returnLabel.call.get())) + m_returnLabels[&_returnLabel.call.get()] = m_assembly.newLabelId(); + m_assembly.setSourceLocation(originLocationOf(_returnLabel.call.get())); + m_assembly.appendLabelReference(m_returnLabels.at(&_returnLabel.call.get())); + m_assembly.setSourceLocation(sourceLocation); + }, + [&](VariableSlot const& _variable) + { + if (m_currentFunctionInfo && util::contains(m_currentFunctionInfo->returnVariables, _variable)) + { + m_assembly.setSourceLocation(originLocationOf(_variable)); + m_assembly.appendConstant(0); + m_assembly.setSourceLocation(sourceLocation); + return; + } + yulAssert(false, "Variable not found on stack."); + }, + [&](TemporarySlot const&) + { + yulAssert(false, "Function call result requested, but not found on stack."); + }, + [&](JunkSlot const&) + { + // Note: this will always be popped, so we can push anything. + m_assembly.appendInstruction(evmasm::Instruction::CODESIZE); + } + }, _slot); + }, + // Pop callback. + [&]() + { + m_assembly.appendInstruction(evmasm::Instruction::POP); + } + ); + yulAssert(m_assembly.stackHeight() == static_cast(m_stack.size()), ""); +} diff --git a/libyul/backends/evm/DirectEVMCodeTransform.h b/libyul/backends/evm/DirectEVMCodeTransform.h new file mode 100644 index 000000000..cf25033f6 --- /dev/null +++ b/libyul/backends/evm/DirectEVMCodeTransform.h @@ -0,0 +1,98 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Code generator for translating Yul / inline assembly to EVM. + */ +#pragma once + +#include +#include +#include + +namespace solidity::yul +{ + +class DirectEVMCodeTransform: public ASTWalker +{ +public: + /// Use named labels for functions 1) Yes and check that the names are unique + /// 2) For none of the functions 3) for the first function of each name. + enum class UseNamedLabels { YesAndForceUnique, Never, ForFirstFunctionOfEachName }; + + static void run( + AbstractAssembly& _assembly, + BuiltinContext& _builtinContext, + UseNamedLabels _useNamedLabelsForFunctions, + AsmAnalysisInfo const& _analysisInfo, + Dialect const& _dialect, + Block const& _ast + ); + + void operator()(Literal const&) override; + void operator()(Identifier const&) override; + void operator()(FunctionCall const& _funCall) override; + void operator()(ExpressionStatement const& _statement) override; + void operator()(Assignment const& _assignment) override; + void operator()(VariableDeclaration const& _varDecl) override; + void operator()(If const& _if) override; + void operator()(Switch const& _switch) override; + void operator()(ForLoop const&) override; + void operator()(FunctionDefinition const&) override; + void operator()(Break const&) override; + void operator()(Continue const&) override; + void operator()(Leave const&) override; + void operator()(Block const& _block) override; + + void visit(Statement const& _stmt) override; + using ASTWalker::visit; +private: + DirectEVMCodeTransform( + AbstractAssembly& _assembly, + BuiltinContext& _builtinContext, + UseNamedLabels _useNamedLabelsForFunctions, + DirectStackLayoutGenerator::Context const& _context, + AsmAnalysisInfo const& _analysisInfo, + Dialect const& _dialect + ): + m_assembly(_assembly), + m_builtinContext(_builtinContext), + m_useNamedLabelsForFunctions(_useNamedLabelsForFunctions), + m_context(_context), + m_info(_analysisInfo), + m_dialect(_dialect) + {} + + /// Shuffles m_stack to the desired @a _targetStack while emitting the shuffling code to m_assembly. + /// Sets the source locations to the one in @a _debugData. + void createStackLayout(std::shared_ptr _debugData, Stack _targetStack); + + AbstractAssembly& m_assembly; + BuiltinContext& m_builtinContext; + UseNamedLabels m_useNamedLabelsForFunctions; + DirectStackLayoutGenerator::Context const& m_context; + AsmAnalysisInfo const& m_info; + Dialect const& m_dialect; + Stack m_stack; + CFG::FunctionInfo const* m_currentFunctionInfo = nullptr; + std::map m_returnLabels; + std::map m_blockLabels; + std::map const m_functionLabels; + std::vector m_stackErrors; +}; + +} \ No newline at end of file diff --git a/libyul/backends/evm/DirectStackLayoutGenerator.cpp b/libyul/backends/evm/DirectStackLayoutGenerator.cpp index 7a84dd2d6..e923e8588 100644 --- a/libyul/backends/evm/DirectStackLayoutGenerator.cpp +++ b/libyul/backends/evm/DirectStackLayoutGenerator.cpp @@ -22,63 +22,266 @@ #include #include +#include + +#include +#include #include +#include #include +#include using namespace solidity; using namespace solidity::yul; using namespace std; -void DirectStackLayoutGenerator::run(Block const& _block) +DirectStackLayoutGenerator::Context DirectStackLayoutGenerator::run(AsmAnalysisInfo const& _analysisInfo, Dialect const& _dialect, Block const& _block) { + /* yulAssert(_block.statements.size() > 0, ""); yulAssert(holds_alternative(_block.statements.front()), ""); { - DirectStackLayoutGenerator generator; + DirectStackLayoutGenerator generator{_analysisInfo, _dialect}; generator(get(_block.statements.front())); } for (Statement const& statement: _block.statements | ranges::views::drop_exactly(1)) { FunctionDefinition const* functionDefinition = get_if(&statement); yulAssert(functionDefinition, ""); - DirectStackLayoutGenerator generator; + DirectStackLayoutGenerator generator{_analysisInfo, _dialect}; generator(*functionDefinition); - } + }*/ + Context context; + DirectStackLayoutGenerator generator{context, _analysisInfo, _dialect}; + generator(_block); + return context; } void DirectStackLayoutGenerator::operator()(Block const& _block) { + ScopedSaveAndRestore saveScope(m_scope, m_info.scopes.at(&_block).get()); + for (auto const& statement: _block.statements) + if (auto const* function = get_if(&statement)) + registerFunction(*function); for (Statement const& statement: _block.statements | ranges::views::reverse) visit(statement); + + m_context.layout.blockInfos[&_block].entry = m_stack; } void DirectStackLayoutGenerator::operator()(VariableDeclaration const& _variableDeclaration) { + auto declaredVariables = _variableDeclaration.variables | ranges::views::transform([&](TypedName const& _var) { + return VariableSlot{lookupVariable(_var.name), _var.debugData}; + }) | ranges::to>; + visitAssignmentOrDeclaration( + debugDataOf(_variableDeclaration), + declaredVariables, + _variableDeclaration.value ? _variableDeclaration.value.get() : nullptr + ); } void DirectStackLayoutGenerator::operator()(Assignment const& _assignment) { + auto assignedVariables = _assignment.variableNames | ranges::views::transform([&](Identifier const& _var) { + return VariableSlot{lookupVariable(_var.name), _var.debugData}; + }) | ranges::to>; + visitAssignmentOrDeclaration( + debugDataOf(_assignment), + assignedVariables, + _assignment.value.get() + ); +} +void DirectStackLayoutGenerator::operator()(Literal const& _literal) +{ + m_stack.emplace_back(LiteralSlot{valueOfLiteral(_literal), debugDataOf(_literal)}); +} + +void DirectStackLayoutGenerator::operator()(Identifier const& _identifier) +{ + m_stack.emplace_back(VariableSlot{lookupVariable(_identifier.name), debugDataOf(_identifier)}); +} + +void DirectStackLayoutGenerator::operator()(FunctionCall const& _funCall) +{ + yulAssert(visitFunctionCall(_funCall) == 1, ""); } void DirectStackLayoutGenerator::operator()(ExpressionStatement const& _expressionStatement) { - + std::visit(util::GenericVisitor{ + [&](FunctionCall const& _call) { + yulAssert(visitFunctionCall(_call) == 0, ""); + }, + [&](auto const&) { yulAssert(false, ""); } + }, _expressionStatement.expression); } void DirectStackLayoutGenerator::operator()(If const& _if) { + DirectStackLayoutGenerator bodyGenerator{m_context, m_info, m_dialect, m_stack}; + bodyGenerator(_if.body); + m_stack += move(bodyGenerator.m_stack); + visit(*_if.condition); } void DirectStackLayoutGenerator::operator()(Switch const& _switch) { + Stack combinedStack; + for (auto const& switchCase: _switch.cases) + { + DirectStackLayoutGenerator bodyGenerator{m_context, m_info, m_dialect, m_stack}; + bodyGenerator(switchCase.body); + for (auto const& slot: bodyGenerator.m_stack) + if (!util::contains(combinedStack, slot)) + combinedStack.emplace_back(slot); + } + m_stack += move(combinedStack); + visit(*_switch.expression); } void DirectStackLayoutGenerator::operator()(ForLoop const& _forLoop) { + yulAssert(_forLoop.pre.statements.empty(), ""); + DirectStackLayoutGenerator bodyGenerator{m_context, m_info, m_dialect, m_stack}; + bodyGenerator(_forLoop.post); + bodyGenerator(_forLoop.body); + m_stack += move(bodyGenerator.m_stack); + visit(*_forLoop.condition); } -void DirectStackLayoutGenerator::operator()(FunctionDefinition const& _functionDefinition) +void DirectStackLayoutGenerator::operator()(FunctionDefinition const& _function) { -} \ No newline at end of file + yulAssert(m_scope, ""); + yulAssert(m_scope->identifiers.count(_function.name), ""); + Scope::Function& function = std::get(m_scope->identifiers.at(_function.name)); + + auto const& functionInfo = m_context.functionInfo.at(&function); + + yulAssert(m_info.scopes.at(&_function.body), ""); + Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); + yulAssert(virtualFunctionScope, ""); + + DirectStackLayoutGenerator bodyGenerator{m_context, m_info, m_dialect, m_stack}; + bodyGenerator.m_stack = functionInfo.returnVariables | ranges::views::transform([](auto const& _varSlot){ + return StackSlot{_varSlot}; + }) | ranges::to; + bodyGenerator.m_stack.emplace_back(FunctionReturnLabelSlot{function}); + bodyGenerator(_function.body); +} + +void DirectStackLayoutGenerator::visit(Statement const& _statement) +{ + ASTWalker::visit(_statement); + m_context.layout.statementInfos[&_statement] = m_stack; +} + +size_t DirectStackLayoutGenerator::visitFunctionCall(FunctionCall const& _call) +{ + if (BuiltinFunction const* builtin = m_dialect.builtin(_call.functionName.name)) + { + for (auto&& [idx, arg]: _call.arguments | ranges::views::enumerate | ranges::views::reverse) + if (!builtin->literalArgument(idx).has_value()) + std::visit(*this, arg); + return builtin->returns.size(); + } + else + { + m_stack.emplace_back(FunctionCallReturnLabelSlot{_call}); + for (auto const& arg: _call.arguments | ranges::views::reverse) + std::visit(*this, arg); + Scope::Function const& function = lookupFunction(_call.functionName.name); + return function.returns.size(); + } +} + +void DirectStackLayoutGenerator::visitAssignmentOrDeclaration( + std::shared_ptr _debugData, + vector const& _variables, + Expression const* _expression +) +{ + // TODO: reproduce proper createIdealLayout here + cxx20::erase_if(m_stack, [&](StackSlot const& slot) { + if (auto const* varSlot = get_if(&slot)) + if (util::contains(_variables, *varSlot)) + return true; + return false; + }); + if (!_expression) + { + m_stack += Stack(_variables.size(), LiteralSlot{0, _debugData}); + return; + } + + if (_variables.size() == 1 && !holds_alternative(*_expression)) + { + visit(*_expression); + return; + } + + auto const* call = get_if(_expression); + yulAssert(call, ""); + yulAssert(visitFunctionCall(*call) == _variables.size(), ""); +} + +Scope::Function const& DirectStackLayoutGenerator::lookupFunction(YulString _name) const +{ + Scope::Function const* function = nullptr; + yulAssert(m_scope->lookup(_name, util::GenericVisitor{ + [](Scope::Variable&) { yulAssert(false, "Expected function name."); }, + [&](Scope::Function& _function) { function = &_function; } + }), "Function name not found."); + yulAssert(function, ""); + return *function; +} + +Scope::Variable const& DirectStackLayoutGenerator::lookupVariable(YulString _name) const +{ + yulAssert(m_scope, ""); + Scope::Variable const* var = nullptr; + if (m_scope->lookup(_name, util::GenericVisitor{ + [&](Scope::Variable& _var) { var = &_var; }, + [](Scope::Function&) + { + yulAssert(false, "Function not removed during desugaring."); + } + })) + { + yulAssert(var, ""); + return *var; + }; + yulAssert(false, "External identifier access unimplemented."); +} + +void DirectStackLayoutGenerator::registerFunction(FunctionDefinition const& _function) +{ + yulAssert(m_scope, ""); + yulAssert(m_scope->identifiers.count(_function.name), ""); + Scope::Function& function = std::get(m_scope->identifiers.at(_function.name)); + m_context.functionList.emplace_back(&function); + + yulAssert(m_info.scopes.at(&_function.body), ""); + Scope* virtualFunctionScope = m_info.scopes.at(m_info.virtualBlocks.at(&_function).get()).get(); + yulAssert(virtualFunctionScope, ""); + + bool inserted = m_context.functionInfo.emplace(std::make_pair(&function, FunctionInfo{ + _function.debugData, + function, + _function.parameters | ranges::views::transform([&](auto const& _param) { + return VariableSlot{ + std::get(virtualFunctionScope->identifiers.at(_param.name)), + _param.debugData + }; + }) | ranges::to, + _function.returnVariables | ranges::views::transform([&](auto const& _retVar) { + return VariableSlot{ + std::get(virtualFunctionScope->identifiers.at(_retVar.name)), + _retVar.debugData + }; + }) | ranges::to + })).second; + yulAssert(inserted); +} diff --git a/libyul/backends/evm/DirectStackLayoutGenerator.h b/libyul/backends/evm/DirectStackLayoutGenerator.h index 352051e66..b0f8ea508 100644 --- a/libyul/backends/evm/DirectStackLayoutGenerator.h +++ b/libyul/backends/evm/DirectStackLayoutGenerator.h @@ -25,42 +25,75 @@ #include +#include + namespace solidity::yul { struct StackLayout { - struct StackInfo + struct BlockInfo { Stack entry; - Stack exit; }; - std::map blockInfos; - std::map statementInfos; + std::map blockInfos; + std::map statementInfos; }; class DirectStackLayoutGenerator: public ASTWalker { public: - static void run(Block const& _ast); + struct FunctionInfo { + std::shared_ptr debugData; + Scope::Function const& function; + std::vector parameters; + std::vector returnVariables; + }; + struct Context { + StackLayout layout; + std::vector functionList; + std::map functionInfo; + }; + static Context run(AsmAnalysisInfo const& _analysisInfo, Dialect const& _dialect, Block const& _ast); + + void operator()(Literal const&) override; + void operator()(Identifier const&) override; + void operator()(FunctionCall const& _funCall) override; + void operator()(ExpressionStatement const& _statement) override; + void operator()(Assignment const& _assignment) override; + void operator()(VariableDeclaration const& _varDecl) override; + void operator()(If const& _if) override; + void operator()(Switch const& _switch) override; + void operator()(ForLoop const&) override; + void operator()(FunctionDefinition const&) override; + void operator()(Break const&) override; + void operator()(Continue const&) override; + void operator()(Leave const&) override; + void operator()(Block const& _block) override; + + void visit(Statement const& _stmt) override; + using ASTWalker::visit; - virtual void operator()(Literal const&) {} - virtual void operator()(Identifier const&) {} - virtual void operator()(FunctionCall const& _funCall); - virtual void operator()(ExpressionStatement const& _statement); - virtual void operator()(Assignment const& _assignment); - virtual void operator()(VariableDeclaration const& _varDecl); - virtual void operator()(If const& _if); - virtual void operator()(Switch const& _switch); - virtual void operator()(ForLoop const&); - virtual void operator()(FunctionDefinition const&); - virtual void operator()(Break const&); - virtual void operator()(Continue const&); - virtual void operator()(Leave const&); - virtual void operator()(Block const& _block); private: - DirectStackLayoutGenerator() {} + DirectStackLayoutGenerator(Context& _context, AsmAnalysisInfo const& _info, Dialect const& _dialect, Stack _prefix = {}): + m_context(_context), m_info(_info), m_dialect(_dialect), m_prefix(std::move(_prefix)) {} + + size_t visitFunctionCall(FunctionCall const&); + void visitAssignmentOrDeclaration( + std::shared_ptr _debugData, + std::vector const& _variables, + Expression const* _expression + ); + Scope::Function const& lookupFunction(YulString _name) const; + Scope::Variable const& lookupVariable(YulString _name) const; + void registerFunction(FunctionDefinition const& _function); + + Context& m_context; + AsmAnalysisInfo const& m_info; + Dialect const& m_dialect; + Scope* m_scope = nullptr; + Stack m_prefix; Stack m_stack; };