aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AST.cpp3
-rw-r--r--Compiler.cpp51
-rw-r--r--CompilerUtils.cpp27
-rw-r--r--CompilerUtils.h16
-rw-r--r--ExpressionCompiler.cpp310
-rw-r--r--ExpressionCompiler.h25
6 files changed, 288 insertions, 144 deletions
diff --git a/AST.cpp b/AST.cpp
index acb7b50c..5be23b7c 100644
--- a/AST.cpp
+++ b/AST.cpp
@@ -469,9 +469,6 @@ void FunctionDefinition::checkTypeRequirements()
{
if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
- // todo delete when will be implemented arrays as parameter type in internal functions
- if (getVisibility() == Visibility::Public && var->getType()->getCategory() == Type::Category::Array)
- BOOST_THROW_EXCEPTION(var->createTypeError("Arrays only implemented for external functions."));
if (getVisibility() >= Visibility::Public && !(var->getType()->externalType()))
BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for public and external functions."));
}
diff --git a/Compiler.cpp b/Compiler.cpp
index 0a75e55a..0d7fbbfe 100644
--- a/Compiler.cpp
+++ b/Compiler.cpp
@@ -52,6 +52,7 @@ void Compiler::compileContract(ContractDefinition const& _contract,
map<ContractDefinition const*, bytes const*> const& _contracts)
{
m_context = CompilerContext(); // clear it just in case
+ CompilerUtils(m_context).initialiseFreeMemoryPointer();
initializeContext(_contract, _contracts);
appendFunctionSelector(_contract);
set<Declaration const*> functions = m_context.getFunctionsWithoutCode();
@@ -67,6 +68,7 @@ void Compiler::compileContract(ContractDefinition const& _contract,
// Swap the runtime context with the creation-time context
swap(m_context, m_runtimeContext);
+ CompilerUtils(m_context).initialiseFreeMemoryPointer();
initializeContext(_contract, _contracts);
packIntoContractCreator(_contract, m_runtimeContext);
if (m_optimize)
@@ -233,31 +235,42 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool
m_context << u256(CompilerUtils::dataStartOffset);
for (TypePointer const& type: _typeParameters)
{
- switch (type->getCategory())
+ if (type->getCategory() == Type::Category::Array)
{
- case Type::Category::Array:
- if (type->isDynamicallySized())
+ auto const& arrayType = dynamic_cast<ArrayType const&>(*type);
+ if (arrayType.location() == ReferenceType::Location::CallData)
{
- // put on stack: data_pointer length
- CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory);
- // stack: data_offset next_pointer
- //@todo once we support nested arrays, this offset needs to be dynamic.
- m_context << eth::Instruction::SWAP1 << u256(CompilerUtils::dataStartOffset);
- m_context << eth::Instruction::ADD;
- // stack: next_pointer data_pointer
- // retrieve length
- CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true);
- // stack: next_pointer length data_pointer
- m_context << eth::Instruction::SWAP2;
+ if (type->isDynamicallySized())
+ {
+ // put on stack: data_pointer length
+ CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory);
+ // stack: data_offset next_pointer
+ //@todo once we support nested arrays, this offset needs to be dynamic.
+ m_context << eth::Instruction::SWAP1 << u256(CompilerUtils::dataStartOffset);
+ m_context << eth::Instruction::ADD;
+ // stack: next_pointer data_pointer
+ // retrieve length
+ CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(256), !_fromMemory, true);
+ // stack: next_pointer length data_pointer
+ m_context << eth::Instruction::SWAP2;
+ }
+ else
+ {
+ // leave the pointer on the stack
+ m_context << eth::Instruction::DUP1;
+ m_context << u256(type->getCalldataEncodedSize()) << eth::Instruction::ADD;
+ }
}
else
{
- // leave the pointer on the stack
- m_context << eth::Instruction::DUP1;
- m_context << u256(type->getCalldataEncodedSize()) << eth::Instruction::ADD;
+ solAssert(arrayType.location() == ReferenceType::Location::Memory, "");
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ CompilerUtils(m_context).storeInMemoryDynamic(*type);
+ CompilerUtils(m_context).storeFreeMemoryPointer();
}
- break;
- default:
+ }
+ else
+ {
solAssert(!type->isDynamicallySized(), "Unknown dynamically sized type: " + type->toString());
CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, true);
}
diff --git a/CompilerUtils.cpp b/CompilerUtils.cpp
index 3549ef98..7a96db92 100644
--- a/CompilerUtils.cpp
+++ b/CompilerUtils.cpp
@@ -31,7 +31,31 @@ namespace dev
namespace solidity
{
-const unsigned int CompilerUtils::dataStartOffset = 4;
+const unsigned CompilerUtils::dataStartOffset = 4;
+const size_t CompilerUtils::freeMemoryPointer = 64;
+
+void CompilerUtils::initialiseFreeMemoryPointer()
+{
+ m_context << u256(freeMemoryPointer + 32);
+ storeFreeMemoryPointer();
+}
+
+void CompilerUtils::fetchFreeMemoryPointer()
+{
+ m_context << u256(freeMemoryPointer) << eth::Instruction::MLOAD;
+}
+
+void CompilerUtils::storeFreeMemoryPointer()
+{
+ m_context << u256(freeMemoryPointer) << eth::Instruction::MSTORE;
+}
+
+void CompilerUtils::toSizeAfterFreeMemoryPointer()
+{
+ fetchFreeMemoryPointer();
+ m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::SUB;
+ m_context << eth::Instruction::SWAP1;
+}
unsigned CompilerUtils::loadFromMemory(
unsigned _offset,
@@ -187,6 +211,7 @@ unsigned CompilerUtils::getSizeOnStack(vector<shared_ptr<Type const>> const& _va
void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundaries)
{
unsigned length = storeInMemory(0, _type, _padToWordBoundaries);
+ solAssert(length <= CompilerUtils::freeMemoryPointer, "");
m_context << u256(length) << u256(0) << eth::Instruction::SHA3;
}
diff --git a/CompilerUtils.h b/CompilerUtils.h
index 45f53e12..27c46ba1 100644
--- a/CompilerUtils.h
+++ b/CompilerUtils.h
@@ -35,6 +35,15 @@ class CompilerUtils
public:
CompilerUtils(CompilerContext& _context): m_context(_context) {}
+ /// Stores the initial value of the free-memory-pointer at its position;
+ void initialiseFreeMemoryPointer();
+ /// Copies the free memory pointer to the stack.
+ void fetchFreeMemoryPointer();
+ /// Stores the free memory pointer from the stack.
+ void storeFreeMemoryPointer();
+ /// Appends code that transforms memptr to (memptr - free_memptr) memptr
+ void toSizeAfterFreeMemoryPointer();
+
/// Loads data from memory to the stack.
/// @param _offset offset in memory (or calldata)
/// @param _type data type to load
@@ -67,7 +76,7 @@ public:
bool _padToWordBoundaries = false
);
/// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack
- /// and also updates that.
+ /// and also updates that. For arrays, only copies the data part.
/// Stack pre: memory_offset value...
/// Stack post: (memory_offset+length)
void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true);
@@ -95,7 +104,10 @@ public:
/// Bytes we need to the start of call data.
/// - The size in bytes of the function (hash) identifier.
- static const unsigned int dataStartOffset;
+ static const unsigned dataStartOffset;
+
+ /// Position of the free-memory-pointer in memory;
+ static const size_t freeMemoryPointer;
private:
/// Prepares the given type for storing in memory by shifting it if necessary.
diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp
index ba80a8ea..d9b6da14 100644
--- a/ExpressionCompiler.cpp
+++ b/ExpressionCompiler.cpp
@@ -73,6 +73,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
{
if (auto mappingType = dynamic_cast<MappingType const*>(returnType.get()))
{
+ solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
// pop offset
m_context << eth::Instruction::POP;
// move storage offset to memory.
@@ -470,21 +471,28 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
_functionCall.getExpression().accept(*this);
solAssert(!function.gasSet(), "Gas limit set for contract creation.");
solAssert(function.getReturnParameterTypes().size() == 1, "");
+ TypePointers argumentTypes;
+ for (auto const& arg: arguments)
+ {
+ arg->accept(*this);
+ argumentTypes.push_back(arg->getType());
+ }
ContractDefinition const& contract = dynamic_cast<ContractType const&>(
*function.getReturnParameterTypes().front()).getContractDefinition();
// copy the contract's code into memory
bytes const& bytecode = m_context.getCompiledContract(contract);
- m_context << u256(bytecode.size());
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ m_context << u256(bytecode.size()) << eth::Instruction::DUP1;
//@todo could be done by actually appending the Assembly, but then we probably need to compile
// multiple times. Will revisit once external fuctions are inlined.
m_context.appendData(bytecode);
- //@todo copy to memory position 0, shift as soon as we use memory
- m_context << u256(0) << eth::Instruction::CODECOPY;
+ m_context << eth::Instruction::DUP4 << eth::Instruction::CODECOPY;
- m_context << u256(bytecode.size());
- appendArgumentsCopyToMemory(arguments, function.getParameterTypes());
- // size, offset, endowment
- m_context << u256(0);
+ m_context << eth::Instruction::ADD;
+ encodeToMemory(argumentTypes, function.getParameterTypes());
+ // now on stack: memory_end_ptr
+ // need: size, offset, endowment
+ CompilerUtils(m_context).toSizeAfterFreeMemoryPointer();
if (function.valueSet())
m_context << eth::dupInstruction(3);
else
@@ -546,12 +554,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
break;
case Location::SHA3:
{
- // we might compute a sha as part of argumentsAppendCopyToMemory, this is only a hack
- // and should be removed once we have a real free memory pointer
- m_context << u256(0x40);
- appendArgumentsCopyToMemory(arguments, TypePointers(), function.padArguments(), false, true);
- m_context << u256(0x40) << eth::Instruction::SWAP1 << eth::Instruction::SUB;
- m_context << u256(0x40) << eth::Instruction::SHA3;
+ TypePointers argumentTypes;
+ for (auto const& arg: arguments)
+ {
+ arg->accept(*this);
+ argumentTypes.push_back(arg->getType());
+ }
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ encodeToMemory(argumentTypes, TypePointers(), function.padArguments(), true);
+ CompilerUtils(m_context).toSizeAfterFreeMemoryPointer();
+ m_context << eth::Instruction::SHA3;
break;
}
case Location::Log0:
@@ -566,9 +578,15 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arguments[arg]->accept(*this);
appendTypeConversion(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true);
}
- m_context << u256(0);
- appendExpressionCopyToMemory(*function.getParameterTypes().front(), *arguments.front());
- m_context << u256(0) << eth::logInstruction(logNumber);
+ arguments.front()->accept(*this);
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ encodeToMemory(
+ {arguments.front()->getType()},
+ {function.getParameterTypes().front()},
+ false,
+ true);
+ CompilerUtils(m_context).toSizeAfterFreeMemoryPointer();
+ m_context << eth::logInstruction(logNumber);
break;
}
case Location::Event:
@@ -582,8 +600,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
++numIndexed;
arguments[arg - 1]->accept(*this);
- appendTypeConversion(*arguments[arg - 1]->getType(),
- *function.getParameterTypes()[arg - 1], true);
+ appendTypeConversion(
+ *arguments[arg - 1]->getType(),
+ *function.getParameterTypes()[arg - 1],
+ true
+ );
}
if (!event.isAnonymous())
{
@@ -593,18 +614,20 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(numIndexed <= 4, "Too many indexed arguments.");
// Copy all non-indexed arguments to memory (data)
// Memory position is only a hack and should be removed once we have free memory pointer.
- m_context << u256(0x40);
- vector<ASTPointer<Expression const>> nonIndexedArgs;
- TypePointers nonIndexedTypes;
+ TypePointers nonIndexedArgTypes;
+ TypePointers nonIndexedParamTypes;
for (unsigned arg = 0; arg < arguments.size(); ++arg)
if (!event.getParameters()[arg]->isIndexed())
{
- nonIndexedArgs.push_back(arguments[arg]);
- nonIndexedTypes.push_back(function.getParameterTypes()[arg]);
+ arguments[arg]->accept(*this);
+ nonIndexedArgTypes.push_back(arguments[arg]->getType());
+ nonIndexedParamTypes.push_back(function.getParameterTypes()[arg]);
}
- appendArgumentsCopyToMemory(nonIndexedArgs, nonIndexedTypes);
- m_context << u256(0x40) << eth::Instruction::SWAP1 << eth::Instruction::SUB;
- m_context << u256(0x40) << eth::logInstruction(numIndexed);
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ encodeToMemory(nonIndexedArgTypes, nonIndexedParamTypes);
+ // need: topic1 ... topicn memsize memstart
+ CompilerUtils(m_context).toSizeAfterFreeMemoryPointer();
+ m_context << eth::logInstruction(numIndexed);
break;
}
case Location::BlockHash:
@@ -804,8 +827,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
Type const& keyType = *dynamic_cast<MappingType const&>(baseType).getKeyType();
m_context << u256(0); // memory position
solAssert(_indexAccess.getIndexExpression(), "Index expression expected.");
+ solAssert(keyType.getCalldataEncodedSize() <= 0x20, "Dynamic keys not yet implemented.");
appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression());
m_context << eth::Instruction::SWAP1;
+ solAssert(CompilerUtils::freeMemoryPointer >= 0x40, "");
appendTypeMoveToMemory(IntegerType(256));
m_context << u256(0) << eth::Instruction::SHA3;
m_context << u256(0);
@@ -1058,42 +1083,81 @@ void ExpressionCompiler::appendExternalFunctionCall(
unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize);
unsigned valueStackPos = m_context.currentToBaseStackOffset(1);
- bool returnSuccessCondition =
- _functionType.getLocation() == FunctionType::Location::Bare ||
- _functionType.getLocation() == FunctionType::Location::BareCallCode;
+ using FunctionKind = FunctionType::Location;
+ FunctionKind funKind = _functionType.getLocation();
+ bool returnSuccessCondition = funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode;
+
//@todo only return the first return value for now
- Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr :
- _functionType.getReturnParameterTypes().front().get();
- unsigned retSize = firstType ? firstType->getCalldataEncodedSize() : 0;
+ Type const* firstReturnType =
+ _functionType.getReturnParameterTypes().empty() ?
+ nullptr :
+ _functionType.getReturnParameterTypes().front().get();
+ unsigned retSize = firstReturnType ? firstReturnType->getCalldataEncodedSize() : 0;
if (returnSuccessCondition)
retSize = 0; // return value actually is success condition
- m_context << u256(retSize) << u256(0);
- if (_functionType.isBareCall())
- m_context << u256(0);
- else
+ // Evaluate arguments.
+ TypePointers argumentTypes;
+ bool manualFunctionId =
+ (funKind == FunctionKind::Bare || funKind == FunctionKind::BareCallCode) &&
+ !_arguments.empty() &&
+ _arguments.front()->getType()->getRealType()->getCalldataEncodedSize(false) ==
+ CompilerUtils::dataStartOffset;
+ if (manualFunctionId)
{
- // copy function identifier
- m_context << eth::dupInstruction(gasValueSize + 3);
- CompilerUtils(m_context).storeInMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8));
- m_context << u256(CompilerUtils::dataStartOffset);
+ // If we have a BareCall or BareCallCode and the first type has exactly 4 bytes, use it as
+ // function identifier.
+ _arguments.front()->accept(*this);
+ appendTypeConversion(
+ *_arguments.front()->getType(),
+ IntegerType(8 * CompilerUtils::dataStartOffset),
+ true
+ );
+ for (unsigned i = 0; i < gasValueSize; ++i)
+ m_context << eth::swapInstruction(gasValueSize - i);
+ gasStackPos++;
+ valueStackPos++;
+ }
+ for (size_t i = manualFunctionId ? 1 : 0; i < _arguments.size(); ++i)
+ {
+ _arguments[i]->accept(*this);
+ argumentTypes.push_back(_arguments[i]->getType());
}
- // For bare call, activate "4 byte pad exception": If the first argument has exactly 4 bytes,
- // do not pad it to 32 bytes.
+ // Copy function identifier to memory.
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ if (!_functionType.isBareCall() || manualFunctionId)
+ {
+ m_context << eth::dupInstruction(2 + gasValueSize + CompilerUtils::getSizeOnStack(argumentTypes));
+ appendTypeMoveToMemory(IntegerType(8 * CompilerUtils::dataStartOffset), false);
+ }
// If the function takes arbitrary parameters, copy dynamic length data in place.
- appendArgumentsCopyToMemory(
- _arguments,
+ // Move argumenst to memory, will not update the free memory pointer (but will update the memory
+ // pointer on the stack).
+ encodeToMemory(
+ argumentTypes,
_functionType.getParameterTypes(),
_functionType.padArguments(),
- _functionType.getLocation() == FunctionType::Location::Bare ||
- _functionType.getLocation() == FunctionType::Location::BareCallCode,
_functionType.takesArbitraryParameters()
);
- // CALL arguments: outSize, outOff, inSize, (already present up to here)
- // inOff, value, addr, gas (stack top)
- m_context << u256(0);
+ // Stack now:
+ // <stack top>
+ // input_memory_end
+ // value [if _functionType.valueSet()]
+ // gas [if _functionType.gasSet()]
+ // function identifier [unless bare]
+ // contract address
+
+ // Output data will replace input data.
+ // put on stack: <size of output> <memory pos of output> <size of input> <memory pos of input>
+ m_context << u256(retSize);
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SUB;
+ m_context << eth::Instruction::DUP2;
+
+ // CALL arguments: outSize, outOff, inSize, inOff (already present up to here)
+ // value, addr, gas (stack top)
if (_functionType.valueSet())
m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos));
else
@@ -1109,19 +1173,16 @@ void ExpressionCompiler::appendExternalFunctionCall(
u256(eth::c_callGas + 10 + (_functionType.valueSet() ? eth::c_callValueTransferGas : 0) + eth::c_callNewAccountGas) <<
eth::Instruction::GAS <<
eth::Instruction::SUB;
- if (
- _functionType.getLocation() == FunctionType::Location::CallCode ||
- _functionType.getLocation() == FunctionType::Location::BareCallCode
- )
+ if (funKind == FunctionKind::CallCode || funKind == FunctionKind::BareCallCode)
m_context << eth::Instruction::CALLCODE;
else
m_context << eth::Instruction::CALL;
unsigned remainsSize =
- 1 + // contract address
+ 2 + // contract address, input_memory_end
_functionType.valueSet() +
_functionType.gasSet() +
- !_functionType.isBareCall();
+ (!_functionType.isBareCall() || manualFunctionId);
if (returnSuccessCondition)
m_context << eth::swapInstruction(remainsSize);
@@ -1138,52 +1199,93 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
// already there
}
- else if (_functionType.getLocation() == FunctionType::Location::RIPEMD160)
+ else if (funKind == FunctionKind::RIPEMD160)
{
// fix: built-in contract returns right-aligned data
- CompilerUtils(m_context).loadFromMemory(0, IntegerType(160), false, true);
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ CompilerUtils(m_context).loadFromMemoryDynamic(IntegerType(160), false, true, false);
appendTypeConversion(IntegerType(160), FixedBytesType(20));
}
- else if (firstType)
- CompilerUtils(m_context).loadFromMemory(0, *firstType, false, true);
+ else if (firstReturnType)
+ {
+ //@todo manually update free memory pointer if we accept returning memory-stored objects
+ CompilerUtils(m_context).fetchFreeMemoryPointer();
+ CompilerUtils(m_context).loadFromMemoryDynamic(*firstReturnType, false, true, false);
+ }
}
-void ExpressionCompiler::appendArgumentsCopyToMemory(
- vector<ASTPointer<Expression const>> const& _arguments,
- TypePointers const& _types,
+void ExpressionCompiler::encodeToMemory(
+ TypePointers const& _givenTypes,
+ TypePointers const& _targetTypes,
bool _padToWordBoundaries,
- bool _padExceptionIfFourBytes,
bool _copyDynamicDataInPlace
)
{
- solAssert(_types.empty() || _types.size() == _arguments.size(), "");
- TypePointers types = _types;
- if (_types.empty())
- for (ASTPointer<Expression const> const& argument: _arguments)
- types.push_back(argument->getType()->getRealType());
-
- vector<size_t> dynamicArguments;
- unsigned stackSizeOfDynamicTypes = 0;
- for (size_t i = 0; i < _arguments.size(); ++i)
+ // stack: <v1> <v2> ... <vn> <mem>
+ TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes;
+ solAssert(targetTypes.size() == _givenTypes.size(), "");
+ for (TypePointer& t: targetTypes)
+ t = t->getRealType()->externalType();
+
+ // Stack during operation:
+ // <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
+ // The values dyn_head_i are added during the first loop and they point to the head part
+ // of the ith dynamic parameter, which is filled once the dynamic parts are processed.
+
+ // store memory start pointer
+ m_context << eth::Instruction::DUP1;
+
+ unsigned argSize = CompilerUtils::getSizeOnStack(_givenTypes);
+ unsigned stackPos = 0; // advances through the argument values
+ unsigned dynPointers = 0; // number of dynamic head pointers on the stack
+ for (size_t i = 0; i < _givenTypes.size(); ++i)
{
- _arguments[i]->accept(*this);
- TypePointer argType = types[i]->externalType();
- solAssert(!!argType, "Externalable type expected.");
- if (argType->isValueType())
- appendTypeConversion(*_arguments[i]->getType(), *argType, true);
+ TypePointer targetType = targetTypes[i];
+ solAssert(!!targetType, "Externalable type expected.");
+ if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
+ {
+ // leave end_of_mem as dyn head pointer
+ m_context << eth::Instruction::DUP1 << u256(32) << eth::Instruction::ADD;
+ dynPointers++;
+ }
else
- argType = _arguments[i]->getType()->getRealType()->externalType();
- solAssert(!!argType, "Externalable type expected.");
- bool pad = _padToWordBoundaries;
- // Do not pad if the first argument has exactly four bytes
- if (i == 0 && pad && _padExceptionIfFourBytes && argType->getCalldataEncodedSize(false) == 4)
- pad = false;
- if (!_copyDynamicDataInPlace && argType->isDynamicallySized())
{
- solAssert(argType->getCategory() == Type::Category::Array, "Unknown dynamic type.");
- auto const& arrayType = dynamic_cast<ArrayType const&>(*_arguments[i]->getType());
- // move memory reference to top of stack
- CompilerUtils(m_context).moveToStackTop(arrayType.getSizeOnStack());
+ CompilerUtils(m_context).copyToStackTop(
+ argSize - stackPos + dynPointers + 2,
+ _givenTypes[i]->getSizeOnStack()
+ );
+ if (targetType->isValueType())
+ appendTypeConversion(*_givenTypes[i], *targetType, true);
+ solAssert(!!targetType, "Externalable type expected.");
+ appendTypeMoveToMemory(*targetType, _padToWordBoundaries);
+ }
+ stackPos += _givenTypes[i]->getSizeOnStack();
+ }
+
+ // now copy the dynamic part
+ // Stack: <v1> <v2> ... <vn> <mem_start> <dyn_head_1> ... <dyn_head_r> <end_of_mem>
+ stackPos = 0;
+ unsigned thisDynPointer = 0;
+ for (size_t i = 0; i < _givenTypes.size(); ++i)
+ {
+ TypePointer targetType = targetTypes[i];
+ solAssert(!!targetType, "Externalable type expected.");
+ if (targetType->isDynamicallySized() && !_copyDynamicDataInPlace)
+ {
+ solAssert(_givenTypes[i]->getCategory() == Type::Category::Array, "Unknown dynamic type.");
+ auto const& arrayType = dynamic_cast<ArrayType const&>(*_givenTypes[i]);
+ // copy tail pointer (=mem_end - mem_start) to memory
+ m_context << eth::dupInstruction(2 + dynPointers) << eth::Instruction::DUP2;
+ m_context << eth::Instruction::SUB;
+ m_context << eth::dupInstruction(2 + dynPointers - thisDynPointer);
+ m_context << eth::Instruction::MSTORE;
+ // now copy the array
+ CompilerUtils(m_context).copyToStackTop(
+ argSize - stackPos + dynPointers + 2,
+ arrayType.getSizeOnStack()
+ );
+ // copy length to memory
+ m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack());
if (arrayType.location() == ReferenceType::Location::CallData)
m_context << eth::Instruction::DUP2; // length is on stack
else if (arrayType.location() == ReferenceType::Location::Storage)
@@ -1194,31 +1296,19 @@ void ExpressionCompiler::appendArgumentsCopyToMemory(
m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD;
}
appendTypeMoveToMemory(IntegerType(256), true);
- stackSizeOfDynamicTypes += arrayType.getSizeOnStack();
- dynamicArguments.push_back(i);
- }
- else
- appendTypeMoveToMemory(*argType, pad);
- }
+ // copy the new memory pointer
+ m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP;
+ // copy data part
+ appendTypeMoveToMemory(arrayType, true);
- // copy dynamic values to memory
- unsigned dynStackPointer = stackSizeOfDynamicTypes;
- // stack layout: <dyn arg 1> ... <dyn arg m> <memory pointer>
- for (size_t i: dynamicArguments)
- {
- auto const& arrayType = dynamic_cast<ArrayType const&>(*_arguments[i]->getType());
- CompilerUtils(m_context).copyToStackTop(1 + dynStackPointer, arrayType.getSizeOnStack());
- dynStackPointer -= arrayType.getSizeOnStack();
- appendTypeMoveToMemory(arrayType, true);
+ thisDynPointer++;
+ }
+ stackPos += _givenTypes[i]->getSizeOnStack();
}
- solAssert(dynStackPointer == 0, "");
- // remove dynamic values (and retain memory pointer)
- if (stackSizeOfDynamicTypes > 0)
- {
- m_context << eth::swapInstruction(stackSizeOfDynamicTypes);
- CompilerUtils(m_context).popStackSlots(stackSizeOfDynamicTypes);
- }
+ // remove unneeded stack elements (and retain memory pointer)
+ m_context << eth::swapInstruction(argSize + dynPointers + 1);
+ CompilerUtils(m_context).popStackSlots(argSize + dynPointers + 1);
}
void ExpressionCompiler::appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries)
diff --git a/ExpressionCompiler.h b/ExpressionCompiler.h
index 174e16d8..90994dfd 100644
--- a/ExpressionCompiler.h
+++ b/ExpressionCompiler.h
@@ -98,21 +98,28 @@ private:
void appendHighBitsCleanup(IntegerType const& _typeOnStack);
/// Appends code to call a function of the given type with the given arguments.
- void appendExternalFunctionCall(FunctionType const& _functionType, std::vector<ASTPointer<Expression const>> const& _arguments);
- /// Appends code that evaluates the given arguments and moves the result to memory encoded as
- /// specified by the ABI. The memory offset is expected to be on the stack and is updated by
- /// this call. If @a _padToWordBoundaries is set to false, all values are concatenated without
- /// padding. If @a _copyDynamicDataInPlace is set, dynamic types is stored (without length)
+ void appendExternalFunctionCall(
+ FunctionType const& _functionType,
+ std::vector<ASTPointer<Expression const>> const& _arguments
+ );
+ /// Copies values (of types @a _givenTypes) given on the stack to a location in memory given
+ /// at the stack top, encoding them according to the ABI as the given types @a _targetTypes.
+ /// Removes the values from the stack and leaves the updated memory pointer.
+ /// Stack pre: <v1> <v2> ... <vn> <memptr>
+ /// Stack post: <memptr_updated>
+ /// Does not touch the memory-free pointer.
+ /// @param _padToWordBoundaries if false, all values are concatenated without padding.
+ /// @param _copyDynamicDataInPlace if true, dynamic types is stored (without length)
/// together with fixed-length data.
- void appendArgumentsCopyToMemory(
- std::vector<ASTPointer<Expression const>> const& _arguments,
- TypePointers const& _types = {},
+ void encodeToMemory(
+ TypePointers const& _givenTypes = {},
+ TypePointers const& _targetTypes = {},
bool _padToWordBoundaries = true,
- bool _padExceptionIfFourBytes = false,
bool _copyDynamicDataInPlace = false
);
/// Appends code that moves a stack element of the given type to memory. The memory offset is
/// expected below the stack element and is updated by this call.
+ /// For arrays, this only copies the data part.
void appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries = true);
/// Appends code that evaluates a single expression and moves the result to memory. The memory offset is
/// expected to be on the stack and is updated by this call.