aboutsummaryrefslogtreecommitdiffstats
path: root/libyul
diff options
context:
space:
mode:
Diffstat (limited to 'libyul')
-rw-r--r--libyul/AsmAnalysis.cpp631
-rw-r--r--libyul/AsmAnalysis.h128
-rw-r--r--libyul/AsmAnalysisInfo.cpp26
-rw-r--r--libyul/AsmAnalysisInfo.h52
-rw-r--r--libyul/AsmCodeGen.cpp162
-rw-r--r--libyul/AsmCodeGen.h56
-rw-r--r--libyul/AsmData.h104
-rw-r--r--libyul/AsmDataForward.h65
-rw-r--r--libyul/AsmParser.cpp617
-rw-r--r--libyul/AsmParser.h95
-rw-r--r--libyul/AsmPrinter.cpp250
-rw-r--r--libyul/AsmPrinter.h68
-rw-r--r--libyul/AsmScope.cpp98
-rw-r--r--libyul/AsmScope.h105
-rw-r--r--libyul/AsmScopeFiller.cpp181
-rw-r--r--libyul/AsmScopeFiller.h88
16 files changed, 2726 insertions, 0 deletions
diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp
new file mode 100644
index 00000000..fb96f73c
--- /dev/null
+++ b/libyul/AsmAnalysis.cpp
@@ -0,0 +1,631 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Analyzer part of inline assembly.
+ */
+
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScopeFiller.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <liblangutil/ErrorReporter.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <memory>
+#include <functional>
+
+using namespace std;
+using namespace dev;
+using namespace langutil;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+namespace {
+
+set<string> const builtinTypes{"bool", "u8", "s8", "u32", "s32", "u64", "s64", "u128", "s128", "u256", "s256"};
+
+}
+
+bool AsmAnalyzer::analyze(Block const& _block)
+{
+ if (!(ScopeFiller(m_info, m_errorReporter))(_block))
+ return false;
+
+ return (*this)(_block);
+}
+
+bool AsmAnalyzer::operator()(Label const& _label)
+{
+ solAssert(!_label.name.empty(), "");
+ checkLooseFeature(
+ _label.location,
+ "The use of labels is disallowed. Please use \"if\", \"switch\", \"for\" or function calls instead."
+ );
+ m_info.stackHeightInfo[&_label] = m_stackHeight;
+ warnOnInstructions(solidity::Instruction::JUMPDEST, _label.location);
+ return true;
+}
+
+bool AsmAnalyzer::operator()(assembly::Instruction const& _instruction)
+{
+ checkLooseFeature(
+ _instruction.location,
+ "The use of non-functional instructions is disallowed. Please use functional notation instead."
+ );
+ auto const& info = instructionInfo(_instruction.instruction);
+ m_stackHeight += info.ret - info.args;
+ m_info.stackHeightInfo[&_instruction] = m_stackHeight;
+ warnOnInstructions(_instruction.instruction, _instruction.location);
+ return true;
+}
+
+bool AsmAnalyzer::operator()(assembly::Literal const& _literal)
+{
+ expectValidType(_literal.type.str(), _literal.location);
+ ++m_stackHeight;
+ if (_literal.kind == assembly::LiteralKind::String && _literal.value.str().size() > 32)
+ {
+ m_errorReporter.typeError(
+ _literal.location,
+ "String literal too long (" + to_string(_literal.value.str().size()) + " > 32)"
+ );
+ return false;
+ }
+ else if (_literal.kind == assembly::LiteralKind::Number && bigint(_literal.value.str()) > u256(-1))
+ {
+ m_errorReporter.typeError(
+ _literal.location,
+ "Number literal too large (> 256 bits)"
+ );
+ return false;
+ }
+ else if (_literal.kind == assembly::LiteralKind::Boolean)
+ {
+ solAssert(m_flavour == AsmFlavour::Yul, "");
+ solAssert(_literal.value == YulString{string("true")} || _literal.value == YulString{string("false")}, "");
+ }
+ m_info.stackHeightInfo[&_literal] = m_stackHeight;
+ return true;
+}
+
+bool AsmAnalyzer::operator()(assembly::Identifier const& _identifier)
+{
+ solAssert(!_identifier.name.empty(), "");
+ size_t numErrorsBefore = m_errorReporter.errors().size();
+ bool success = true;
+ if (m_currentScope->lookup(_identifier.name, Scope::Visitor(
+ [&](Scope::Variable const& _var)
+ {
+ if (!m_activeVariables.count(&_var))
+ {
+ m_errorReporter.declarationError(
+ _identifier.location,
+ "Variable " + _identifier.name.str() + " used before it was declared."
+ );
+ success = false;
+ }
+ ++m_stackHeight;
+ },
+ [&](Scope::Label const&)
+ {
+ ++m_stackHeight;
+ },
+ [&](Scope::Function const&)
+ {
+ m_errorReporter.typeError(
+ _identifier.location,
+ "Function " + _identifier.name.str() + " used without being called."
+ );
+ success = false;
+ }
+ )))
+ {
+ }
+ else
+ {
+ size_t stackSize(-1);
+ if (m_resolver)
+ {
+ bool insideFunction = m_currentScope->insideFunction();
+ stackSize = m_resolver(_identifier, yul::IdentifierContext::RValue, insideFunction);
+ }
+ if (stackSize == size_t(-1))
+ {
+ // Only add an error message if the callback did not do it.
+ if (numErrorsBefore == m_errorReporter.errors().size())
+ m_errorReporter.declarationError(_identifier.location, "Identifier not found.");
+ success = false;
+ }
+ m_stackHeight += stackSize == size_t(-1) ? 1 : stackSize;
+ }
+ m_info.stackHeightInfo[&_identifier] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(FunctionalInstruction const& _instr)
+{
+ solAssert(m_flavour != AsmFlavour::Yul, "");
+ bool success = true;
+ for (auto const& arg: _instr.arguments | boost::adaptors::reversed)
+ if (!expectExpression(arg))
+ success = false;
+ // Parser already checks that the number of arguments is correct.
+ auto const& info = instructionInfo(_instr.instruction);
+ solAssert(info.args == int(_instr.arguments.size()), "");
+ m_stackHeight += info.ret - info.args;
+ m_info.stackHeightInfo[&_instr] = m_stackHeight;
+ warnOnInstructions(_instr.instruction, _instr.location);
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::ExpressionStatement const& _statement)
+{
+ int initialStackHeight = m_stackHeight;
+ bool success = boost::apply_visitor(*this, _statement.expression);
+ if (m_stackHeight != initialStackHeight && (m_flavour != AsmFlavour::Loose || m_errorTypeForLoose))
+ {
+ Error::Type errorType = m_flavour == AsmFlavour::Loose ? *m_errorTypeForLoose : Error::Type::TypeError;
+ string msg =
+ "Top-level expressions are not supposed to return values (this expression returns " +
+ to_string(m_stackHeight - initialStackHeight) +
+ " value" +
+ (m_stackHeight - initialStackHeight == 1 ? "" : "s") +
+ "). Use ``pop()`` or assign them.";
+ m_errorReporter.error(errorType, _statement.location, msg);
+ if (errorType != Error::Type::Warning)
+ success = false;
+ }
+ m_info.stackHeightInfo[&_statement] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::StackAssignment const& _assignment)
+{
+ checkLooseFeature(
+ _assignment.location,
+ "The use of stack assignment is disallowed. Please use assignment in functional notation instead."
+ );
+ bool success = checkAssignment(_assignment.variableName, size_t(-1));
+ m_info.stackHeightInfo[&_assignment] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::Assignment const& _assignment)
+{
+ solAssert(_assignment.value, "");
+ int const expectedItems = _assignment.variableNames.size();
+ solAssert(expectedItems >= 1, "");
+ int const stackHeight = m_stackHeight;
+ bool success = boost::apply_visitor(*this, *_assignment.value);
+ if ((m_stackHeight - stackHeight) != expectedItems)
+ {
+ m_errorReporter.declarationError(
+ _assignment.location,
+ "Variable count does not match number of values (" +
+ to_string(expectedItems) +
+ " vs. " +
+ to_string(m_stackHeight - stackHeight) +
+ ")"
+ );
+ return false;
+ }
+ for (auto const& variableName: _assignment.variableNames)
+ if (!checkAssignment(variableName, 1))
+ success = false;
+ m_info.stackHeightInfo[&_assignment] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::VariableDeclaration const& _varDecl)
+{
+ bool success = true;
+ int const numVariables = _varDecl.variables.size();
+ if (_varDecl.value)
+ {
+ int const stackHeight = m_stackHeight;
+ success = boost::apply_visitor(*this, *_varDecl.value);
+ if ((m_stackHeight - stackHeight) != numVariables)
+ {
+ m_errorReporter.declarationError(_varDecl.location, "Variable count mismatch.");
+ return false;
+ }
+ }
+ else
+ m_stackHeight += numVariables;
+
+ for (auto const& variable: _varDecl.variables)
+ {
+ expectValidType(variable.type.str(), variable.location);
+ m_activeVariables.insert(&boost::get<Scope::Variable>(m_currentScope->identifiers.at(variable.name)));
+ }
+ m_info.stackHeightInfo[&_varDecl] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::FunctionDefinition const& _funDef)
+{
+ solAssert(!_funDef.name.empty(), "");
+ Block const* virtualBlock = m_info.virtualBlocks.at(&_funDef).get();
+ solAssert(virtualBlock, "");
+ Scope& varScope = scope(virtualBlock);
+ for (auto const& var: _funDef.parameters + _funDef.returnVariables)
+ {
+ expectValidType(var.type.str(), var.location);
+ m_activeVariables.insert(&boost::get<Scope::Variable>(varScope.identifiers.at(var.name)));
+ }
+
+ int const stackHeight = m_stackHeight;
+ m_stackHeight = _funDef.parameters.size() + _funDef.returnVariables.size();
+
+ bool success = (*this)(_funDef.body);
+
+ m_stackHeight = stackHeight;
+ m_info.stackHeightInfo[&_funDef] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::FunctionCall const& _funCall)
+{
+ solAssert(!_funCall.functionName.name.empty(), "");
+ bool success = true;
+ size_t arguments = 0;
+ size_t returns = 0;
+ if (!m_currentScope->lookup(_funCall.functionName.name, Scope::Visitor(
+ [&](Scope::Variable const&)
+ {
+ m_errorReporter.typeError(
+ _funCall.functionName.location,
+ "Attempt to call variable instead of function."
+ );
+ success = false;
+ },
+ [&](Scope::Label const&)
+ {
+ m_errorReporter.typeError(
+ _funCall.functionName.location,
+ "Attempt to call label instead of function."
+ );
+ success = false;
+ },
+ [&](Scope::Function const& _fun)
+ {
+ /// TODO: compare types too
+ arguments = _fun.arguments.size();
+ returns = _fun.returns.size();
+ }
+ )))
+ {
+ m_errorReporter.declarationError(_funCall.functionName.location, "Function not found.");
+ success = false;
+ }
+ if (success)
+ {
+ if (_funCall.arguments.size() != arguments)
+ {
+ m_errorReporter.typeError(
+ _funCall.functionName.location,
+ "Expected " + to_string(arguments) + " arguments but got " +
+ to_string(_funCall.arguments.size()) + "."
+ );
+ success = false;
+ }
+ }
+ for (auto const& arg: _funCall.arguments | boost::adaptors::reversed)
+ if (!expectExpression(arg))
+ success = false;
+ m_stackHeight += int(returns) - int(arguments);
+ m_info.stackHeightInfo[&_funCall] = m_stackHeight;
+ return success;
+}
+
+bool AsmAnalyzer::operator()(If const& _if)
+{
+ bool success = true;
+
+ if (!expectExpression(*_if.condition))
+ success = false;
+ m_stackHeight--;
+
+ if (!(*this)(_if.body))
+ success = false;
+
+ m_info.stackHeightInfo[&_if] = m_stackHeight;
+
+ return success;
+}
+
+bool AsmAnalyzer::operator()(Switch const& _switch)
+{
+ solAssert(_switch.expression, "");
+
+ bool success = true;
+
+ if (!expectExpression(*_switch.expression))
+ success = false;
+
+ set<tuple<LiteralKind, YulString>> cases;
+ for (auto const& _case: _switch.cases)
+ {
+ if (_case.value)
+ {
+ int const initialStackHeight = m_stackHeight;
+ // We cannot use "expectExpression" here because *_case.value is not a
+ // Statement and would be converted to a Statement otherwise.
+ if (!(*this)(*_case.value))
+ success = false;
+ expectDeposit(1, initialStackHeight, _case.value->location);
+ m_stackHeight--;
+
+ /// Note: the parser ensures there is only one default case
+ auto val = make_tuple(_case.value->kind, _case.value->value);
+ if (!cases.insert(val).second)
+ {
+ m_errorReporter.declarationError(
+ _case.location,
+ "Duplicate case defined"
+ );
+ success = false;
+ }
+ }
+
+ if (!(*this)(_case.body))
+ success = false;
+ }
+
+ m_stackHeight--;
+ m_info.stackHeightInfo[&_switch] = m_stackHeight;
+
+ return success;
+}
+
+bool AsmAnalyzer::operator()(assembly::ForLoop const& _for)
+{
+ solAssert(_for.condition, "");
+
+ Scope* originalScope = m_currentScope;
+
+ bool success = true;
+ if (!(*this)(_for.pre))
+ success = false;
+ // The block was closed already, but we re-open it again and stuff the
+ // condition, the body and the post part inside.
+ m_stackHeight += scope(&_for.pre).numberOfVariables();
+ m_currentScope = &scope(&_for.pre);
+
+ if (!expectExpression(*_for.condition))
+ success = false;
+ m_stackHeight--;
+ if (!(*this)(_for.body))
+ success = false;
+ if (!(*this)(_for.post))
+ success = false;
+
+ m_stackHeight -= scope(&_for.pre).numberOfVariables();
+ m_info.stackHeightInfo[&_for] = m_stackHeight;
+ m_currentScope = originalScope;
+
+ return success;
+}
+
+bool AsmAnalyzer::operator()(Block const& _block)
+{
+ bool success = true;
+ auto previousScope = m_currentScope;
+ m_currentScope = &scope(&_block);
+
+ int const initialStackHeight = m_stackHeight;
+
+ for (auto const& s: _block.statements)
+ if (!boost::apply_visitor(*this, s))
+ success = false;
+
+ m_stackHeight -= scope(&_block).numberOfVariables();
+
+ int const stackDiff = m_stackHeight - initialStackHeight;
+ if (stackDiff != 0)
+ {
+ m_errorReporter.declarationError(
+ _block.location,
+ "Unbalanced stack at the end of a block: " +
+ (
+ stackDiff > 0 ?
+ to_string(stackDiff) + string(" surplus item(s).") :
+ to_string(-stackDiff) + string(" missing item(s).")
+ )
+ );
+ success = false;
+ }
+
+ m_info.stackHeightInfo[&_block] = m_stackHeight;
+ m_currentScope = previousScope;
+ return success;
+}
+
+bool AsmAnalyzer::expectExpression(Expression const& _expr)
+{
+ bool success = true;
+ int const initialHeight = m_stackHeight;
+ if (!boost::apply_visitor(*this, _expr))
+ success = false;
+ if (!expectDeposit(1, initialHeight, locationOf(_expr)))
+ success = false;
+ return success;
+}
+
+bool AsmAnalyzer::expectDeposit(int _deposit, int _oldHeight, SourceLocation const& _location)
+{
+ if (m_stackHeight - _oldHeight != _deposit)
+ {
+ m_errorReporter.typeError(
+ _location,
+ "Expected expression to return one item to the stack, but did return " +
+ to_string(m_stackHeight - _oldHeight) +
+ " items."
+ );
+ return false;
+ }
+ return true;
+}
+
+bool AsmAnalyzer::checkAssignment(assembly::Identifier const& _variable, size_t _valueSize)
+{
+ solAssert(!_variable.name.empty(), "");
+ bool success = true;
+ size_t numErrorsBefore = m_errorReporter.errors().size();
+ size_t variableSize(-1);
+ if (Scope::Identifier const* var = m_currentScope->lookup(_variable.name))
+ {
+ // Check that it is a variable
+ if (var->type() != typeid(Scope::Variable))
+ {
+ m_errorReporter.typeError(_variable.location, "Assignment requires variable.");
+ success = false;
+ }
+ else if (!m_activeVariables.count(&boost::get<Scope::Variable>(*var)))
+ {
+ m_errorReporter.declarationError(
+ _variable.location,
+ "Variable " + _variable.name.str() + " used before it was declared."
+ );
+ success = false;
+ }
+ variableSize = 1;
+ }
+ else if (m_resolver)
+ {
+ bool insideFunction = m_currentScope->insideFunction();
+ variableSize = m_resolver(_variable, yul::IdentifierContext::LValue, insideFunction);
+ }
+ if (variableSize == size_t(-1))
+ {
+ // Only add message if the callback did not.
+ if (numErrorsBefore == m_errorReporter.errors().size())
+ m_errorReporter.declarationError(_variable.location, "Variable not found or variable not lvalue.");
+ success = false;
+ }
+ if (_valueSize == size_t(-1))
+ _valueSize = variableSize == size_t(-1) ? 1 : variableSize;
+
+ m_stackHeight -= _valueSize;
+
+ if (_valueSize != variableSize && variableSize != size_t(-1))
+ {
+ m_errorReporter.typeError(
+ _variable.location,
+ "Variable size (" +
+ to_string(variableSize) +
+ ") and value size (" +
+ to_string(_valueSize) +
+ ") do not match."
+ );
+ success = false;
+ }
+ return success;
+}
+
+Scope& AsmAnalyzer::scope(Block const* _block)
+{
+ solAssert(m_info.scopes.count(_block) == 1, "Scope requested but not present.");
+ auto scopePtr = m_info.scopes.at(_block);
+ solAssert(scopePtr, "Scope requested but not present.");
+ return *scopePtr;
+}
+void AsmAnalyzer::expectValidType(string const& type, SourceLocation const& _location)
+{
+ if (m_flavour != AsmFlavour::Yul)
+ return;
+
+ if (!builtinTypes.count(type))
+ m_errorReporter.typeError(
+ _location,
+ "\"" + type + "\" is not a valid type (user defined types are not yet supported)."
+ );
+}
+
+void AsmAnalyzer::warnOnInstructions(solidity::Instruction _instr, SourceLocation const& _location)
+{
+ // We assume that returndatacopy, returndatasize and staticcall are either all available
+ // or all not available.
+ solAssert(m_evmVersion.supportsReturndata() == m_evmVersion.hasStaticCall(), "");
+ // Similarly we assume bitwise shifting and create2 go together.
+ solAssert(m_evmVersion.hasBitwiseShifting() == m_evmVersion.hasCreate2(), "");
+
+ if (_instr == solidity::Instruction::EXTCODEHASH)
+ m_errorReporter.warning(
+ _location,
+ "The \"" +
+ boost::to_lower_copy(instructionInfo(_instr).name)
+ + "\" instruction is not supported by the VM version \"" +
+ "" + m_evmVersion.name() +
+ "\" you are currently compiling for. " +
+ "It will be interpreted as an invalid instruction on this VM."
+ );
+ else if ((
+ _instr == solidity::Instruction::RETURNDATACOPY ||
+ _instr == solidity::Instruction::RETURNDATASIZE ||
+ _instr == solidity::Instruction::STATICCALL
+ ) && !m_evmVersion.supportsReturndata())
+ m_errorReporter.warning(
+ _location,
+ "The \"" +
+ boost::to_lower_copy(instructionInfo(_instr).name)
+ + "\" instruction is only available for Byzantium-compatible VMs. " +
+ "You are currently compiling for \"" +
+ m_evmVersion.name() +
+ "\", where it will be interpreted as an invalid instruction."
+ );
+ else if ((
+ _instr == solidity::Instruction::SHL ||
+ _instr == solidity::Instruction::SHR ||
+ _instr == solidity::Instruction::SAR ||
+ _instr == solidity::Instruction::CREATE2
+ ) && !m_evmVersion.hasBitwiseShifting())
+ m_errorReporter.warning(
+ _location,
+ "The \"" +
+ boost::to_lower_copy(instructionInfo(_instr).name)
+ + "\" instruction is only available for Constantinople-compatible VMs. " +
+ "You are currently compiling for \"" +
+ m_evmVersion.name() +
+ "\", where it will be interpreted as an invalid instruction."
+ );
+
+ if (_instr == solidity::Instruction::JUMP || _instr == solidity::Instruction::JUMPI || _instr == solidity::Instruction::JUMPDEST)
+ {
+ solAssert(m_flavour == AsmFlavour::Loose, "");
+ m_errorReporter.error(
+ m_errorTypeForLoose ? *m_errorTypeForLoose : Error::Type::Warning,
+ _location,
+ "Jump instructions and labels are low-level EVM features that can lead to "
+ "incorrect stack access. Because of that they are discouraged. "
+ "Please consider using \"switch\", \"if\" or \"for\" statements instead."
+ );
+ }
+}
+
+void AsmAnalyzer::checkLooseFeature(SourceLocation const& _location, string const& _description)
+{
+ if (m_flavour != AsmFlavour::Loose)
+ solAssert(false, _description);
+ else if (m_errorTypeForLoose)
+ m_errorReporter.error(*m_errorTypeForLoose, _location, _description);
+}
diff --git a/libyul/AsmAnalysis.h b/libyul/AsmAnalysis.h
new file mode 100644
index 00000000..194f736e
--- /dev/null
+++ b/libyul/AsmAnalysis.h
@@ -0,0 +1,128 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Analysis part of inline assembly.
+ */
+
+#pragma once
+
+#include <liblangutil/Exceptions.h>
+#include <liblangutil/EVMVersion.h>
+
+#include <libsolidity/inlineasm/AsmScope.h>
+
+#include <libyul/backends/evm/AbstractAssembly.h>
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <boost/variant.hpp>
+#include <boost/optional.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace langutil
+{
+class ErrorReporter;
+struct SourceLocation;
+}
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+struct AsmAnalysisInfo;
+
+/**
+ * Performs the full analysis stage, calls the ScopeFiller internally, then resolves
+ * references and performs other checks.
+ * If all these checks pass, code generation should not throw errors.
+ */
+class AsmAnalyzer: public boost::static_visitor<bool>
+{
+public:
+ explicit AsmAnalyzer(
+ AsmAnalysisInfo& _analysisInfo,
+ langutil::ErrorReporter& _errorReporter,
+ EVMVersion _evmVersion,
+ boost::optional<langutil::Error::Type> _errorTypeForLoose,
+ AsmFlavour _flavour = AsmFlavour::Loose,
+ yul::ExternalIdentifierAccess::Resolver const& _resolver = yul::ExternalIdentifierAccess::Resolver()
+ ):
+ m_resolver(_resolver),
+ m_info(_analysisInfo),
+ m_errorReporter(_errorReporter),
+ m_evmVersion(_evmVersion),
+ m_flavour(_flavour),
+ m_errorTypeForLoose(_errorTypeForLoose)
+ {}
+
+ bool analyze(assembly::Block const& _block);
+
+ bool operator()(assembly::Instruction const&);
+ bool operator()(assembly::Literal const& _literal);
+ bool operator()(assembly::Identifier const&);
+ bool operator()(assembly::FunctionalInstruction const& _functionalInstruction);
+ bool operator()(assembly::Label const& _label);
+ bool operator()(assembly::ExpressionStatement const&);
+ bool operator()(assembly::StackAssignment const&);
+ bool operator()(assembly::Assignment const& _assignment);
+ bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
+ bool operator()(assembly::FunctionDefinition const& _functionDefinition);
+ bool operator()(assembly::FunctionCall const& _functionCall);
+ bool operator()(assembly::If const& _if);
+ bool operator()(assembly::Switch const& _switch);
+ bool operator()(assembly::ForLoop const& _forLoop);
+ bool operator()(assembly::Block const& _block);
+
+private:
+ /// Visits the statement and expects it to deposit one item onto the stack.
+ bool expectExpression(Expression const& _expr);
+ bool expectDeposit(int _deposit, int _oldHeight, langutil::SourceLocation const& _location);
+
+ /// Verifies that a variable to be assigned to exists and has the same size
+ /// as the value, @a _valueSize, unless that is equal to -1.
+ bool checkAssignment(assembly::Identifier const& _assignment, size_t _valueSize = size_t(-1));
+
+ Scope& scope(assembly::Block const* _block);
+ void expectValidType(std::string const& type, langutil::SourceLocation const& _location);
+ void warnOnInstructions(solidity::Instruction _instr, langutil::SourceLocation const& _location);
+
+ /// Depending on @a m_flavour and @a m_errorTypeForLoose, throws an internal compiler
+ /// exception (if the flavour is not Loose), reports an error/warning
+ /// (if m_errorTypeForLoose is set) or does nothing.
+ void checkLooseFeature(langutil::SourceLocation const& _location, std::string const& _description);
+
+ int m_stackHeight = 0;
+ yul::ExternalIdentifierAccess::Resolver m_resolver;
+ Scope* m_currentScope = nullptr;
+ /// Variables that are active at the current point in assembly (as opposed to
+ /// "part of the scope but not yet declared")
+ std::set<Scope::Variable const*> m_activeVariables;
+ AsmAnalysisInfo& m_info;
+ langutil::ErrorReporter& m_errorReporter;
+ EVMVersion m_evmVersion;
+ AsmFlavour m_flavour = AsmFlavour::Loose;
+ boost::optional<langutil::Error::Type> m_errorTypeForLoose;
+};
+
+}
+}
+}
diff --git a/libyul/AsmAnalysisInfo.cpp b/libyul/AsmAnalysisInfo.cpp
new file mode 100644
index 00000000..22318b12
--- /dev/null
+++ b/libyul/AsmAnalysisInfo.cpp
@@ -0,0 +1,26 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Information generated during analyzer part of inline assembly.
+ */
+
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <libsolidity/inlineasm/AsmScope.h>
+
+#include <ostream>
+
diff --git a/libyul/AsmAnalysisInfo.h b/libyul/AsmAnalysisInfo.h
new file mode 100644
index 00000000..bd3b28c4
--- /dev/null
+++ b/libyul/AsmAnalysisInfo.h
@@ -0,0 +1,52 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Information generated during analyzer part of inline assembly.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <boost/variant.hpp>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+struct Scope;
+
+struct AsmAnalysisInfo
+{
+ using StackHeightInfo = std::map<void const*, int>;
+ using Scopes = std::map<assembly::Block const*, std::shared_ptr<Scope>>;
+ Scopes scopes;
+ StackHeightInfo stackHeightInfo;
+ /// Virtual blocks which will be used for scopes for function arguments and return values.
+ std::map<FunctionDefinition const*, std::shared_ptr<assembly::Block const>> virtualBlocks;
+};
+
+}
+}
+}
diff --git a/libyul/AsmCodeGen.cpp b/libyul/AsmCodeGen.cpp
new file mode 100644
index 00000000..2800cc7b
--- /dev/null
+++ b/libyul/AsmCodeGen.cpp
@@ -0,0 +1,162 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2016
+ * Code-generating part of inline assembly.
+ */
+
+#include <libsolidity/inlineasm/AsmCodeGen.h>
+
+#include <libsolidity/inlineasm/AsmParser.h>
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <libevmasm/Assembly.h>
+#include <liblangutil/SourceLocation.h>
+#include <libevmasm/Instruction.h>
+
+#include <libyul/backends/evm/AbstractAssembly.h>
+#include <libyul/backends/evm/EVMCodeTransform.h>
+
+#include <libdevcore/CommonIO.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+#include <boost/range/adaptor/map.hpp>
+#include <boost/range/algorithm/count_if.hpp>
+
+#include <memory>
+#include <functional>
+
+using namespace std;
+using namespace dev;
+using namespace langutil;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+class EthAssemblyAdapter: public yul::AbstractAssembly
+{
+public:
+ explicit EthAssemblyAdapter(eth::Assembly& _assembly):
+ m_assembly(_assembly)
+ {
+ }
+ virtual void setSourceLocation(SourceLocation const& _location) override
+ {
+ m_assembly.setSourceLocation(_location);
+ }
+ virtual int stackHeight() const override { return m_assembly.deposit(); }
+ virtual void appendInstruction(solidity::Instruction _instruction) override
+ {
+ m_assembly.append(_instruction);
+ }
+ virtual void appendConstant(u256 const& _constant) override
+ {
+ m_assembly.append(_constant);
+ }
+ /// Append a label.
+ virtual void appendLabel(LabelID _labelId) override
+ {
+ m_assembly.append(eth::AssemblyItem(eth::Tag, _labelId));
+ }
+ /// Append a label reference.
+ virtual void appendLabelReference(LabelID _labelId) override
+ {
+ m_assembly.append(eth::AssemblyItem(eth::PushTag, _labelId));
+ }
+ virtual size_t newLabelId() override
+ {
+ return assemblyTagToIdentifier(m_assembly.newTag());
+ }
+ virtual size_t namedLabel(std::string const& _name) override
+ {
+ return assemblyTagToIdentifier(m_assembly.namedTag(_name));
+ }
+ virtual void appendLinkerSymbol(std::string const& _linkerSymbol) override
+ {
+ m_assembly.appendLibraryAddress(_linkerSymbol);
+ }
+ virtual void appendJump(int _stackDiffAfter) override
+ {
+ appendInstruction(solidity::Instruction::JUMP);
+ m_assembly.adjustDeposit(_stackDiffAfter);
+ }
+ virtual void appendJumpTo(LabelID _labelId, int _stackDiffAfter) override
+ {
+ appendLabelReference(_labelId);
+ appendJump(_stackDiffAfter);
+ }
+ virtual void appendJumpToIf(LabelID _labelId) override
+ {
+ appendLabelReference(_labelId);
+ appendInstruction(solidity::Instruction::JUMPI);
+ }
+ virtual void appendBeginsub(LabelID, int) override
+ {
+ // TODO we could emulate that, though
+ solAssert(false, "BEGINSUB not implemented for EVM 1.0");
+ }
+ /// Call a subroutine.
+ virtual void appendJumpsub(LabelID, int, int) override
+ {
+ // TODO we could emulate that, though
+ solAssert(false, "JUMPSUB not implemented for EVM 1.0");
+ }
+
+ /// Return from a subroutine.
+ virtual void appendReturnsub(int, int) override
+ {
+ // TODO we could emulate that, though
+ solAssert(false, "RETURNSUB not implemented for EVM 1.0");
+ }
+
+ virtual void appendAssemblySize() override
+ {
+ m_assembly.appendProgramSize();
+ }
+
+private:
+ static LabelID assemblyTagToIdentifier(eth::AssemblyItem const& _tag)
+ {
+ u256 id = _tag.data();
+ solAssert(id <= std::numeric_limits<LabelID>::max(), "Tag id too large.");
+ return LabelID(id);
+ }
+
+ eth::Assembly& m_assembly;
+};
+
+void assembly::CodeGenerator::assemble(
+ Block const& _parsedData,
+ AsmAnalysisInfo& _analysisInfo,
+ eth::Assembly& _assembly,
+ yul::ExternalIdentifierAccess const& _identifierAccess,
+ bool _useNamedLabelsForFunctions
+)
+{
+ EthAssemblyAdapter assemblyAdapter(_assembly);
+ yul::CodeTransform(
+ assemblyAdapter,
+ _analysisInfo,
+ false,
+ false,
+ _identifierAccess,
+ _useNamedLabelsForFunctions
+ )(_parsedData);
+}
diff --git a/libyul/AsmCodeGen.h b/libyul/AsmCodeGen.h
new file mode 100644
index 00000000..bbc31397
--- /dev/null
+++ b/libyul/AsmCodeGen.h
@@ -0,0 +1,56 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2016
+ * Code-generating part of inline assembly.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmAnalysis.h>
+
+#include <functional>
+
+namespace dev
+{
+namespace eth
+{
+class Assembly;
+}
+namespace solidity
+{
+namespace assembly
+{
+struct Block;
+
+class CodeGenerator
+{
+public:
+ /// Performs code generation and appends generated to _assembly.
+ static void assemble(
+ Block const& _parsedData,
+ AsmAnalysisInfo& _analysisInfo,
+ eth::Assembly& _assembly,
+ yul::ExternalIdentifierAccess const& _identifierAccess = yul::ExternalIdentifierAccess(),
+ bool _useNamedLabelsForFunctions = false
+ );
+};
+
+}
+}
+}
diff --git a/libyul/AsmData.h b/libyul/AsmData.h
new file mode 100644
index 00000000..23a9db75
--- /dev/null
+++ b/libyul/AsmData.h
@@ -0,0 +1,104 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2016
+ * Parsed inline assembly to be used by the AST
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <libevmasm/Instruction.h>
+#include <liblangutil/SourceLocation.h>
+
+#include <libyul/YulString.h>
+
+#include <boost/variant.hpp>
+#include <boost/noncopyable.hpp>
+
+#include <map>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+using YulString = dev::yul::YulString;
+using Type = YulString;
+
+struct TypedName { langutil::SourceLocation location; YulString name; Type type; };
+using TypedNameList = std::vector<TypedName>;
+
+/// Direct EVM instruction (except PUSHi and JUMPDEST)
+struct Instruction { langutil::SourceLocation location; solidity::Instruction instruction; };
+/// Literal number or string (up to 32 bytes)
+enum class LiteralKind { Number, Boolean, String };
+struct Literal { langutil::SourceLocation location; LiteralKind kind; YulString value; Type type; };
+/// External / internal identifier or label reference
+struct Identifier { langutil::SourceLocation location; YulString name; };
+/// Jump label ("name:")
+struct Label { langutil::SourceLocation location; YulString name; };
+/// Assignment from stack (":= x", moves stack top into x, potentially multiple slots)
+struct StackAssignment { langutil::SourceLocation location; Identifier variableName; };
+/// Assignment ("x := mload(20:u256)", expects push-1-expression on the right hand
+/// side and requires x to occupy exactly one stack slot.
+///
+/// Multiple assignment ("x, y := f()"), where the left hand side variables each occupy
+/// a single stack slot and expects a single expression on the right hand returning
+/// the same amount of items as the number of variables.
+struct Assignment { langutil::SourceLocation location; std::vector<Identifier> variableNames; std::shared_ptr<Expression> value; };
+/// Functional instruction, e.g. "mul(mload(20:u256), add(2:u256, x))"
+struct FunctionalInstruction { langutil::SourceLocation location; solidity::Instruction instruction; std::vector<Expression> arguments; };
+struct FunctionCall { langutil::SourceLocation location; Identifier functionName; std::vector<Expression> arguments; };
+/// Statement that contains only a single expression
+struct ExpressionStatement { langutil::SourceLocation location; Expression expression; };
+/// Block-scope variable declaration ("let x:u256 := mload(20:u256)"), non-hoisted
+struct VariableDeclaration { langutil::SourceLocation location; TypedNameList variables; std::shared_ptr<Expression> value; };
+/// Block that creates a scope (frees declared stack variables)
+struct Block { langutil::SourceLocation location; std::vector<Statement> statements; };
+/// Function definition ("function f(a, b) -> (d, e) { ... }")
+struct FunctionDefinition { langutil::SourceLocation location; YulString name; TypedNameList parameters; TypedNameList returnVariables; Block body; };
+/// Conditional execution without "else" part.
+struct If { langutil::SourceLocation location; std::shared_ptr<Expression> condition; Block body; };
+/// Switch case or default case
+struct Case { langutil::SourceLocation location; std::shared_ptr<Literal> value; Block body; };
+/// Switch statement
+struct Switch { langutil::SourceLocation location; std::shared_ptr<Expression> expression; std::vector<Case> cases; };
+struct ForLoop { langutil::SourceLocation location; Block pre; std::shared_ptr<Expression> condition; Block post; Block body; };
+
+struct LocationExtractor: boost::static_visitor<langutil::SourceLocation>
+{
+ template <class T> langutil::SourceLocation operator()(T const& _node) const
+ {
+ return _node.location;
+ }
+};
+
+/// Extracts the source location from an inline assembly node.
+template <class T> inline langutil::SourceLocation locationOf(T const& _node)
+{
+ return boost::apply_visitor(LocationExtractor(), _node);
+}
+
+}
+}
+}
diff --git a/libyul/AsmDataForward.h b/libyul/AsmDataForward.h
new file mode 100644
index 00000000..69cf8f1d
--- /dev/null
+++ b/libyul/AsmDataForward.h
@@ -0,0 +1,65 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2016
+ * Forward declaration of classes for inline assembly / Yul AST
+ */
+
+#pragma once
+
+#include <boost/variant.hpp>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+struct Instruction;
+struct Literal;
+struct Label;
+struct StackAssignment;
+struct Identifier;
+struct Assignment;
+struct VariableDeclaration;
+struct FunctionalInstruction;
+struct FunctionDefinition;
+struct FunctionCall;
+struct If;
+struct Switch;
+struct Case;
+struct ForLoop;
+struct ExpressionStatement;
+struct Block;
+
+struct TypedName;
+
+using Expression = boost::variant<FunctionalInstruction, FunctionCall, Identifier, Literal>;
+using Statement = boost::variant<ExpressionStatement, Instruction, Label, StackAssignment, Assignment, VariableDeclaration, FunctionDefinition, If, Switch, ForLoop, Block>;
+
+enum class AsmFlavour
+{
+ Loose, // no types, EVM instructions as function, jumps and direct stack manipulations
+ Strict, // no types, EVM instructions as functions, but no jumps and no direct stack manipulations
+ Yul // same as Strict mode with types
+};
+
+}
+}
+}
diff --git a/libyul/AsmParser.cpp b/libyul/AsmParser.cpp
new file mode 100644
index 00000000..b11f70e0
--- /dev/null
+++ b/libyul/AsmParser.cpp
@@ -0,0 +1,617 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2016
+ * Solidity inline assembly parser.
+ */
+
+#include <libsolidity/inlineasm/AsmParser.h>
+#include <liblangutil/Scanner.h>
+#include <liblangutil/ErrorReporter.h>
+
+#include <boost/algorithm/string.hpp>
+
+#include <cctype>
+#include <algorithm>
+
+using namespace std;
+using namespace dev;
+using namespace langutil;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scanner, bool _reuseScanner)
+{
+ m_recursionDepth = 0;
+ try
+ {
+ m_scanner = _scanner;
+ auto block = make_shared<Block>(parseBlock());
+ if (!_reuseScanner)
+ expectToken(Token::EOS);
+ return block;
+ }
+ catch (FatalError const&)
+ {
+ if (m_errorReporter.errors().empty())
+ throw; // Something is weird here, rather throw again.
+ }
+ return nullptr;
+}
+
+assembly::Block Parser::parseBlock()
+{
+ RecursionGuard recursionGuard(*this);
+ assembly::Block block = createWithLocation<Block>();
+ expectToken(Token::LBrace);
+ while (currentToken() != Token::RBrace)
+ block.statements.emplace_back(parseStatement());
+ block.location.end = endPosition();
+ advance();
+ return block;
+}
+
+assembly::Statement Parser::parseStatement()
+{
+ RecursionGuard recursionGuard(*this);
+ switch (currentToken())
+ {
+ case Token::Let:
+ return parseVariableDeclaration();
+ case Token::Function:
+ return parseFunctionDefinition();
+ case Token::LBrace:
+ return parseBlock();
+ case Token::If:
+ {
+ assembly::If _if = createWithLocation<assembly::If>();
+ m_scanner->next();
+ _if.condition = make_shared<Expression>(parseExpression());
+ _if.body = parseBlock();
+ return _if;
+ }
+ case Token::Switch:
+ {
+ assembly::Switch _switch = createWithLocation<assembly::Switch>();
+ m_scanner->next();
+ _switch.expression = make_shared<Expression>(parseExpression());
+ while (m_scanner->currentToken() == Token::Case)
+ _switch.cases.emplace_back(parseCase());
+ if (m_scanner->currentToken() == Token::Default)
+ _switch.cases.emplace_back(parseCase());
+ if (m_scanner->currentToken() == Token::Default)
+ fatalParserError("Only one default case allowed.");
+ else if (m_scanner->currentToken() == Token::Case)
+ fatalParserError("Case not allowed after default case.");
+ if (_switch.cases.empty())
+ fatalParserError("Switch statement without any cases.");
+ _switch.location.end = _switch.cases.back().body.location.end;
+ return _switch;
+ }
+ case Token::For:
+ return parseForLoop();
+ case Token::Assign:
+ {
+ if (m_flavour != AsmFlavour::Loose)
+ break;
+ assembly::StackAssignment assignment = createWithLocation<assembly::StackAssignment>();
+ advance();
+ expectToken(Token::Colon);
+ assignment.variableName.location = location();
+ assignment.variableName.name = YulString(currentLiteral());
+ if (instructions().count(assignment.variableName.name.str()))
+ fatalParserError("Identifier expected, got instruction name.");
+ assignment.location.end = endPosition();
+ expectToken(Token::Identifier);
+ return assignment;
+ }
+ default:
+ break;
+ }
+ // Options left:
+ // Simple instruction (might turn into functional),
+ // literal,
+ // identifier (might turn into label or functional assignment)
+ ElementaryOperation elementary(parseElementaryOperation());
+ switch (currentToken())
+ {
+ case Token::LParen:
+ {
+ Expression expr = parseCall(std::move(elementary));
+ return ExpressionStatement{locationOf(expr), expr};
+ }
+ case Token::Comma:
+ {
+ // if a comma follows, a multiple assignment is assumed
+
+ if (elementary.type() != typeid(assembly::Identifier))
+ fatalParserError("Label name / variable name must precede \",\" (multiple assignment).");
+ assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary);
+
+ Assignment assignment = createWithLocation<Assignment>(identifier.location);
+ assignment.variableNames.emplace_back(identifier);
+
+ do
+ {
+ expectToken(Token::Comma);
+ elementary = parseElementaryOperation();
+ if (elementary.type() != typeid(assembly::Identifier))
+ fatalParserError("Variable name expected in multiple assignment.");
+ assignment.variableNames.emplace_back(boost::get<assembly::Identifier>(elementary));
+ }
+ while (currentToken() == Token::Comma);
+
+ expectToken(Token::Colon);
+ expectToken(Token::Assign);
+
+ assignment.value.reset(new Expression(parseExpression()));
+ assignment.location.end = locationOf(*assignment.value).end;
+ return assignment;
+ }
+ case Token::Colon:
+ {
+ if (elementary.type() != typeid(assembly::Identifier))
+ fatalParserError("Label name / variable name must precede \":\".");
+ assembly::Identifier const& identifier = boost::get<assembly::Identifier>(elementary);
+ advance();
+ // identifier:=: should be parsed as identifier: =: (i.e. a label),
+ // while identifier:= (being followed by a non-colon) as identifier := (assignment).
+ if (currentToken() == Token::Assign && peekNextToken() != Token::Colon)
+ {
+ assembly::Assignment assignment = createWithLocation<assembly::Assignment>(identifier.location);
+ if (m_flavour != AsmFlavour::Yul && instructions().count(identifier.name.str()))
+ fatalParserError("Cannot use instruction names for identifier names.");
+ advance();
+ assignment.variableNames.emplace_back(identifier);
+ assignment.value.reset(new Expression(parseExpression()));
+ assignment.location.end = locationOf(*assignment.value).end;
+ return assignment;
+ }
+ else
+ {
+ // label
+ if (m_flavour != AsmFlavour::Loose)
+ fatalParserError("Labels are not supported.");
+ Label label = createWithLocation<Label>(identifier.location);
+ label.name = identifier.name;
+ return label;
+ }
+ }
+ default:
+ if (m_flavour != AsmFlavour::Loose)
+ fatalParserError("Call or assignment expected.");
+ break;
+ }
+ if (elementary.type() == typeid(assembly::Identifier))
+ {
+ Expression expr = boost::get<assembly::Identifier>(elementary);
+ return ExpressionStatement{locationOf(expr), expr};
+ }
+ else if (elementary.type() == typeid(assembly::Literal))
+ {
+ Expression expr = boost::get<assembly::Literal>(elementary);
+ return ExpressionStatement{locationOf(expr), expr};
+ }
+ else
+ {
+ solAssert(elementary.type() == typeid(assembly::Instruction), "Invalid elementary operation.");
+ return boost::get<assembly::Instruction>(elementary);
+ }
+}
+
+assembly::Case Parser::parseCase()
+{
+ RecursionGuard recursionGuard(*this);
+ assembly::Case _case = createWithLocation<assembly::Case>();
+ if (m_scanner->currentToken() == Token::Default)
+ m_scanner->next();
+ else if (m_scanner->currentToken() == Token::Case)
+ {
+ m_scanner->next();
+ ElementaryOperation literal = parseElementaryOperation();
+ if (literal.type() != typeid(assembly::Literal))
+ fatalParserError("Literal expected.");
+ _case.value = make_shared<Literal>(boost::get<assembly::Literal>(std::move(literal)));
+ }
+ else
+ fatalParserError("Case or default case expected.");
+ _case.body = parseBlock();
+ _case.location.end = _case.body.location.end;
+ return _case;
+}
+
+assembly::ForLoop Parser::parseForLoop()
+{
+ RecursionGuard recursionGuard(*this);
+ ForLoop forLoop = createWithLocation<ForLoop>();
+ expectToken(Token::For);
+ forLoop.pre = parseBlock();
+ forLoop.condition = make_shared<Expression>(parseExpression());
+ forLoop.post = parseBlock();
+ forLoop.body = parseBlock();
+ forLoop.location.end = forLoop.body.location.end;
+ return forLoop;
+}
+
+assembly::Expression Parser::parseExpression()
+{
+ RecursionGuard recursionGuard(*this);
+ // In strict mode, this might parse a plain Instruction, but
+ // it will be converted to a FunctionalInstruction inside
+ // parseCall below.
+ ElementaryOperation operation = parseElementaryOperation();
+ if (operation.type() == typeid(Instruction))
+ {
+ Instruction const& instr = boost::get<Instruction>(operation);
+ // Disallow instructions returning multiple values (and DUP/SWAP) as expression.
+ if (
+ instructionInfo(instr.instruction).ret != 1 ||
+ isDupInstruction(instr.instruction) ||
+ isSwapInstruction(instr.instruction)
+ )
+ fatalParserError(
+ "Instruction \"" +
+ instructionNames().at(instr.instruction) +
+ "\" not allowed in this context."
+ );
+ if (m_flavour != AsmFlavour::Loose && currentToken() != Token::LParen)
+ fatalParserError(
+ "Non-functional instructions are not allowed in this context."
+ );
+ // Enforce functional notation for instructions requiring multiple arguments.
+ int args = instructionInfo(instr.instruction).args;
+ if (args > 0 && currentToken() != Token::LParen)
+ fatalParserError(string(
+ "Expected '(' (instruction \"" +
+ instructionNames().at(instr.instruction) +
+ "\" expects " +
+ to_string(args) +
+ " arguments)"
+ ));
+ }
+ if (currentToken() == Token::LParen)
+ return parseCall(std::move(operation));
+ else if (operation.type() == typeid(Instruction))
+ {
+ // Instructions not taking arguments are allowed as expressions.
+ solAssert(m_flavour == AsmFlavour::Loose, "");
+ Instruction& instr = boost::get<Instruction>(operation);
+ return FunctionalInstruction{std::move(instr.location), instr.instruction, {}};
+ }
+ else if (operation.type() == typeid(assembly::Identifier))
+ return boost::get<assembly::Identifier>(operation);
+ else
+ {
+ solAssert(operation.type() == typeid(assembly::Literal), "");
+ return boost::get<assembly::Literal>(operation);
+ }
+}
+
+std::map<string, dev::solidity::Instruction> const& Parser::instructions()
+{
+ // Allowed instructions, lowercase names.
+ static map<string, dev::solidity::Instruction> s_instructions;
+ if (s_instructions.empty())
+ {
+ for (auto const& instruction: solidity::c_instructions)
+ {
+ if (
+ instruction.second == solidity::Instruction::JUMPDEST ||
+ solidity::isPushInstruction(instruction.second)
+ )
+ continue;
+ string name = instruction.first;
+ transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); });
+ s_instructions[name] = instruction.second;
+ }
+ }
+ return s_instructions;
+}
+
+std::map<dev::solidity::Instruction, string> const& Parser::instructionNames()
+{
+ static map<dev::solidity::Instruction, string> s_instructionNames;
+ if (s_instructionNames.empty())
+ {
+ for (auto const& instr: instructions())
+ s_instructionNames[instr.second] = instr.first;
+ // set the ambiguous instructions to a clear default
+ s_instructionNames[solidity::Instruction::SELFDESTRUCT] = "selfdestruct";
+ s_instructionNames[solidity::Instruction::KECCAK256] = "keccak256";
+ }
+ return s_instructionNames;
+}
+
+Parser::ElementaryOperation Parser::parseElementaryOperation()
+{
+ RecursionGuard recursionGuard(*this);
+ ElementaryOperation ret;
+ switch (currentToken())
+ {
+ case Token::Identifier:
+ case Token::Return:
+ case Token::Byte:
+ case Token::Address:
+ {
+ string literal;
+ if (currentToken() == Token::Return)
+ literal = "return";
+ else if (currentToken() == Token::Byte)
+ literal = "byte";
+ else if (currentToken() == Token::Address)
+ literal = "address";
+ else
+ literal = currentLiteral();
+ // first search the set of instructions.
+ if (m_flavour != AsmFlavour::Yul && instructions().count(literal))
+ {
+ dev::solidity::Instruction const& instr = instructions().at(literal);
+ ret = Instruction{location(), instr};
+ }
+ else
+ ret = Identifier{location(), YulString{literal}};
+ advance();
+ break;
+ }
+ case Token::StringLiteral:
+ case Token::Number:
+ case Token::TrueLiteral:
+ case Token::FalseLiteral:
+ {
+ LiteralKind kind = LiteralKind::Number;
+ switch (currentToken())
+ {
+ case Token::StringLiteral:
+ kind = LiteralKind::String;
+ break;
+ case Token::Number:
+ if (!isValidNumberLiteral(currentLiteral()))
+ fatalParserError("Invalid number literal.");
+ kind = LiteralKind::Number;
+ break;
+ case Token::TrueLiteral:
+ case Token::FalseLiteral:
+ kind = LiteralKind::Boolean;
+ break;
+ default:
+ break;
+ }
+
+ Literal literal{
+ location(),
+ kind,
+ YulString{currentLiteral()},
+ {}
+ };
+ advance();
+ if (m_flavour == AsmFlavour::Yul)
+ {
+ expectToken(Token::Colon);
+ literal.location.end = endPosition();
+ literal.type = YulString{expectAsmIdentifier()};
+ }
+ else if (kind == LiteralKind::Boolean)
+ fatalParserError("True and false are not valid literals.");
+ ret = std::move(literal);
+ break;
+ }
+ default:
+ fatalParserError(
+ m_flavour == AsmFlavour::Yul ?
+ "Literal or identifier expected." :
+ "Literal, identifier or instruction expected."
+ );
+ }
+ return ret;
+}
+
+assembly::VariableDeclaration Parser::parseVariableDeclaration()
+{
+ RecursionGuard recursionGuard(*this);
+ VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
+ expectToken(Token::Let);
+ while (true)
+ {
+ varDecl.variables.emplace_back(parseTypedName());
+ if (currentToken() == Token::Comma)
+ expectToken(Token::Comma);
+ else
+ break;
+ }
+ if (currentToken() == Token::Colon)
+ {
+ expectToken(Token::Colon);
+ expectToken(Token::Assign);
+ varDecl.value.reset(new Expression(parseExpression()));
+ varDecl.location.end = locationOf(*varDecl.value).end;
+ }
+ else
+ varDecl.location.end = varDecl.variables.back().location.end;
+ return varDecl;
+}
+
+assembly::FunctionDefinition Parser::parseFunctionDefinition()
+{
+ RecursionGuard recursionGuard(*this);
+ FunctionDefinition funDef = createWithLocation<FunctionDefinition>();
+ expectToken(Token::Function);
+ funDef.name = YulString{expectAsmIdentifier()};
+ expectToken(Token::LParen);
+ while (currentToken() != Token::RParen)
+ {
+ funDef.parameters.emplace_back(parseTypedName());
+ if (currentToken() == Token::RParen)
+ break;
+ expectToken(Token::Comma);
+ }
+ expectToken(Token::RParen);
+ if (currentToken() == Token::Sub)
+ {
+ expectToken(Token::Sub);
+ expectToken(Token::GreaterThan);
+ while (true)
+ {
+ funDef.returnVariables.emplace_back(parseTypedName());
+ if (currentToken() == Token::LBrace)
+ break;
+ expectToken(Token::Comma);
+ }
+ }
+ funDef.body = parseBlock();
+ funDef.location.end = funDef.body.location.end;
+ return funDef;
+}
+
+assembly::Expression Parser::parseCall(Parser::ElementaryOperation&& _initialOp)
+{
+ RecursionGuard recursionGuard(*this);
+ if (_initialOp.type() == typeid(Instruction))
+ {
+ solAssert(m_flavour != AsmFlavour::Yul, "Instructions are invalid in Yul");
+ Instruction& instruction = boost::get<Instruction>(_initialOp);
+ FunctionalInstruction ret;
+ ret.instruction = instruction.instruction;
+ ret.location = std::move(instruction.location);
+ solidity::Instruction instr = ret.instruction;
+ InstructionInfo instrInfo = instructionInfo(instr);
+ if (solidity::isDupInstruction(instr))
+ fatalParserError("DUPi instructions not allowed for functional notation");
+ if (solidity::isSwapInstruction(instr))
+ fatalParserError("SWAPi instructions not allowed for functional notation");
+ expectToken(Token::LParen);
+ unsigned args = unsigned(instrInfo.args);
+ for (unsigned i = 0; i < args; ++i)
+ {
+ /// check for premature closing parentheses
+ if (currentToken() == Token::RParen)
+ fatalParserError(string(
+ "Expected expression (instruction \"" +
+ instructionNames().at(instr) +
+ "\" expects " +
+ to_string(args) +
+ " arguments)"
+ ));
+
+ ret.arguments.emplace_back(parseExpression());
+ if (i != args - 1)
+ {
+ if (currentToken() != Token::Comma)
+ fatalParserError(string(
+ "Expected ',' (instruction \"" +
+ instructionNames().at(instr) +
+ "\" expects " +
+ to_string(args) +
+ " arguments)"
+ ));
+ else
+ advance();
+ }
+ }
+ ret.location.end = endPosition();
+ if (currentToken() == Token::Comma)
+ fatalParserError(string(
+ "Expected ')' (instruction \"" +
+ instructionNames().at(instr) +
+ "\" expects " +
+ to_string(args) +
+ " arguments)"
+ ));
+ expectToken(Token::RParen);
+ return ret;
+ }
+ else if (_initialOp.type() == typeid(Identifier))
+ {
+ FunctionCall ret;
+ ret.functionName = std::move(boost::get<Identifier>(_initialOp));
+ ret.location = ret.functionName.location;
+ expectToken(Token::LParen);
+ while (currentToken() != Token::RParen)
+ {
+ ret.arguments.emplace_back(parseExpression());
+ if (currentToken() == Token::RParen)
+ break;
+ expectToken(Token::Comma);
+ }
+ ret.location.end = endPosition();
+ expectToken(Token::RParen);
+ return ret;
+ }
+ else
+ fatalParserError(
+ m_flavour == AsmFlavour::Yul ?
+ "Function name expected." :
+ "Assembly instruction or function name required in front of \"(\")"
+ );
+
+ return {};
+}
+
+TypedName Parser::parseTypedName()
+{
+ RecursionGuard recursionGuard(*this);
+ TypedName typedName = createWithLocation<TypedName>();
+ typedName.name = YulString{expectAsmIdentifier()};
+ if (m_flavour == AsmFlavour::Yul)
+ {
+ expectToken(Token::Colon);
+ typedName.location.end = endPosition();
+ typedName.type = YulString{expectAsmIdentifier()};
+ }
+ return typedName;
+}
+
+string Parser::expectAsmIdentifier()
+{
+ string name = currentLiteral();
+ if (m_flavour == AsmFlavour::Yul)
+ {
+ switch (currentToken())
+ {
+ case Token::Return:
+ case Token::Byte:
+ case Token::Address:
+ case Token::Bool:
+ advance();
+ return name;
+ default:
+ break;
+ }
+ }
+ else if (instructions().count(name))
+ fatalParserError("Cannot use instruction names for identifier names.");
+ expectToken(Token::Identifier);
+ return name;
+}
+
+bool Parser::isValidNumberLiteral(string const& _literal)
+{
+ try
+ {
+ // Try to convert _literal to u256.
+ auto tmp = u256(_literal);
+ (void) tmp;
+ }
+ catch (...)
+ {
+ return false;
+ }
+ if (boost::starts_with(_literal, "0x"))
+ return true;
+ else
+ return _literal.find_first_not_of("0123456789") == string::npos;
+}
diff --git a/libyul/AsmParser.h b/libyul/AsmParser.h
new file mode 100644
index 00000000..9e13799a
--- /dev/null
+++ b/libyul/AsmParser.h
@@ -0,0 +1,95 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2016
+ * Solidity inline assembly parser.
+ */
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <libsolidity/inlineasm/AsmData.h>
+#include <liblangutil/SourceLocation.h>
+#include <liblangutil/Scanner.h>
+#include <liblangutil/ParserBase.h>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+class Parser: public langutil::ParserBase
+{
+public:
+ explicit Parser(langutil::ErrorReporter& _errorReporter, AsmFlavour _flavour = AsmFlavour::Loose):
+ ParserBase(_errorReporter), m_flavour(_flavour) {}
+
+ /// Parses an inline assembly block starting with `{` and ending with `}`.
+ /// @param _reuseScanner if true, do check for end of input after the `}`.
+ /// @returns an empty shared pointer on error.
+ std::shared_ptr<Block> parse(std::shared_ptr<langutil::Scanner> const& _scanner, bool _reuseScanner);
+
+protected:
+ using ElementaryOperation = boost::variant<assembly::Instruction, assembly::Literal, assembly::Identifier>;
+
+ /// Creates an inline assembly node with the given source location.
+ template <class T> T createWithLocation(langutil::SourceLocation const& _loc = {}) const
+ {
+ T r;
+ r.location = _loc;
+ if (r.location.isEmpty())
+ {
+ r.location.start = position();
+ r.location.end = endPosition();
+ }
+ if (!r.location.sourceName)
+ r.location.sourceName = sourceName();
+ return r;
+ }
+ langutil::SourceLocation location() const { return {position(), endPosition(), sourceName()}; }
+
+ Block parseBlock();
+ Statement parseStatement();
+ Case parseCase();
+ ForLoop parseForLoop();
+ /// Parses a functional expression that has to push exactly one stack element
+ assembly::Expression parseExpression();
+ static std::map<std::string, dev::solidity::Instruction> const& instructions();
+ static std::map<dev::solidity::Instruction, std::string> const& instructionNames();
+ /// Parses an elementary operation, i.e. a literal, identifier or instruction.
+ /// This will parse instructions even in strict mode as part of the full parser
+ /// for FunctionalInstruction.
+ ElementaryOperation parseElementaryOperation();
+ VariableDeclaration parseVariableDeclaration();
+ FunctionDefinition parseFunctionDefinition();
+ assembly::Expression parseCall(ElementaryOperation&& _initialOp);
+ TypedName parseTypedName();
+ std::string expectAsmIdentifier();
+
+ static bool isValidNumberLiteral(std::string const& _literal);
+
+private:
+ AsmFlavour m_flavour = AsmFlavour::Loose;
+};
+
+}
+}
+}
diff --git a/libyul/AsmPrinter.cpp b/libyul/AsmPrinter.cpp
new file mode 100644
index 00000000..7151fcfa
--- /dev/null
+++ b/libyul/AsmPrinter.cpp
@@ -0,0 +1,250 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2017
+ * Converts a parsed assembly into its textual form.
+ */
+
+#include <libsolidity/inlineasm/AsmPrinter.h>
+#include <libsolidity/inlineasm/AsmData.h>
+#include <liblangutil/Exceptions.h>
+
+#include <libdevcore/CommonData.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+
+#include <memory>
+#include <functional>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+//@TODO source locations
+
+string AsmPrinter::operator()(assembly::Instruction const& _instruction)
+{
+ solAssert(!m_yul, "");
+ solAssert(isValidInstruction(_instruction.instruction), "Invalid instruction");
+ return boost::to_lower_copy(instructionInfo(_instruction.instruction).name);
+}
+
+string AsmPrinter::operator()(assembly::Literal const& _literal)
+{
+ switch (_literal.kind)
+ {
+ case LiteralKind::Number:
+ solAssert(isValidDecimal(_literal.value.str()) || isValidHex(_literal.value.str()), "Invalid number literal");
+ return _literal.value.str() + appendTypeName(_literal.type);
+ case LiteralKind::Boolean:
+ solAssert(_literal.value.str() == "true" || _literal.value.str() == "false", "Invalid bool literal.");
+ return ((_literal.value.str() == "true") ? "true" : "false") + appendTypeName(_literal.type);
+ case LiteralKind::String:
+ break;
+ }
+
+ string out;
+ for (char c: _literal.value.str())
+ if (c == '\\')
+ out += "\\\\";
+ else if (c == '"')
+ out += "\\\"";
+ else if (c == '\b')
+ out += "\\b";
+ else if (c == '\f')
+ out += "\\f";
+ else if (c == '\n')
+ out += "\\n";
+ else if (c == '\r')
+ out += "\\r";
+ else if (c == '\t')
+ out += "\\t";
+ else if (c == '\v')
+ out += "\\v";
+ else if (!isprint(c, locale::classic()))
+ {
+ ostringstream o;
+ o << std::hex << setfill('0') << setw(2) << (unsigned)(unsigned char)(c);
+ out += "\\x" + o.str();
+ }
+ else
+ out += c;
+ return "\"" + out + "\"" + appendTypeName(_literal.type);
+}
+
+string AsmPrinter::operator()(assembly::Identifier const& _identifier)
+{
+ solAssert(!_identifier.name.empty(), "Invalid identifier.");
+ return _identifier.name.str();
+}
+
+string AsmPrinter::operator()(assembly::FunctionalInstruction const& _functionalInstruction)
+{
+ solAssert(!m_yul, "");
+ solAssert(isValidInstruction(_functionalInstruction.instruction), "Invalid instruction");
+ return
+ boost::to_lower_copy(instructionInfo(_functionalInstruction.instruction).name) +
+ "(" +
+ boost::algorithm::join(
+ _functionalInstruction.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)),
+ ", ") +
+ ")";
+}
+
+string AsmPrinter::operator()(ExpressionStatement const& _statement)
+{
+ return boost::apply_visitor(*this, _statement.expression);
+}
+
+string AsmPrinter::operator()(assembly::Label const& _label)
+{
+ solAssert(!m_yul, "");
+ solAssert(!_label.name.empty(), "Invalid label.");
+ return _label.name.str() + ":";
+}
+
+string AsmPrinter::operator()(assembly::StackAssignment const& _assignment)
+{
+ solAssert(!m_yul, "");
+ solAssert(!_assignment.variableName.name.empty(), "Invalid variable name.");
+ return "=: " + (*this)(_assignment.variableName);
+}
+
+string AsmPrinter::operator()(assembly::Assignment const& _assignment)
+{
+ solAssert(_assignment.variableNames.size() >= 1, "");
+ string variables = (*this)(_assignment.variableNames.front());
+ for (size_t i = 1; i < _assignment.variableNames.size(); ++i)
+ variables += ", " + (*this)(_assignment.variableNames[i]);
+ return variables + " := " + boost::apply_visitor(*this, *_assignment.value);
+}
+
+string AsmPrinter::operator()(assembly::VariableDeclaration const& _variableDeclaration)
+{
+ string out = "let ";
+ out += boost::algorithm::join(
+ _variableDeclaration.variables | boost::adaptors::transformed(
+ [this](TypedName argument) { return formatTypedName(argument); }
+ ),
+ ", "
+ );
+ if (_variableDeclaration.value)
+ {
+ out += " := ";
+ out += boost::apply_visitor(*this, *_variableDeclaration.value);
+ }
+ return out;
+}
+
+string AsmPrinter::operator()(assembly::FunctionDefinition const& _functionDefinition)
+{
+ solAssert(!_functionDefinition.name.empty(), "Invalid function name.");
+ string out = "function " + _functionDefinition.name.str() + "(";
+ out += boost::algorithm::join(
+ _functionDefinition.parameters | boost::adaptors::transformed(
+ [this](TypedName argument) { return formatTypedName(argument); }
+ ),
+ ", "
+ );
+ out += ")";
+ if (!_functionDefinition.returnVariables.empty())
+ {
+ out += " -> ";
+ out += boost::algorithm::join(
+ _functionDefinition.returnVariables | boost::adaptors::transformed(
+ [this](TypedName argument) { return formatTypedName(argument); }
+ ),
+ ", "
+ );
+ }
+
+ return out + "\n" + (*this)(_functionDefinition.body);
+}
+
+string AsmPrinter::operator()(assembly::FunctionCall const& _functionCall)
+{
+ return
+ (*this)(_functionCall.functionName) + "(" +
+ boost::algorithm::join(
+ _functionCall.arguments | boost::adaptors::transformed(boost::apply_visitor(*this)),
+ ", " ) +
+ ")";
+}
+
+string AsmPrinter::operator()(If const& _if)
+{
+ solAssert(_if.condition, "Invalid if condition.");
+ return "if " + boost::apply_visitor(*this, *_if.condition) + "\n" + (*this)(_if.body);
+}
+
+string AsmPrinter::operator()(Switch const& _switch)
+{
+ solAssert(_switch.expression, "Invalid expression pointer.");
+ string out = "switch " + boost::apply_visitor(*this, *_switch.expression);
+ for (auto const& _case: _switch.cases)
+ {
+ if (!_case.value)
+ out += "\ndefault ";
+ else
+ out += "\ncase " + (*this)(*_case.value) + " ";
+ out += (*this)(_case.body);
+ }
+ return out;
+}
+
+string AsmPrinter::operator()(assembly::ForLoop const& _forLoop)
+{
+ solAssert(_forLoop.condition, "Invalid for loop condition.");
+ string out = "for ";
+ out += (*this)(_forLoop.pre);
+ out += "\n";
+ out += boost::apply_visitor(*this, *_forLoop.condition);
+ out += "\n";
+ out += (*this)(_forLoop.post);
+ out += "\n";
+ out += (*this)(_forLoop.body);
+ return out;
+}
+
+string AsmPrinter::operator()(Block const& _block)
+{
+ if (_block.statements.empty())
+ return "{\n}";
+ string body = boost::algorithm::join(
+ _block.statements | boost::adaptors::transformed(boost::apply_visitor(*this)),
+ "\n"
+ );
+ boost::replace_all(body, "\n", "\n ");
+ return "{\n " + body + "\n}";
+}
+
+string AsmPrinter::formatTypedName(TypedName _variable) const
+{
+ solAssert(!_variable.name.empty(), "Invalid variable name.");
+ return _variable.name.str() + appendTypeName(_variable.type);
+}
+
+string AsmPrinter::appendTypeName(YulString _type) const
+{
+ if (m_yul)
+ return ":" + _type.str();
+ return "";
+}
diff --git a/libyul/AsmPrinter.h b/libyul/AsmPrinter.h
new file mode 100644
index 00000000..72048975
--- /dev/null
+++ b/libyul/AsmPrinter.h
@@ -0,0 +1,68 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * @author Christian <c@ethdev.com>
+ * @date 2017
+ * Converts a parsed assembly into its textual form.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <libyul/YulString.h>
+
+#include <boost/variant.hpp>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+class AsmPrinter: public boost::static_visitor<std::string>
+{
+public:
+ explicit AsmPrinter(bool _yul = false): m_yul(_yul) {}
+
+ std::string operator()(assembly::Instruction const& _instruction);
+ std::string operator()(assembly::Literal const& _literal);
+ std::string operator()(assembly::Identifier const& _identifier);
+ std::string operator()(assembly::FunctionalInstruction const& _functionalInstruction);
+ std::string operator()(assembly::ExpressionStatement const& _expr);
+ std::string operator()(assembly::Label const& _label);
+ std::string operator()(assembly::StackAssignment const& _assignment);
+ std::string operator()(assembly::Assignment const& _assignment);
+ std::string operator()(assembly::VariableDeclaration const& _variableDeclaration);
+ std::string operator()(assembly::FunctionDefinition const& _functionDefinition);
+ std::string operator()(assembly::FunctionCall const& _functionCall);
+ std::string operator()(assembly::If const& _if);
+ std::string operator()(assembly::Switch const& _switch);
+ std::string operator()(assembly::ForLoop const& _forLoop);
+ std::string operator()(assembly::Block const& _block);
+
+private:
+ std::string formatTypedName(TypedName _variable) const;
+ std::string appendTypeName(yul::YulString _type) const;
+
+ bool m_yul = false;
+};
+
+}
+}
+}
diff --git a/libyul/AsmScope.cpp b/libyul/AsmScope.cpp
new file mode 100644
index 00000000..10893b96
--- /dev/null
+++ b/libyul/AsmScope.cpp
@@ -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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Scopes for identifiers.
+ */
+
+#include <libsolidity/inlineasm/AsmScope.h>
+
+using namespace std;
+using namespace dev;
+using namespace dev::solidity::assembly;
+
+bool Scope::registerLabel(yul::YulString _name)
+{
+ if (exists(_name))
+ return false;
+ identifiers[_name] = Label();
+ return true;
+}
+
+bool Scope::registerVariable(yul::YulString _name, YulType const& _type)
+{
+ if (exists(_name))
+ return false;
+ Variable variable;
+ variable.type = _type;
+ identifiers[_name] = variable;
+ return true;
+}
+
+bool Scope::registerFunction(yul::YulString _name, std::vector<YulType> const& _arguments, std::vector<YulType> const& _returns)
+{
+ if (exists(_name))
+ return false;
+ identifiers[_name] = Function{_arguments, _returns};
+ return true;
+}
+
+Scope::Identifier* Scope::lookup(yul::YulString _name)
+{
+ bool crossedFunctionBoundary = false;
+ for (Scope* s = this; s; s = s->superScope)
+ {
+ auto id = s->identifiers.find(_name);
+ if (id != s->identifiers.end())
+ {
+ if (crossedFunctionBoundary && id->second.type() == typeid(Scope::Variable))
+ return nullptr;
+ else
+ return &id->second;
+ }
+
+ if (s->functionScope)
+ crossedFunctionBoundary = true;
+ }
+ return nullptr;
+}
+
+bool Scope::exists(yul::YulString _name) const
+{
+ if (identifiers.count(_name))
+ return true;
+ else if (superScope)
+ return superScope->exists(_name);
+ else
+ return false;
+}
+
+size_t Scope::numberOfVariables() const
+{
+ size_t count = 0;
+ for (auto const& identifier: identifiers)
+ if (identifier.second.type() == typeid(Scope::Variable))
+ count++;
+ return count;
+}
+
+bool Scope::insideFunction() const
+{
+ for (Scope const* s = this; s; s = s->superScope)
+ if (s->functionScope)
+ return true;
+ return false;
+}
diff --git a/libyul/AsmScope.h b/libyul/AsmScope.h
new file mode 100644
index 00000000..12c05716
--- /dev/null
+++ b/libyul/AsmScope.h
@@ -0,0 +1,105 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Scopes for identifiers.
+ */
+
+#pragma once
+
+#include <liblangutil/Exceptions.h>
+
+#include <libyul/YulString.h>
+
+#include <libdevcore/Visitor.h>
+
+#include <boost/variant.hpp>
+#include <boost/optional.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+struct Scope
+{
+ using YulType = yul::YulString;
+ using LabelID = size_t;
+
+ struct Variable { YulType type; };
+ struct Label { };
+ struct Function
+ {
+ std::vector<YulType> arguments;
+ std::vector<YulType> returns;
+ };
+
+ using Identifier = boost::variant<Variable, Label, Function>;
+ using Visitor = GenericVisitor<Variable const, Label const, Function const>;
+ using NonconstVisitor = GenericVisitor<Variable, Label, Function>;
+
+ bool registerVariable(yul::YulString _name, YulType const& _type);
+ bool registerLabel(yul::YulString _name);
+ bool registerFunction(
+ yul::YulString _name,
+ std::vector<YulType> const& _arguments,
+ std::vector<YulType> const& _returns
+ );
+
+ /// Looks up the identifier in this or super scopes and returns a valid pointer if found
+ /// or a nullptr if not found. Variable lookups up across function boundaries will fail, as
+ /// will any lookups across assembly boundaries.
+ /// The pointer will be invalidated if the scope is modified.
+ /// @param _crossedFunction if true, we already crossed a function boundary during recursive lookup
+ Identifier* lookup(yul::YulString _name);
+ /// Looks up the identifier in this and super scopes (will not find variables across function
+ /// boundaries and generally stops at assembly boundaries) and calls the visitor, returns
+ /// false if not found.
+ template <class V>
+ bool lookup(yul::YulString _name, V const& _visitor)
+ {
+ if (Identifier* id = lookup(_name))
+ {
+ boost::apply_visitor(_visitor, *id);
+ return true;
+ }
+ else
+ return false;
+ }
+ /// @returns true if the name exists in this scope or in super scopes (also searches
+ /// across function and assembly boundaries).
+ bool exists(yul::YulString _name) const;
+
+ /// @returns the number of variables directly registered inside the scope.
+ size_t numberOfVariables() const;
+ /// @returns true if this scope is inside a function.
+ bool insideFunction() const;
+
+ Scope* superScope = nullptr;
+ /// If true, variables from the super scope are not visible here (other identifiers are),
+ /// but they are still taken into account to prevent shadowing.
+ bool functionScope = false;
+ std::map<yul::YulString, Identifier> identifiers;
+};
+
+}
+}
+}
diff --git a/libyul/AsmScopeFiller.cpp b/libyul/AsmScopeFiller.cpp
new file mode 100644
index 00000000..09934bd8
--- /dev/null
+++ b/libyul/AsmScopeFiller.cpp
@@ -0,0 +1,181 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Module responsible for registering identifiers inside their scopes.
+ */
+
+#include <libsolidity/inlineasm/AsmScopeFiller.h>
+
+#include <libsolidity/inlineasm/AsmData.h>
+#include <libsolidity/inlineasm/AsmScope.h>
+#include <libsolidity/inlineasm/AsmAnalysisInfo.h>
+
+#include <liblangutil/ErrorReporter.h>
+#include <liblangutil/Exceptions.h>
+
+#include <libdevcore/CommonData.h>
+
+#include <boost/range/adaptor/reversed.hpp>
+
+#include <memory>
+#include <functional>
+
+using namespace std;
+using namespace dev;
+using namespace langutil;
+using namespace dev::solidity;
+using namespace dev::solidity::assembly;
+
+ScopeFiller::ScopeFiller(AsmAnalysisInfo& _info, ErrorReporter& _errorReporter):
+ m_info(_info), m_errorReporter(_errorReporter)
+{
+ m_currentScope = &scope(nullptr);
+}
+
+bool ScopeFiller::operator()(ExpressionStatement const& _expr)
+{
+ return boost::apply_visitor(*this, _expr.expression);
+}
+
+bool ScopeFiller::operator()(Label const& _item)
+{
+ if (!m_currentScope->registerLabel(_item.name))
+ {
+ //@TODO secondary location
+ m_errorReporter.declarationError(
+ _item.location,
+ "Label name " + _item.name.str() + " already taken in this scope."
+ );
+ return false;
+ }
+ return true;
+}
+
+bool ScopeFiller::operator()(assembly::VariableDeclaration const& _varDecl)
+{
+ for (auto const& variable: _varDecl.variables)
+ if (!registerVariable(variable, _varDecl.location, *m_currentScope))
+ return false;
+ return true;
+}
+
+bool ScopeFiller::operator()(assembly::FunctionDefinition const& _funDef)
+{
+ bool success = true;
+ vector<Scope::YulType> arguments;
+ for (auto const& _argument: _funDef.parameters)
+ arguments.emplace_back(_argument.type.str());
+ vector<Scope::YulType> returns;
+ for (auto const& _return: _funDef.returnVariables)
+ returns.emplace_back(_return.type.str());
+ if (!m_currentScope->registerFunction(_funDef.name, arguments, returns))
+ {
+ //@TODO secondary location
+ m_errorReporter.declarationError(
+ _funDef.location,
+ "Function name " + _funDef.name.str() + " already taken in this scope."
+ );
+ success = false;
+ }
+
+ auto virtualBlock = m_info.virtualBlocks[&_funDef] = make_shared<Block>();
+ Scope& varScope = scope(virtualBlock.get());
+ varScope.superScope = m_currentScope;
+ m_currentScope = &varScope;
+ varScope.functionScope = true;
+ for (auto const& var: _funDef.parameters + _funDef.returnVariables)
+ if (!registerVariable(var, _funDef.location, varScope))
+ success = false;
+
+ if (!(*this)(_funDef.body))
+ success = false;
+
+ solAssert(m_currentScope == &varScope, "");
+ m_currentScope = m_currentScope->superScope;
+
+ return success;
+}
+
+bool ScopeFiller::operator()(If const& _if)
+{
+ return (*this)(_if.body);
+}
+
+bool ScopeFiller::operator()(Switch const& _switch)
+{
+ bool success = true;
+ for (auto const& _case: _switch.cases)
+ if (!(*this)(_case.body))
+ success = false;
+ return success;
+}
+
+bool ScopeFiller::operator()(ForLoop const& _forLoop)
+{
+ Scope* originalScope = m_currentScope;
+
+ bool success = true;
+ if (!(*this)(_forLoop.pre))
+ success = false;
+ m_currentScope = &scope(&_forLoop.pre);
+ if (!boost::apply_visitor(*this, *_forLoop.condition))
+ success = false;
+ if (!(*this)(_forLoop.body))
+ success = false;
+ if (!(*this)(_forLoop.post))
+ success = false;
+
+ m_currentScope = originalScope;
+
+ return success;
+}
+
+bool ScopeFiller::operator()(Block const& _block)
+{
+ bool success = true;
+ scope(&_block).superScope = m_currentScope;
+ m_currentScope = &scope(&_block);
+
+ for (auto const& s: _block.statements)
+ if (!boost::apply_visitor(*this, s))
+ success = false;
+
+ m_currentScope = m_currentScope->superScope;
+ return success;
+}
+
+bool ScopeFiller::registerVariable(TypedName const& _name, SourceLocation const& _location, Scope& _scope)
+{
+ if (!_scope.registerVariable(_name.name, _name.type))
+ {
+ //@TODO secondary location
+ m_errorReporter.declarationError(
+ _location,
+ "Variable name " + _name.name.str() + " already taken in this scope."
+ );
+ return false;
+ }
+ return true;
+}
+
+Scope& ScopeFiller::scope(Block const* _block)
+{
+ auto& scope = m_info.scopes[_block];
+ if (!scope)
+ scope = make_shared<Scope>();
+ return *scope;
+}
diff --git a/libyul/AsmScopeFiller.h b/libyul/AsmScopeFiller.h
new file mode 100644
index 00000000..7454fd6c
--- /dev/null
+++ b/libyul/AsmScopeFiller.h
@@ -0,0 +1,88 @@
+/*
+ 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 <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Module responsible for registering identifiers inside their scopes.
+ */
+
+#pragma once
+
+#include <libsolidity/inlineasm/AsmDataForward.h>
+
+#include <boost/variant.hpp>
+
+#include <functional>
+#include <memory>
+
+namespace langutil
+{
+class ErrorReporter;
+struct SourceLocation;
+}
+
+namespace dev
+{
+namespace solidity
+{
+namespace assembly
+{
+
+struct TypedName;
+struct Scope;
+struct AsmAnalysisInfo;
+
+/**
+ * Fills scopes with identifiers and checks for name clashes.
+ * Does not resolve references.
+ */
+class ScopeFiller: public boost::static_visitor<bool>
+{
+public:
+ ScopeFiller(AsmAnalysisInfo& _info, langutil::ErrorReporter& _errorReporter);
+
+ bool operator()(assembly::Instruction const&) { return true; }
+ bool operator()(assembly::Literal const&) { return true; }
+ bool operator()(assembly::Identifier const&) { return true; }
+ bool operator()(assembly::FunctionalInstruction const&) { return true; }
+ bool operator()(assembly::ExpressionStatement const& _expr);
+ bool operator()(assembly::Label const& _label);
+ bool operator()(assembly::StackAssignment const&) { return true; }
+ bool operator()(assembly::Assignment const&) { return true; }
+ bool operator()(assembly::VariableDeclaration const& _variableDeclaration);
+ bool operator()(assembly::FunctionDefinition const& _functionDefinition);
+ bool operator()(assembly::FunctionCall const&) { return true; }
+ bool operator()(assembly::If const& _if);
+ bool operator()(assembly::Switch const& _switch);
+ bool operator()(assembly::ForLoop const& _forLoop);
+ bool operator()(assembly::Block const& _block);
+
+private:
+ bool registerVariable(
+ TypedName const& _name,
+ langutil::SourceLocation const& _location,
+ Scope& _scope
+ );
+
+ Scope& scope(assembly::Block const* _block);
+
+ Scope* m_currentScope = nullptr;
+ AsmAnalysisInfo& m_info;
+ langutil::ErrorReporter& m_errorReporter;
+};
+
+}
+}
+}