Function selector and variable (un)packing.

This commit is contained in:
Christian 2014-10-30 18:15:25 +01:00
parent a5f3602738
commit cb9cb48dc7
4 changed files with 117 additions and 11 deletions

View File

@ -51,16 +51,106 @@ void Compiler::compileContract(ContractDefinition& _contract)
function->accept(*this);
}
void Compiler::appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition>> const&)
void Compiler::appendFunctionSelector(vector<ASTPointer<FunctionDefinition>> 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<string, pair<FunctionDefinition const*, eth::AssemblyItem>> publicFunctions;
for (ASTPointer<FunctionDefinition> 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<string, pair<FunctionDefinition const*, eth::AssemblyItem>> const& f: publicFunctions)
m_context.appendJumpTo(f.second.second) << eth::Instruction::JUMPDEST;
m_context << returnTag << eth::Instruction::RETURN;
for (pair<string, pair<FunctionDefinition const*, eth::AssemblyItem>> 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<VariableDeclaration> 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<ASTPointer<VariableDeclaration>> 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

View File

@ -20,6 +20,7 @@
* Solidity AST to EVM bytecode compiler.
*/
#include <ostream>
#include <libsolidity/ASTVisitor.h>
#include <libsolidity/CompilerContext.h>
@ -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<ASTPointer<FunctionDefinition> >& _functions);
void appendFunctionSelector(std::vector<ASTPointer<FunctionDefinition> > 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<eth::AssemblyItem> m_breakTags; ///< tag to jump to for a "break" statement

View File

@ -22,6 +22,7 @@
#pragma once
#include <ostream>
#include <libevmface/Instruction.h>
#include <liblll/Assembly.h>
#include <libsolidity/Types.h>
@ -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;

View File

@ -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;
};