From f0494307232e52dcc268f5f32d26cc89d7e98e3a Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 1 Mar 2016 22:56:39 +0100 Subject: Code generation (missing external access and source locations). --- libsolidity/analysis/ReferencesResolver.cpp | 22 +++ libsolidity/analysis/ReferencesResolver.h | 1 + libsolidity/analysis/TypeChecker.cpp | 57 ++++++ libsolidity/analysis/TypeChecker.h | 1 + libsolidity/ast/AST.cpp | 7 + libsolidity/ast/AST.h | 15 +- libsolidity/ast/ASTAnnotations.h | 11 ++ libsolidity/codegen/Compiler.cpp | 86 +++++++++ libsolidity/codegen/Compiler.h | 1 + libsolidity/codegen/CompilerContext.h | 2 + libsolidity/inlineasm/AsmCodeGen.cpp | 263 ++++++++++++++++++++++++++++ libsolidity/inlineasm/AsmCodeGen.h | 66 +++++++ libsolidity/inlineasm/AsmData.h | 60 +++---- libsolidity/inlineasm/AsmParser.cpp | 67 ++++--- libsolidity/inlineasm/AsmParser.h | 21 ++- libsolidity/inlineasm/AsmStack.cpp | 47 +++++ libsolidity/inlineasm/AsmStack.h | 59 +++++++ libsolidity/parsing/Parser.cpp | 17 +- solc/CommandLineInterface.cpp | 35 ++-- solc/CommandLineInterface.h | 6 +- test/libsolidity/InlineAssembly.cpp | 41 +++-- test/libsolidity/SolidityEndToEndTest.cpp | 89 ++++++++++ 22 files changed, 866 insertions(+), 108 deletions(-) create mode 100644 libsolidity/inlineasm/AsmCodeGen.cpp create mode 100644 libsolidity/inlineasm/AsmCodeGen.h create mode 100644 libsolidity/inlineasm/AsmStack.cpp create mode 100644 libsolidity/inlineasm/AsmStack.h 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 #include #include +#include +#include using namespace std; using namespace dev; @@ -112,6 +114,26 @@ void ReferencesResolver::endVisit(ArrayTypeName const& _typeName) _typeName.annotation().type = make_shared(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 #include #include +#include // needed for inline assembly +#include 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(declaration)) + pushes = 1; + else if (auto var = dynamic_cast(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(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(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 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(*m_annotation); } +InlineAssemblyAnnotation& InlineAssembly::annotation() const +{ + if (!m_annotation) + m_annotation = new InlineAssemblyAnnotation(); + return static_cast(*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 const& _docString, - std::shared_ptr const& _operations + std::shared_ptr 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 m_operations; + std::shared_ptr 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 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 #include #include +#include #include #include #include @@ -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(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(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(decl)) + { + solAssert(contract->isLibrary(), ""); + _assembly.appendLibraryAddress(contract->name()); + } + else + solAssert(false, "Invalid declaration type."); + } else { + // lvalue context + auto variable = dynamic_cast(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 . +*/ +/** + * @author Christian + * @date 2016 + * Code-generating part of inline assembly. + */ + +#include +#include +#include +#include +#include +#include +#include + +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(_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 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 const& _label) { return _label.first == _labelName; } + ); + return label != labels.end() ? &label->second : nullptr; + } + + eth::Assembly assembly; + map labels; + vector> 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 + 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(_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(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(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(_deposit) + + " item(s) to the stack, but did deposit " + + boost::lexical_cast(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 . +*/ +/** + * @author Christian + * @date 2016 + * Code-generating part of inline assembly. + */ + +#pragma once + +#include +#include + +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; + 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; - /// 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 value; }; - /// Functional instruction, e.g. "mul(mload(20), add(2, x))" - struct FunctionalInstruction { Instruction instruction; std::vector arguments; }; - /// Block-scope variable declaration ("let x := mload(20)"), non-hoisted - struct VariableDeclaration { std::string name; std::shared_ptr value; }; - /// Block that creates a scope (frees declared stack variables) - struct Block { std::vector 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; +/// 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 value; }; +/// Functional instruction, e.g. "mul(mload(20), add(2, x))" +struct FunctionalInstruction { Instruction instruction; std::vector arguments; }; +/// Block-scope variable declaration ("let x := mload(20)"), non-hoisted +struct VariableDeclaration { std::string name; std::shared_ptr value; }; +/// Block that creates a scope (frees declared stack variables) +struct Block { std::vector 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 InlineAssemblyParser::parse(std::shared_ptr const& _scanner) +shared_ptr Parser::parse(std::shared_ptr const& _scanner) { try { m_scanner = _scanner; - return make_shared(parseBlock()); + return make_shared(parseBlock()); } catch (FatalError const&) { @@ -45,17 +46,17 @@ shared_ptr InlineAssemblyParser::parse(std::shared_ptr 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(statement).name; + string const& name = boost::get(statement).name; m_scanner->next(); if (m_scanner->currentToken() == Token::Assign) { // functional assignment m_scanner->next(); - unique_ptr value; - value.reset(new AsmData::Statement(parseExpression())); - return AsmData::FunctionalAssignment{{move(name)}, move(value)}; + unique_ptr 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 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 value; - value.reset(new AsmData::Statement(parseExpression())); - return AsmData::VariableDeclaration{name, move(value)}; + unique_ptr 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(_instruction).instruction; + eth::Instruction instr = boost::get(_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 arguments; + vector 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 parse(std::shared_ptr const& _scanner); + std::shared_ptr parse(std::shared_ptr 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 . +*/ +/** + * @author Christian + * @date 2016 + * Full-stack Solidity inline assember. + */ + +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; +using namespace dev::solidity::assembly; + +bool InlineAssemblyStack::parse(const std::shared_ptr& _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 . +*/ +/** + * @author Christian + * @date 2016 + * Full-stack Solidity inline assember. + */ + +#pragma once + +#include +#include +#include + +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 const& _scanner); + eth::Assembly assemble(); + + ErrorList const& errors() const { return m_errors; } + +private: + std::shared_ptr 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 Parser::parseInlineAssembly(ASTPointer 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 operations = parser.parse(m_scanner); + assembly::Parser asmParser(m_errors); + shared_ptr block = asmParser.parse(m_scanner); nodeFactory.markEndPosition(); - return nodeFactory.createNode(_docString, operations); + return nodeFactory.createNode(_docString, block); } ASTPointer Parser::parseIfStatement(ASTPointer const& _docString) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1cf87bc9..a302e024 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -773,7 +773,7 @@ void CommandLineInterface::handleAst(string const& _argStr) void CommandLineInterface::actOnInput() { if (m_onlyAssemble) - return; //@TODO + outputAssembly(); else if (m_onlyLink) writeLinkedFiles(); else @@ -831,27 +831,40 @@ bool CommandLineInterface::assemble() { //@TODO later, we will use the convenience interface and should also remove the include above bool successful = true; - ErrorList errors; map> scanners; for (auto const& src: m_sourceCodes) { auto scanner = make_shared(CharStream(src.second), src.first); scanners[src.first] = scanner; - InlineAssemblyParser parser(errors); - if (!parser.parse(scanner)) + if (!m_assemblyStacks[src.first].parse(scanner)) successful = false; + else + //@TODO we should not just throw away the result here + m_assemblyStacks[src.first].assemble(); } - for (auto const& error: errors) - SourceReferenceFormatter::printExceptionInformation( - cerr, - *error, - (error->type() == Error::Type::Warning) ? "Warning" : "Error", - [&](string const& _source) -> Scanner const& { return *scanners.at(_source); } - ); + for (auto const& stack: m_assemblyStacks) + for (auto const& error: stack.second.errors()) + SourceReferenceFormatter::printExceptionInformation( + cerr, + *error, + (error->type() == Error::Type::Warning) ? "Warning" : "Error", + [&](string const& _source) -> Scanner const& { return *scanners.at(_source); } + ); return successful; } +void CommandLineInterface::outputAssembly() +{ + for (auto const& src: m_sourceCodes) + { + cout << endl << "======= " << src.first << " =======" << endl; + eth::Assembly assembly = m_assemblyStacks[src.first].assemble(); + cout << assembly.assemble().toHex() << endl; + cout << assembly.out(); + } +} + void CommandLineInterface::outputCompilationResults() { handleCombinedJSON(); diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 1c40e26d..52854bac 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -21,10 +21,11 @@ */ #pragma once -#include #include #include #include +#include +#include namespace dev { @@ -52,6 +53,7 @@ private: /// Parse assembly input. bool assemble(); + void outputAssembly(); void outputCompilationResults(); @@ -91,6 +93,8 @@ private: std::map m_libraries; /// Solidity compiler stack std::unique_ptr m_compiler; + /// Assembly stacks for assembly-only mode + std::map m_assemblyStacks; }; } diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index c435f5d6..153b3fa8 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -23,8 +23,9 @@ #include #include #include +#include #include -#include +#include #include #include #include "../TestHelper.h" @@ -40,32 +41,38 @@ namespace test namespace { -shared_ptr parse(std::string const& _source, ErrorList& _errors) -{ - return InlineAssemblyParser(_errors).parse(std::make_shared(CharStream(_source))); -} -bool successParse(std::string const& _source) +bool successParse(std::string const& _source, bool _assemble = false) { - ErrorList errors; + assembly::InlineAssemblyStack stack; try { - auto assembly = parse(_source, errors); - if (!assembly) + if (!stack.parse(std::make_shared(CharStream(_source)))) return false; + if (_assemble) + { + stack.assemble(); + if (!stack.errors().empty()) + return false; + } } catch (FatalError const&) { - if (Error::containsErrorOfType(errors, Error::Type::ParserError)) + if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) return false; } - if (Error::containsErrorOfType(errors, Error::Type::ParserError)) + if (Error::containsErrorOfType(stack.errors(), Error::Type::ParserError)) return false; - BOOST_CHECK(Error::containsOnlyWarnings(errors)); + BOOST_CHECK(Error::containsOnlyWarnings(stack.errors())); return true; } +bool successAssemble(string const& _source) +{ + return successParse(_source, true); +} + } @@ -131,6 +138,16 @@ BOOST_AUTO_TEST_CASE(blocks) BOOST_CHECK(successParse("{ let x := 7 { let y := 3 } { let z := 2 } }")); } +BOOST_AUTO_TEST_CASE(string_literals) +{ + BOOST_CHECK(successAssemble("{ let x := \"12345678901234567890123456789012\" }")); +} + +BOOST_AUTO_TEST_CASE(oversize_string_literals) +{ + BOOST_CHECK(!successAssemble("{ let x := \"123456789012345678901234567890123\" }")); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 56f07415..663493c9 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -6486,6 +6486,95 @@ BOOST_AUTO_TEST_CASE(fixed_bytes_length_access) BOOST_CHECK(callContractFunction("f(bytes32)", "789") == encodeArgs(u256(32), u256(16), u256(8))); } +BOOST_AUTO_TEST_CASE(inline_assembly_write_to_stack) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint r, bytes32 r2) { + assembly { r := 7 r2 := "abcdef" } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7), string("abcdef"))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_read_and_write_stack) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint r) { + for (uint x = 0; x < 10; ++x) + assembly { r := add(r, x) } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(45))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_memory_access) +{ + char const* sourceCode = R"( + contract C { + function test() returns (bytes) { + bytes memory x = new bytes(5); + for (uint i = 0; i < x.length; ++i) + x[i] = byte(i + 1); + assembly { mstore(add(x, 32), "12345678901234567890123456789012") } + return x; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(0x20), u256(5), string("12345"))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_storage_access) +{ + char const* sourceCode = R"( + contract C { + uint16 x; + uint16 public y; + uint public z; + function f() { + // we know that z is aligned because it is too large, so we just discard its + // intra-slot offset value + assembly { 7 z pop sstore } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("z()") == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(inline_assembly_jumps) +{ + char const* sourceCode = R"( + contract C { + function f() { + assembly { + let n := calldataload(4) + let a := 1 + let b := a + loop: + jumpi(loopend, eq(n, 0)) + a add swap1 + n := sub(n, 1) + jump(loop) + loopend: + mstore(0, a) + return(0, 0x20) + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()", u256(5)) == encodeArgs(u256(13))); + BOOST_CHECK(callContractFunction("f()", u256(7)) == encodeArgs(u256(34))); +} + BOOST_AUTO_TEST_SUITE_END() } -- cgit