diff options
author | chriseth <c@ethdev.com> | 2016-03-02 05:56:39 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2016-03-30 08:37:00 +0800 |
commit | f0494307232e52dcc268f5f32d26cc89d7e98e3a (patch) | |
tree | 5a03eae3515eb50d67388e7d7d1193d016baaddf /libsolidity | |
parent | 949b00ed591303c531ed8fa73087b710b7a554de (diff) | |
download | dexon-solidity-f0494307232e52dcc268f5f32d26cc89d7e98e3a.tar.gz dexon-solidity-f0494307232e52dcc268f5f32d26cc89d7e98e3a.tar.zst dexon-solidity-f0494307232e52dcc268f5f32d26cc89d7e98e3a.zip |
Code generation (missing external access and source locations).
Diffstat (limited to 'libsolidity')
-rw-r--r-- | libsolidity/analysis/ReferencesResolver.cpp | 22 | ||||
-rw-r--r-- | libsolidity/analysis/ReferencesResolver.h | 1 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 57 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.h | 1 | ||||
-rw-r--r-- | libsolidity/ast/AST.cpp | 7 | ||||
-rw-r--r-- | libsolidity/ast/AST.h | 15 | ||||
-rw-r--r-- | libsolidity/ast/ASTAnnotations.h | 11 | ||||
-rw-r--r-- | libsolidity/codegen/Compiler.cpp | 86 | ||||
-rw-r--r-- | libsolidity/codegen/Compiler.h | 1 | ||||
-rw-r--r-- | libsolidity/codegen/CompilerContext.h | 2 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmCodeGen.cpp | 263 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmCodeGen.h | 66 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmData.h | 60 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.cpp | 67 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmParser.h | 21 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmStack.cpp | 47 | ||||
-rw-r--r-- | libsolidity/inlineasm/AsmStack.h | 59 | ||||
-rw-r--r-- | libsolidity/parsing/Parser.cpp | 17 |
18 files changed, 719 insertions, 84 deletions
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index ca002f58..d7542bf3 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -25,6 +25,8 @@ #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/analysis/ConstantEvaluator.h> +#include <libsolidity/inlineasm/AsmCodeGen.h> +#include <libsolidity/inlineasm/AsmData.h> using namespace std; using namespace dev; @@ -112,6 +114,26 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) _typeName.annotation().type = make_shared<ArrayType>(DataLocation::Storage, baseType); } +bool ReferencesResolver::visit(InlineAssembly const& _inlineAssembly) +{ + // We need to perform a full code generation pass here as inline assembly does not distinguish + // reference resolution and code generation. + // Errors created in this stage are completely ignored because we do not yet know + // the type and size of external identifiers, which would result in false errors. + ErrorList errorsIgnored; + assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errorsIgnored); + codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly&, assembly::CodeGenerator::IdentifierContext) { + auto declarations = m_resolver.nameFromCurrentScope(_identifier.name); + if (declarations.size() != 1) + return false; + _inlineAssembly.annotation().externalReferences[&_identifier] = declarations.front(); + // At this stage we neither know the code to generate nor the stack size of the identifier, + // so we do not modify assembly. + return true; + }); + return false; +} + bool ReferencesResolver::visit(Return const& _return) { _return.annotation().functionReturnParameters = m_returnParameters; diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index a2d71dc3..1986b2bb 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -64,6 +64,7 @@ private: virtual void endVisit(UserDefinedTypeName const& _typeName) override; virtual void endVisit(Mapping const& _typeName) override; virtual void endVisit(ArrayTypeName const& _typeName) override; + virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(Return const& _return) override; virtual void endVisit(VariableDeclaration const& _variable) override; diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 44f4629b..c63b6b5a 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -24,6 +24,8 @@ #include <memory> #include <boost/range/adaptor/reversed.hpp> #include <libsolidity/ast/AST.h> +#include <libevmasm/Assembly.h> // needed for inline assembly +#include <libsolidity/inlineasm/AsmCodeGen.h> using namespace std; using namespace dev; @@ -547,6 +549,61 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) return false; } +bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) +{ + // Inline assembly does not have its own type-checking phase, so we just run the + // code-generator and see whether it produces any errors. + // External references have already been resolved in a prior stage and stored in the annotation. + assembly::CodeGenerator codeGen(_inlineAssembly.operations(), m_errors); + codeGen.typeCheck([&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { + auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); + if (ref == _inlineAssembly.annotation().externalReferences.end()) + return false; + Declaration const* declaration = ref->second; + solAssert(!!declaration, ""); + if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + { + solAssert(!!declaration->type(), "Type of declaration required but not yet determined."); + unsigned pushes = 0; + if (dynamic_cast<FunctionDefinition const*>(declaration)) + pushes = 1; + else if (auto var = dynamic_cast<VariableDeclaration const*>(declaration)) + { + if (var->isConstant()) + fatalTypeError(SourceLocation(), "Constant variables not yet implemented for inline assembly."); + if (var->isLocalVariable()) + pushes = var->type()->sizeOnStack(); + else if (var->type()->isValueType()) + pushes = 1; + else + pushes = 2; // slot number, intra slot offset + } + else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration)) + { + if (!contract->isLibrary()) + return false; + pushes = 1; + } + for (unsigned i = 0; i < pushes; ++i) + _assembly.append(u256(0)); // just to verify the stack height + } + else + { + // lvalue context + if (auto varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) + { + if (!varDecl->isLocalVariable()) + return false; // only local variables are inline-assemlby lvalues + for (unsigned i = 0; i < declaration->type()->sizeOnStack(); ++i) + _assembly.append(eth::Instruction::POP); // remove value just to verify the stack height + } + else + return false; + } + return true; + }); + return false; +} bool TypeChecker::visit(IfStatement const& _ifStatement) { diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index b884db49..48f8285a 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -84,6 +84,7 @@ private: /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases); virtual bool visit(EventDefinition const& _eventDef) override; + virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; virtual bool visit(ForStatement const& _forStatement) override; diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index b5affa8e..294daa13 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -363,6 +363,13 @@ StatementAnnotation& Statement::annotation() const return static_cast<StatementAnnotation&>(*m_annotation); } +InlineAssemblyAnnotation& InlineAssembly::annotation() const +{ + if (!m_annotation) + m_annotation = new InlineAssemblyAnnotation(); + return static_cast<InlineAssemblyAnnotation&>(*m_annotation); +} + ReturnAnnotation& Return::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index f53c78f2..7bb2529a 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -855,8 +855,11 @@ public: virtual StatementAnnotation& annotation() const override; }; -// Forward-declaration to InlineAssembly.h -class AsmData; +namespace assembly +{ +// Forward-declaration to AsmData.h +struct Block; +} /** * Inline assembly. @@ -867,16 +870,18 @@ public: InlineAssembly( SourceLocation const& _location, ASTPointer<ASTString> const& _docString, - std::shared_ptr<AsmData> const& _operations + std::shared_ptr<assembly::Block> const& _operations ): Statement(_location, _docString), m_operations(_operations) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; - AsmData const& operations() const { return *m_operations; } + assembly::Block const& operations() const { return *m_operations; } + + virtual InlineAssemblyAnnotation& annotation() const override; private: - std::shared_ptr<AsmData> m_operations; + std::shared_ptr<assembly::Block> m_operations; }; /** diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 235338bb..2a192e47 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -110,6 +110,17 @@ struct StatementAnnotation: ASTAnnotation, DocumentedAnnotation { }; +namespace assembly +{ +struct Identifier; // forward +} + +struct InlineAssemblyAnnotation: StatementAnnotation +{ + /// Mapping containing resolved references to external identifiers. + std::map<assembly::Identifier const*, Declaration const*> externalReferences; +}; + struct ReturnAnnotation: StatementAnnotation { /// Reference to the return parameters of the function. diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index c7eb71a8..69e23359 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -26,6 +26,7 @@ #include <libevmcore/Instruction.h> #include <libethcore/ChainOperationParams.h> #include <libevmasm/Assembly.h> +#include <libsolidity/inlineasm/AsmCodeGen.h> #include <libsolidity/ast/AST.h> #include <libsolidity/codegen/ExpressionCompiler.h> #include <libsolidity/codegen/CompilerUtils.h> @@ -497,6 +498,91 @@ bool Compiler::visit(FunctionDefinition const& _function) return false; } +bool Compiler::visit(InlineAssembly const& _inlineAssembly) +{ + ErrorList errors; + assembly::CodeGenerator codeGen(_inlineAssembly.operations(), errors); + int startStackHeight = m_context.stackHeight(); + m_context.appendInlineAssembly(codeGen.assemble( + [&](assembly::Identifier const& _identifier, eth::Assembly& _assembly, assembly::CodeGenerator::IdentifierContext _context) { + auto ref = _inlineAssembly.annotation().externalReferences.find(&_identifier); + if (ref == _inlineAssembly.annotation().externalReferences.end()) + return false; + Declaration const* decl = ref->second; + solAssert(!!decl, ""); + if (_context == assembly::CodeGenerator::IdentifierContext::RValue) + { + solAssert(!!decl->type(), "Type of declaration required but not yet determined."); + if (/*FunctionDefinition const* functionDef = */dynamic_cast<FunctionDefinition const*>(decl)) + { + solAssert(false, "Referencing local functions in inline assembly not yet implemented."); + // This does not work directly, because the label does not exist in _assembly + // (it is a fresh assembly object). + // _assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag()); + } + else if (auto variable = dynamic_cast<VariableDeclaration const*>(decl)) + { + solAssert(!variable->isConstant(), ""); + if (m_context.isLocalVariable(variable)) + { + int stackDiff = _assembly.deposit() + startStackHeight - m_context.baseStackOffsetOfVariable(*variable); + if (stackDiff < 1 || stackDiff > 16) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_comment("Stack too deep, try removing local variables.") + ); + for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i) + _assembly.append(eth::dupInstruction(stackDiff)); + } + else + { + solAssert(m_context.isStateVariable(variable), "Invalid variable type."); + auto const& location = m_context.storageLocationOfVariable(*variable); + if (!variable->type()->isValueType()) + { + solAssert(location.second == 0, "Intra-slot offest assumed to be zero."); + _assembly.append(location.first); + } + else + { + _assembly.append(location.first); + _assembly.append(u256(location.second)); + } + } + } + else if (auto contract = dynamic_cast<ContractDefinition const*>(decl)) + { + solAssert(contract->isLibrary(), ""); + _assembly.appendLibraryAddress(contract->name()); + } + else + solAssert(false, "Invalid declaration type."); + } else { + // lvalue context + auto variable = dynamic_cast<VariableDeclaration const*>(decl); + solAssert( + !!variable || !m_context.isLocalVariable(variable), + "Can only assign to stack variables in inline assembly." + ); + unsigned size = variable->type()->sizeOnStack(); + int stackDiff = _assembly.deposit() + startStackHeight - m_context.baseStackOffsetOfVariable(*variable) - size; + if (stackDiff > 16 || stackDiff < 1) + BOOST_THROW_EXCEPTION( + CompilerError() << + errinfo_comment("Stack too deep, try removing local variables.") + ); + for (unsigned i = 0; i < size; ++i) { + _assembly.append(eth::swapInstruction(stackDiff)); + _assembly.append(eth::Instruction::POP); + } + } + return true; + } + )); + solAssert(errors.empty(), "Code generation for inline assembly with errors requested."); + return false; +} + bool Compiler::visit(IfStatement const& _ifStatement) { StackHeightChecker checker(m_context); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index fa33bd30..68ad904a 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -94,6 +94,7 @@ private: virtual bool visit(VariableDeclaration const& _variableDeclaration) override; virtual bool visit(FunctionDefinition const& _function) override; + virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; virtual bool visit(ForStatement const& _forStatement) override; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 5287088a..bd8fc295 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -109,6 +109,8 @@ public: /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) /// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset. eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); } + /// Appends the given code (used by inline assembly) ignoring any stack height changes. + void appendInlineAssembly(eth::Assembly const& _assembly) { int deposit = m_asm.deposit(); m_asm.append(_assembly); m_asm.setDeposit(deposit); } /// Pushes the size of the final program void appendProgramSize() { return m_asm.appendProgramSize(); } /// Adds data to the data section, pushes a reference to the stack diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp new file mode 100644 index 00000000..89e95bc1 --- /dev/null +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -0,0 +1,263 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. 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 <memory> +#include <functional> +#include <libevmasm/Assembly.h> +#include <libevmasm/SourceLocation.h> +#include <libsolidity/inlineasm/AsmParser.h> +#include <libsolidity/inlineasm/AsmData.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::assembly; + +struct GeneratorState +{ + explicit GeneratorState(ErrorList& _errors): errors(_errors) {} + + void addError(Error::Type _type, std::string const& _description, SourceLocation const& _location = SourceLocation()) + { + auto err = make_shared<Error>(_type); + if (!_location.isEmpty()) + *err << errinfo_sourceLocation(_location); + *err << errinfo_comment(_description); + errors.push_back(err); + } + + int const* findVariable(string const& _variableName) const + { + auto localVariable = find_if( + variables.rbegin(), + variables.rend(), + [&](pair<string, int> const& _var) { return _var.first == _variableName; } + ); + return localVariable != variables.rend() ? &localVariable->second : nullptr; + } + eth::AssemblyItem const* findLabel(string const& _labelName) const + { + auto label = find_if( + labels.begin(), + labels.end(), + [&](pair<string, eth::AssemblyItem> const& _label) { return _label.first == _labelName; } + ); + return label != labels.end() ? &label->second : nullptr; + } + + eth::Assembly assembly; + map<string, eth::AssemblyItem> labels; + vector<pair<string, int>> variables; ///< name plus stack height + ErrorList& errors; +}; + +/** + * Scans the inline assembly data for labels, creates tags in the assembly and searches for + * duplicate labels. + */ +class LabelOrganizer: public boost::static_visitor<> +{ +public: + LabelOrganizer(GeneratorState& _state): m_state(_state) {} + + template <class T> + void operator()(T const& /*_item*/) { } + void operator()(Label const& _item) + { + if (m_state.labels.count(_item.name)) + //@TODO location and secondary location + m_state.addError(Error::Type::DeclarationError, "Label " + _item.name + " declared twice."); + m_state.labels.insert(make_pair(_item.name, m_state.assembly.newTag())); + } + void operator()(assembly::Block const& _block) + { + std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); + } + +private: + GeneratorState& m_state; +}; + +class CodeTransform: public boost::static_visitor<> +{ +public: + /// Create the code transformer which appends assembly to _state.assembly when called + /// with parsed assembly data. + /// @param _identifierAccess used to resolve identifiers external to the inline assembly + explicit CodeTransform( + GeneratorState& _state, + assembly::CodeGenerator::IdentifierAccess const& _identifierAccess = assembly::CodeGenerator::IdentifierAccess() + ): + m_state(_state) + { + if (_identifierAccess) + m_identifierAccess = _identifierAccess; + else + m_identifierAccess = [](assembly::Identifier const&, eth::Assembly&, CodeGenerator::IdentifierContext) { return false; }; + } + + void operator()(Instruction const& _instruction) + { + m_state.assembly.append(_instruction.instruction); + } + void operator()(assembly::Literal const& _literal) + { + if (_literal.isNumber) + m_state.assembly.append(u256(_literal.value)); + else if (_literal.value.size() > 32) + m_state.addError( + Error::Type::TypeError, + "String literal too long (" + boost::lexical_cast<string>(_literal.value.size()) + " > 32)" + ); + else + m_state.assembly.append(_literal.value); + } + void operator()(assembly::Identifier const& _identifier) + { + // First search local variables, then labels, then externals. + if (int const* stackHeight = m_state.findVariable(_identifier.name)) + { + int heightDiff = m_state.assembly.deposit() - *stackHeight; + if (heightDiff <= 0 || heightDiff > 16) + //@TODO location + m_state.addError( + Error::Type::TypeError, + "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")" + ); + else + m_state.assembly.append(eth::dupInstruction(heightDiff)); + return; + } + else if (eth::AssemblyItem const* label = m_state.findLabel(_identifier.name)) + m_state.assembly.append(label->pushTag()); + else if (!m_identifierAccess(_identifier, m_state.assembly, CodeGenerator::IdentifierContext::RValue)) + m_state.addError( + Error::Type::DeclarationError, + "Identifier \"" + string(_identifier.name) + "\" not found or not unique" + ); + } + void operator()(FunctionalInstruction const& _instr) + { + for (auto it = _instr.arguments.rbegin(); it != _instr.arguments.rend(); ++it) + { + int height = m_state.assembly.deposit(); + boost::apply_visitor(*this, *it); + expectDeposit(1, height); + } + (*this)(_instr.instruction); + } + void operator()(Label const& _label) + { + m_state.assembly.append(m_state.labels.at(_label.name)); + } + void operator()(assembly::Assignment const& _assignment) + { + generateAssignment(_assignment.variableName); + } + void operator()(FunctionalAssignment const& _assignment) + { + int height = m_state.assembly.deposit(); + boost::apply_visitor(*this, *_assignment.value); + expectDeposit(1, height); + generateAssignment(_assignment.variableName); + } + void operator()(assembly::VariableDeclaration const& _varDecl) + { + int height = m_state.assembly.deposit(); + boost::apply_visitor(*this, *_varDecl.value); + expectDeposit(1, height); + m_state.variables.push_back(make_pair(_varDecl.name, height)); + } + void operator()(assembly::Block const& _block) + { + size_t numVariables = m_state.variables.size(); + std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); + // pop variables + //@TODO check height before and after + while (m_state.variables.size() > numVariables) + { + m_state.assembly.append(eth::Instruction::POP); + m_state.variables.pop_back(); + } + } + +private: + void generateAssignment(assembly::Identifier const& _variableName) + { + if (int const* stackHeight = m_state.findVariable(_variableName.name)) + { + int heightDiff = m_state.assembly.deposit() - *stackHeight - 1; + if (heightDiff <= 0 || heightDiff > 16) + //@TODO location + m_state.addError( + Error::Type::TypeError, + "Variable inaccessible, too deep inside stack (" + boost::lexical_cast<string>(heightDiff) + ")" + ); + else + { + m_state.assembly.append(eth::swapInstruction(heightDiff)); + m_state.assembly.append(eth::Instruction::POP); + } + return; + } + else if (!m_identifierAccess(_variableName, m_state.assembly, CodeGenerator::IdentifierContext::LValue)) + m_state.addError( + Error::Type::DeclarationError, + "Identifier \"" + string(_variableName.name) + "\" not found, not unique or not lvalue." + ); + } + + void expectDeposit(int _deposit, int _oldHeight) + { + if (m_state.assembly.deposit() != _oldHeight + 1) + //@TODO location + m_state.addError(Error::Type::TypeError, + "Expected instruction(s) to deposit " + + boost::lexical_cast<string>(_deposit) + + " item(s) to the stack, but did deposit " + + boost::lexical_cast<string>(m_state.assembly.deposit() - _oldHeight) + + " item(s)." + ); + } + + GeneratorState& m_state; + assembly::CodeGenerator::IdentifierAccess m_identifierAccess; +}; + +bool assembly::CodeGenerator::typeCheck(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) +{ + size_t initialErrorLen = m_errors.size(); + GeneratorState state(m_errors); + (LabelOrganizer(state))(m_parsedData); + (CodeTransform(state, _identifierAccess))(m_parsedData); + return m_errors.size() == initialErrorLen; +} + +eth::Assembly assembly::CodeGenerator::assemble(assembly::CodeGenerator::IdentifierAccess const& _identifierAccess) +{ + GeneratorState state(m_errors); + (LabelOrganizer(state))(m_parsedData); + (CodeTransform(state, _identifierAccess))(m_parsedData); + return state.assembly; +} + diff --git a/libsolidity/inlineasm/AsmCodeGen.h b/libsolidity/inlineasm/AsmCodeGen.h new file mode 100644 index 00000000..f749ba50 --- /dev/null +++ b/libsolidity/inlineasm/AsmCodeGen.h @@ -0,0 +1,66 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2016 + * Code-generating part of inline assembly. + */ + +#pragma once + +#include <functional> +#include <libsolidity/interface/Exceptions.h> + +namespace dev +{ +namespace eth +{ +class Assembly; +} +namespace solidity +{ +namespace assembly +{ +struct Block; +struct Identifier; + +class CodeGenerator +{ +public: + enum class IdentifierContext { LValue, RValue }; + /// Function type that is called for external identifiers. Such a function should search for + /// the identifier and append appropriate assembly items to the assembly. If in lvalue context, + /// the value to assign is assumed to be on the stack and an assignment is to be performed. + /// If in rvalue context, the function is assumed to append instructions to + /// push the value of the identifier onto the stack. On error, the function should return false. + using IdentifierAccess = std::function<bool(assembly::Identifier const&, eth::Assembly&, IdentifierContext)>; + CodeGenerator( Block const& _parsedData, ErrorList& _errors): + m_parsedData(_parsedData), m_errors(_errors) {} + /// Performs type checks and @returns false on error. + /// Actually runs the full code generation but discards the result. + bool typeCheck(IdentifierAccess const& _identifierAccess = IdentifierAccess()); + /// Performs code generation and @returns the result. + eth::Assembly assemble(IdentifierAccess const& _identifierAccess = IdentifierAccess()); + +private: + Block const& m_parsedData; + ErrorList& m_errors; +}; + +} +} +} diff --git a/libsolidity/inlineasm/AsmData.h b/libsolidity/inlineasm/AsmData.h index a38a9d36..0361a4c2 100644 --- a/libsolidity/inlineasm/AsmData.h +++ b/libsolidity/inlineasm/AsmData.h @@ -29,42 +29,36 @@ namespace dev { namespace solidity { - -class AsmData +namespace assembly { -public: - /// Direct EVM instruction (except PUSHi and JUMPDEST) - struct Instruction { eth::Instruction instruction; }; - /// Literal number or string (up to 32 bytes) - struct Literal { bool isNumber; std::string value; }; - /// External / internal identifier or label reference - struct Identifier { std::string name; }; - struct FunctionalInstruction; - /// Jump label ("name:") - struct Label { std::string name; }; - /// Assignemnt (":= x", moves stack top into x, potentially multiple slots) - struct Assignment { Identifier variableName; }; - struct FunctionalAssignment; - struct VariableDeclaration; - struct Block; - using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionalInstruction, VariableDeclaration, Block>; - /// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand - /// side and requires x to occupy exactly one stack slot. - struct FunctionalAssignment { Identifier variableName; std::shared_ptr<Statement> value; }; - /// Functional instruction, e.g. "mul(mload(20), add(2, x))" - struct FunctionalInstruction { Instruction instruction; std::vector<Statement> arguments; }; - /// Block-scope variable declaration ("let x := mload(20)"), non-hoisted - struct VariableDeclaration { std::string name; std::shared_ptr<Statement> value; }; - /// Block that creates a scope (frees declared stack variables) - struct Block { std::vector<Statement> statements; }; - - AsmData(Block&& _statements): m_statements(_statements) {} - Block const& statements() const { return m_statements; } +/// What follows are the AST nodes for assembly. -private: - Block m_statements; -}; +/// Direct EVM instruction (except PUSHi and JUMPDEST) +struct Instruction { eth::Instruction instruction; }; +/// Literal number or string (up to 32 bytes) +struct Literal { bool isNumber; std::string value; }; +/// External / internal identifier or label reference +struct Identifier { std::string name; }; +struct FunctionalInstruction; +/// Jump label ("name:") +struct Label { std::string name; }; +/// Assignemnt (":= x", moves stack top into x, potentially multiple slots) +struct Assignment { Identifier variableName; }; +struct FunctionalAssignment; +struct VariableDeclaration; +struct Block; +using Statement = boost::variant<Instruction, Literal, Label, Assignment, Identifier, FunctionalAssignment, FunctionalInstruction, VariableDeclaration, Block>; +/// Functional assignment ("x := mload(20)", expects push-1-expression on the right hand +/// side and requires x to occupy exactly one stack slot. +struct FunctionalAssignment { Identifier variableName; std::shared_ptr<Statement> value; }; +/// Functional instruction, e.g. "mul(mload(20), add(2, x))" +struct FunctionalInstruction { Instruction instruction; std::vector<Statement> arguments; }; +/// Block-scope variable declaration ("let x := mload(20)"), non-hoisted +struct VariableDeclaration { std::string name; std::shared_ptr<Statement> value; }; +/// Block that creates a scope (frees declared stack variables) +struct Block { std::vector<Statement> statements; }; } } +} diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp index 28fd5354..124a5d26 100644 --- a/libsolidity/inlineasm/AsmParser.cpp +++ b/libsolidity/inlineasm/AsmParser.cpp @@ -29,13 +29,14 @@ using namespace std; using namespace dev; using namespace dev::solidity; +using namespace dev::solidity::assembly; -shared_ptr<AsmData> InlineAssemblyParser::parse(std::shared_ptr<Scanner> const& _scanner) +shared_ptr<assembly::Block> Parser::parse(std::shared_ptr<Scanner> const& _scanner) { try { m_scanner = _scanner; - return make_shared<AsmData>(parseBlock()); + return make_shared<assembly::Block>(parseBlock()); } catch (FatalError const&) { @@ -45,17 +46,17 @@ shared_ptr<AsmData> InlineAssemblyParser::parse(std::shared_ptr<Scanner> const& return nullptr; } -AsmData::Block InlineAssemblyParser::parseBlock() +assembly::Block Parser::parseBlock() { expectToken(Token::LBrace); - AsmData::Block block; + Block block; while (m_scanner->currentToken() != Token::RBrace) block.statements.emplace_back(parseStatement()); m_scanner->next(); return block; } -AsmData::Statement InlineAssemblyParser::parseStatement() +assembly::Statement Parser::parseStatement() { switch (m_scanner->currentToken()) { @@ -69,8 +70,9 @@ AsmData::Statement InlineAssemblyParser::parseStatement() expectToken(Token::Colon); string name = m_scanner->currentLiteral(); expectToken(Token::Identifier); - return AsmData::Assignment{AsmData::Identifier{name}}; + return assembly::Assignment{assembly::Identifier{name}}; } + case Token::Return: // opcode default: break; } @@ -78,28 +80,28 @@ AsmData::Statement InlineAssemblyParser::parseStatement() // Simple instruction (might turn into functional), // literal, // identifier (might turn into label or functional assignment) - AsmData::Statement statement(parseElementaryOperation()); + Statement statement(parseElementaryOperation()); switch (m_scanner->currentToken()) { case Token::LParen: return parseFunctionalInstruction(statement); case Token::Colon: { - if (statement.type() != typeid(AsmData::Identifier)) + if (statement.type() != typeid(assembly::Identifier)) fatalParserError("Label name / variable name must precede \":\"."); - string const& name = boost::get<AsmData::Identifier>(statement).name; + string const& name = boost::get<assembly::Identifier>(statement).name; m_scanner->next(); if (m_scanner->currentToken() == Token::Assign) { // functional assignment m_scanner->next(); - unique_ptr<AsmData::Statement> value; - value.reset(new AsmData::Statement(parseExpression())); - return AsmData::FunctionalAssignment{{move(name)}, move(value)}; + unique_ptr<Statement> value; + value.reset(new Statement(parseExpression())); + return FunctionalAssignment{{std::move(name)}, std::move(value)}; } else // label - return AsmData::Label{name}; + return Label{name}; } default: break; @@ -107,16 +109,16 @@ AsmData::Statement InlineAssemblyParser::parseStatement() return statement; } -AsmData::Statement InlineAssemblyParser::parseExpression() +assembly::Statement Parser::parseExpression() { - AsmData::Statement operation = parseElementaryOperation(true); + Statement operation = parseElementaryOperation(true); if (m_scanner->currentToken() == Token::LParen) return parseFunctionalInstruction(operation); else return operation; } -AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySinglePusher) +assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher) { // Allowed instructions, lowercase names. static map<string, eth::Instruction> s_instructions; @@ -129,6 +131,8 @@ AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySing ) continue; string name = instruction.first; + if (instruction.second == eth::Instruction::SUICIDE) + name = "selfdestruct"; transform(name.begin(), name.end(), name.begin(), [](unsigned char _c) { return tolower(_c); }); s_instructions[name] = instruction.second; } @@ -138,8 +142,13 @@ AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySing switch (m_scanner->currentToken()) { case Token::Identifier: + case Token::Return: { - string literal = m_scanner->currentLiteral(); + string literal; + if (m_scanner->currentToken() == Token::Return) + literal = "return"; + else + literal = m_scanner->currentLiteral(); // first search the set of instructions. if (s_instructions.count(literal)) { @@ -151,17 +160,17 @@ AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySing fatalParserError("Instruction " + info.name + " not allowed in this context."); } m_scanner->next(); - return AsmData::Instruction{instr}; + return Instruction{instr}; } else m_scanner->next(); - return AsmData::Identifier{literal}; + return Identifier{literal}; break; } case Token::StringLiteral: case Token::Number: { - AsmData::Literal literal{ + Literal literal{ m_scanner->currentToken() == Token::Number, m_scanner->currentLiteral() }; @@ -175,23 +184,23 @@ AsmData::Statement InlineAssemblyParser::parseElementaryOperation(bool _onlySing return {}; } -AsmData::VariableDeclaration InlineAssemblyParser::parseVariableDeclaration() +assembly::VariableDeclaration Parser::parseVariableDeclaration() { expectToken(Token::Let); string name = m_scanner->currentLiteral(); expectToken(Token::Identifier); expectToken(Token::Colon); expectToken(Token::Assign); - unique_ptr<AsmData::Statement> value; - value.reset(new AsmData::Statement(parseExpression())); - return AsmData::VariableDeclaration{name, move(value)}; + unique_ptr<Statement> value; + value.reset(new Statement(parseExpression())); + return VariableDeclaration{name, std::move(value)}; } -AsmData::FunctionalInstruction InlineAssemblyParser::parseFunctionalInstruction(AsmData::Statement const& _instruction) +FunctionalInstruction Parser::parseFunctionalInstruction(assembly::Statement const& _instruction) { - if (_instruction.type() != typeid(AsmData::Instruction)) + if (_instruction.type() != typeid(Instruction)) fatalParserError("Assembly instruction required in front of \"(\")"); - eth::Instruction instr = boost::get<AsmData::Instruction>(_instruction).instruction; + eth::Instruction instr = boost::get<Instruction>(_instruction).instruction; eth::InstructionInfo instrInfo = eth::instructionInfo(instr); if (eth::Instruction::DUP1 <= instr && instr <= eth::Instruction::DUP16) fatalParserError("DUPi instructions not allowed for functional notation"); @@ -199,7 +208,7 @@ AsmData::FunctionalInstruction InlineAssemblyParser::parseFunctionalInstruction( fatalParserError("SWAPi instructions not allowed for functional notation"); expectToken(Token::LParen); - vector<AsmData::Statement> arguments; + vector<Statement> arguments; unsigned args = unsigned(instrInfo.args); for (unsigned i = 0; i < args; ++i) { @@ -208,5 +217,5 @@ AsmData::FunctionalInstruction InlineAssemblyParser::parseFunctionalInstruction( expectToken(Token::Comma); } expectToken(Token::RParen); - return AsmData::FunctionalInstruction{{instr}, move(arguments)}; + return FunctionalInstruction{{instr}, std::move(arguments)}; } diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h index fe84470d..b54da941 100644 --- a/libsolidity/inlineasm/AsmParser.h +++ b/libsolidity/inlineasm/AsmParser.h @@ -31,25 +31,28 @@ namespace dev { namespace solidity { +namespace assembly +{ -class InlineAssemblyParser: public ParserBase +class Parser: public ParserBase { public: - InlineAssemblyParser(ErrorList& _errors): ParserBase(_errors) {} + Parser(ErrorList& _errors): ParserBase(_errors) {} /// Parses an inline assembly block starting with `{` and ending with `}`. /// @returns an empty shared pointer on error. - std::shared_ptr<AsmData> parse(std::shared_ptr<Scanner> const& _scanner); + std::shared_ptr<Block> parse(std::shared_ptr<Scanner> const& _scanner); protected: - AsmData::Block parseBlock(); - AsmData::Statement parseStatement(); + Block parseBlock(); + Statement parseStatement(); /// Parses a functional expression that has to push exactly one stack element - AsmData::Statement parseExpression(); - AsmData::Statement parseElementaryOperation(bool _onlySinglePusher = false); - AsmData::VariableDeclaration parseVariableDeclaration(); - AsmData::FunctionalInstruction parseFunctionalInstruction(AsmData::Statement const& _instruction); + Statement parseExpression(); + Statement parseElementaryOperation(bool _onlySinglePusher = false); + VariableDeclaration parseVariableDeclaration(); + FunctionalInstruction parseFunctionalInstruction(Statement const& _instruction); }; } } +} diff --git a/libsolidity/inlineasm/AsmStack.cpp b/libsolidity/inlineasm/AsmStack.cpp new file mode 100644 index 00000000..22042ada --- /dev/null +++ b/libsolidity/inlineasm/AsmStack.cpp @@ -0,0 +1,47 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2016 + * Full-stack Solidity inline assember. + */ + +#include <libsolidity/inlineasm/AsmStack.h> +#include <memory> +#include <libevmasm/Assembly.h> +#include <libevmasm/SourceLocation.h> +#include <libsolidity/inlineasm/AsmParser.h> +#include <libsolidity/inlineasm/AsmCodeGen.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::assembly; + +bool InlineAssemblyStack::parse(const std::shared_ptr<Scanner>& _scanner) +{ + Parser parser(m_errors); + m_asmBlock = parser.parse(_scanner); + return !!m_asmBlock; +} + +eth::Assembly InlineAssemblyStack::assemble() +{ + CodeGenerator codeGen(*m_asmBlock, m_errors); + return codeGen.assemble(); +} + diff --git a/libsolidity/inlineasm/AsmStack.h b/libsolidity/inlineasm/AsmStack.h new file mode 100644 index 00000000..73ca9583 --- /dev/null +++ b/libsolidity/inlineasm/AsmStack.h @@ -0,0 +1,59 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @author Christian <c@ethdev.com> + * @date 2016 + * Full-stack Solidity inline assember. + */ + +#pragma once + +#include <string> +#include <functional> +#include <libsolidity/interface/Exceptions.h> + +namespace dev +{ +namespace eth +{ +class Assembly; +} +namespace solidity +{ +class Scanner; +namespace assembly +{ +struct Block; + +class InlineAssemblyStack +{ +public: + /// Parse the given inline assembly chunk starting with `{` and ending with the corresponding `}`. + /// @return false or error. + bool parse(std::shared_ptr<Scanner> const& _scanner); + eth::Assembly assemble(); + + ErrorList const& errors() const { return m_errors; } + +private: + std::shared_ptr<Block> m_asmBlock; + ErrorList m_errors; +}; + +} +} +} diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 29377380..bb50f47f 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -735,16 +735,17 @@ ASTPointer<InlineAssembly> Parser::parseInlineAssembly(ASTPointer<ASTString> con { ASTNodeFactory nodeFactory(*this); expectToken(Token::Assembly); - if (m_scanner->currentToken() != Token::StringLiteral) - fatalParserError("Expected assembly name."); - if (m_scanner->currentLiteral() != "evmasm") - fatalParserError("Only \"evmasm\" supported."); - m_scanner->next(); + if (m_scanner->currentToken() == Token::StringLiteral) + { + if (m_scanner->currentLiteral() != "evmasm") + fatalParserError("Only \"evmasm\" supported."); + m_scanner->next(); + } - InlineAssemblyParser parser(m_errors); - shared_ptr<InlineAssemblyBlock> operations = parser.parse(m_scanner); + assembly::Parser asmParser(m_errors); + shared_ptr<assembly::Block> block = asmParser.parse(m_scanner); nodeFactory.markEndPosition(); - return nodeFactory.createNode<InlineAssembly>(_docString, operations); + return nodeFactory.createNode<InlineAssembly>(_docString, block); } ASTPointer<IfStatement> Parser::parseIfStatement(ASTPointer<ASTString> const& _docString) |