mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Function selector and variable (un)packing.
This commit is contained in:
parent
a5f3602738
commit
cb9cb48dc7
100
Compiler.cpp
100
Compiler.cpp
@ -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
|
||||
|
||||
|
16
Compiler.h
16
Compiler.h
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
8
Types.h
8
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;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user