diff --git a/Compiler.cpp b/Compiler.cpp index fea885607..7e40db15f 100644 --- a/Compiler.cpp +++ b/Compiler.cpp @@ -51,16 +51,106 @@ void Compiler::compileContract(ContractDefinition& _contract) function->accept(*this); } -void Compiler::appendFunctionSelector(std::vector> const&) +void Compiler::appendFunctionSelector(vector> const& _functions) { - // filter public functions, and sort by name. Then select function from first byte, - // unpack arguments from calldata, push to stack and jump. Pack return values to - // output and return. + // sort all public functions and store them together with a tag for their argument decoding section + map> publicFunctions; + for (ASTPointer const& f: _functions) + if (f->isPublic()) + publicFunctions.insert(make_pair(f->getName(), make_pair(f.get(), m_context.newTag()))); + + //@todo remove constructor + + if (publicFunctions.size() > 255) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("More than 255 public functions for contract.")); + + //@todo check for calldatasize? + // retrieve the first byte of the call data + m_context << u256(0) << eth::Instruction::CALLDATALOAD << u256(0) << eth::Instruction::BYTE; + // check that it is not too large + m_context << eth::Instruction::DUP1 << u256(publicFunctions.size() - 1) << eth::Instruction::LT; + eth::AssemblyItem returnTag = m_context.appendConditionalJump(); + + // otherwise, jump inside jump table (each entry of the table has size 4) + m_context << u256(4) << eth::Instruction::MUL; + eth::AssemblyItem jumpTableStart = m_context.pushNewTag(); + m_context << eth::Instruction::ADD << eth::Instruction::JUMP; + + // jump table @todo it could be that the optimizer destroys this + m_context << jumpTableStart; + for (pair> const& f: publicFunctions) + m_context.appendJumpTo(f.second.second) << eth::Instruction::JUMPDEST; + + m_context << returnTag << eth::Instruction::RETURN; + + for (pair> const& f: publicFunctions) + { + m_context << f.second.second; + appendFunctionCallSection(*f.second.first); + } +} + +void Compiler::appendFunctionCallSection(FunctionDefinition const& _function) +{ + eth::AssemblyItem returnTag = m_context.pushNewTag(); + + appendCalldataUnpacker(_function); + + m_context.appendJumpTo(m_context.getFunctionEntryLabel(_function)); + m_context << returnTag; + + appendReturnValuePacker(_function); +} + +void Compiler::appendCalldataUnpacker(FunctionDefinition const& _function) +{ + // We do not check the calldata size, everything is zero-padded. + unsigned dataOffset = 1; + + //@todo this can be done more efficiently, saving some CALLDATALOAD calls + for (ASTPointer const& var: _function.getParameters()) + { + unsigned const numBytes = var->getType()->getCalldataEncodedSize(); + if (numBytes == 0) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(var->getLocation()) + << errinfo_comment("Type not yet supported.")); + if (numBytes == 32) + m_context << u256(dataOffset) << eth::Instruction::CALLDATALOAD; + else + m_context << (u256(1) << ((32 - numBytes) * 8)) << u256(dataOffset) + << eth::Instruction::CALLDATALOAD << eth::Instruction::DIV; + dataOffset += numBytes; + } +} + +void Compiler::appendReturnValuePacker(FunctionDefinition const& _function) +{ + //@todo this can be also done more efficiently + unsigned dataOffset = 0; + vector> const& parameters = _function.getReturnParameters(); + for (unsigned i = 0 ; i < parameters.size(); ++i) + { + unsigned numBytes = parameters[i]->getType()->getCalldataEncodedSize(); + if (numBytes == 0) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(parameters[i]->getLocation()) + << errinfo_comment("Type not yet supported.")); + m_context << eth::dupInstruction(parameters.size() - i); + if (numBytes == 32) + m_context << u256(dataOffset) << eth::Instruction::MSTORE; + else + m_context << u256(dataOffset) << (u256(1) << ((32 - numBytes) * 8)) + << eth::Instruction::MUL << eth::Instruction::MSTORE; + dataOffset += numBytes; + } + // note that the stack is not cleaned up here + m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN; } bool Compiler::visit(FunctionDefinition& _function) { - //@todo to simplify this, the colling convention could by changed such that + //@todo to simplify this, the calling convention could by changed such that // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] // although note that this reduces the size of the visible stack diff --git a/Compiler.h b/Compiler.h index 5f6d9b4e9..ebd786658 100644 --- a/Compiler.h +++ b/Compiler.h @@ -20,6 +20,7 @@ * Solidity AST to EVM bytecode compiler. */ +#include #include #include @@ -29,14 +30,20 @@ namespace solidity { class Compiler: private ASTVisitor { public: + Compiler(): m_returnTag(m_context.newTag()) {} + + void compileContract(ContractDefinition& _contract); + bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); } + void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); } + /// Compile the given contract and return the EVM bytecode. static bytes compile(ContractDefinition& _contract); private: - Compiler(): m_returnTag(m_context.newTag()) {} - - void compileContract(ContractDefinition& _contract); - void appendFunctionSelector(const std::vector >& _functions); + void appendFunctionSelector(std::vector > const& _functions); + void appendFunctionCallSection(FunctionDefinition const& _function); + void appendCalldataUnpacker(FunctionDefinition const& _function); + void appendReturnValuePacker(FunctionDefinition const& _function); virtual bool visit(FunctionDefinition& _function) override; virtual bool visit(IfStatement& _ifStatement) override; @@ -47,7 +54,6 @@ private: virtual bool visit(VariableDefinition& _variableDefinition) override; virtual bool visit(ExpressionStatement& _expressionStatement) override; - bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); } CompilerContext m_context; std::vector m_breakTags; ///< tag to jump to for a "break" statement diff --git a/CompilerContext.h b/CompilerContext.h index 90367903b..cce5838ef 100644 --- a/CompilerContext.h +++ b/CompilerContext.h @@ -22,6 +22,7 @@ #pragma once +#include #include #include #include @@ -69,7 +70,8 @@ public: CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } - bytes getAssembledBytecode() { return m_asm.assemble(); } + void streamAssembly(std::ostream& _stream) const { _stream << m_asm; } + bytes getAssembledBytecode() const { return m_asm.assemble(); } private: eth::Assembly m_asm; diff --git a/Types.h b/Types.h index 828f48095..d7c3b241f 100644 --- a/Types.h +++ b/Types.h @@ -68,6 +68,10 @@ public: virtual bool operator==(Type const& _other) const { return getCategory() == _other.getCategory(); } virtual bool operator!=(Type const& _other) const { return !this->operator ==(_other); } + /// @returns number of bytes used by this type when encoded for CALL, or 0 if the encoding + /// is not a simple big-endian encoding or the type cannot be stored on the stack. + virtual unsigned getCalldataEncodedSize() const { return 0; } + virtual std::string toString() const = 0; virtual u256 literalValue(Literal const&) const { assert(false); } }; @@ -93,6 +97,8 @@ public: virtual bool operator==(Type const& _other) const override; + virtual unsigned getCalldataEncodedSize() const { return m_bits / 8; } + virtual std::string toString() const override; virtual u256 literalValue(Literal const& _literal) const override; @@ -121,6 +127,8 @@ public: return _operator == Token::NOT || _operator == Token::DELETE; } + virtual unsigned getCalldataEncodedSize() const { return 1; } + virtual std::string toString() const override { return "bool"; } virtual u256 literalValue(Literal const& _literal) const override; };