2014-12-08 17:52:30 +00:00
|
|
|
/*
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
/**
|
|
|
|
* @author Christian <c@ethdev.com>
|
|
|
|
* @date 2014
|
|
|
|
* Routines used by both the compiler and the expression compiler.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <libsolidity/CompilerUtils.h>
|
|
|
|
#include <libsolidity/AST.h>
|
|
|
|
#include <libevmcore/Instruction.h>
|
2015-06-15 10:10:41 +00:00
|
|
|
#include <libevmcore/Params.h>
|
|
|
|
#include <libsolidity/ArrayUtils.h>
|
2014-12-08 17:52:30 +00:00
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
namespace dev
|
|
|
|
{
|
|
|
|
namespace solidity
|
|
|
|
{
|
|
|
|
|
2015-06-05 15:32:13 +00:00
|
|
|
const unsigned CompilerUtils::dataStartOffset = 4;
|
|
|
|
const size_t CompilerUtils::freeMemoryPointer = 64;
|
2015-06-15 10:10:41 +00:00
|
|
|
const unsigned CompilerUtils::identityContractAddress = 4;
|
2015-06-05 15:32:13 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2015-01-08 23:27:26 +00:00
|
|
|
|
2015-06-05 22:57:51 +00:00
|
|
|
void CompilerUtils::toSizeAfterFreeMemoryPointer()
|
|
|
|
{
|
|
|
|
fetchFreeMemoryPointer();
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::SUB;
|
|
|
|
m_context << eth::Instruction::SWAP1;
|
|
|
|
}
|
|
|
|
|
2015-03-05 14:41:39 +00:00
|
|
|
unsigned CompilerUtils::loadFromMemory(
|
|
|
|
unsigned _offset,
|
|
|
|
Type const& _type,
|
|
|
|
bool _fromCalldata,
|
|
|
|
bool _padToWordBoundaries
|
|
|
|
)
|
2014-12-10 16:15:17 +00:00
|
|
|
{
|
2015-02-20 14:52:30 +00:00
|
|
|
solAssert(_type.getCategory() != Type::Category::Array, "Unable to statically load dynamic type.");
|
2015-02-15 00:02:38 +00:00
|
|
|
m_context << u256(_offset);
|
|
|
|
return loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries);
|
|
|
|
}
|
|
|
|
|
2015-03-03 16:55:28 +00:00
|
|
|
void CompilerUtils::loadFromMemoryDynamic(
|
2015-03-05 14:41:39 +00:00
|
|
|
Type const& _type,
|
|
|
|
bool _fromCalldata,
|
|
|
|
bool _padToWordBoundaries,
|
|
|
|
bool _keepUpdatedMemoryOffset
|
|
|
|
)
|
2015-02-15 00:02:38 +00:00
|
|
|
{
|
2015-02-20 14:52:30 +00:00
|
|
|
solAssert(_type.getCategory() != Type::Category::Array, "Arrays not yet implemented.");
|
2015-03-03 16:55:28 +00:00
|
|
|
if (_keepUpdatedMemoryOffset)
|
|
|
|
m_context << eth::Instruction::DUP1;
|
2015-02-15 00:02:38 +00:00
|
|
|
unsigned numBytes = loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries);
|
2015-03-03 16:55:28 +00:00
|
|
|
if (_keepUpdatedMemoryOffset)
|
|
|
|
{
|
|
|
|
// update memory counter
|
2015-06-15 10:10:41 +00:00
|
|
|
moveToStackTop(_type.getSizeOnStack());
|
2015-03-03 16:55:28 +00:00
|
|
|
m_context << u256(numBytes) << eth::Instruction::ADD;
|
|
|
|
}
|
2014-12-10 16:15:17 +00:00
|
|
|
}
|
|
|
|
|
2015-06-25 12:52:13 +00:00
|
|
|
void CompilerUtils::storeInMemory(unsigned _offset)
|
2014-12-10 16:15:17 +00:00
|
|
|
{
|
2015-06-25 12:52:13 +00:00
|
|
|
unsigned numBytes = prepareMemoryStore(IntegerType(256), true);
|
2015-02-10 16:53:43 +00:00
|
|
|
if (numBytes > 0)
|
|
|
|
m_context << u256(_offset) << eth::Instruction::MSTORE;
|
|
|
|
}
|
|
|
|
|
|
|
|
void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries)
|
|
|
|
{
|
2015-02-20 14:52:30 +00:00
|
|
|
if (_type.getCategory() == Type::Category::Array)
|
2014-12-11 13:19:11 +00:00
|
|
|
{
|
2015-02-20 14:52:30 +00:00
|
|
|
auto const& type = dynamic_cast<ArrayType const&>(_type);
|
|
|
|
solAssert(type.isByteArray(), "Non byte arrays not yet implemented here.");
|
2015-02-11 13:32:46 +00:00
|
|
|
|
2015-06-17 10:01:39 +00:00
|
|
|
if (type.location() == DataLocation::CallData)
|
2015-02-13 00:29:21 +00:00
|
|
|
{
|
2015-06-17 10:01:39 +00:00
|
|
|
if (!type.isDynamicallySized())
|
|
|
|
m_context << type.getLength();
|
2015-02-16 16:33:13 +00:00
|
|
|
// stack: target source_offset source_len
|
2015-06-16 09:28:35 +00:00
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5;
|
2015-06-17 10:01:39 +00:00
|
|
|
// stack: target source_offset source_len source_len source_offset target
|
2015-06-16 09:28:35 +00:00
|
|
|
m_context << eth::Instruction::CALLDATACOPY;
|
|
|
|
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD;
|
|
|
|
m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP;
|
2015-02-13 00:29:21 +00:00
|
|
|
}
|
2015-06-17 10:01:39 +00:00
|
|
|
else if (type.location() == DataLocation::Memory)
|
2015-06-15 10:10:41 +00:00
|
|
|
{
|
|
|
|
// memcpy using the built-in contract
|
|
|
|
ArrayUtils(m_context).retrieveLength(type);
|
|
|
|
if (type.isDynamicallySized())
|
|
|
|
{
|
|
|
|
// change pointer to data part
|
|
|
|
m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD;
|
|
|
|
m_context << eth::Instruction::SWAP1;
|
|
|
|
}
|
|
|
|
// stack: <target> <source> <length>
|
|
|
|
// stack for call: outsize target size source value contract gas
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4;
|
|
|
|
m_context << eth::Instruction::DUP2 << eth::Instruction::DUP5;
|
|
|
|
m_context << u256(0) << u256(identityContractAddress);
|
|
|
|
//@TODO do not use ::CALL if less than 32 bytes?
|
|
|
|
//@todo in production, we should not have to pair c_callNewAccountGas.
|
2015-06-22 18:50:29 +00:00
|
|
|
m_context << u256(eth::c_callGas + 15 + eth::c_callNewAccountGas) << eth::Instruction::GAS;
|
2015-06-15 10:10:41 +00:00
|
|
|
m_context << eth::Instruction::SUB << eth::Instruction::CALL;
|
|
|
|
m_context << eth::Instruction::POP; // ignore return value
|
|
|
|
|
|
|
|
m_context << eth::Instruction::SWAP1 << eth::Instruction::POP;
|
|
|
|
// stack: <target> <length>
|
|
|
|
|
|
|
|
if (_padToWordBoundaries && (type.isDynamicallySized() || (type.getLength()) % 32 != 0))
|
|
|
|
{
|
|
|
|
// stack: <target> <length>
|
|
|
|
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD;
|
|
|
|
// stack: <length> <target + length>
|
|
|
|
m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND;
|
|
|
|
// stack: <target + length> <remainder = length % 32>
|
|
|
|
eth::AssemblyItem skip = m_context.newTag();
|
|
|
|
if (type.isDynamicallySized())
|
|
|
|
{
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO;
|
|
|
|
m_context.appendConditionalJumpTo(skip);
|
|
|
|
}
|
|
|
|
// round off, load from there.
|
|
|
|
// stack <target + length> <remainder = length % 32>
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3;
|
|
|
|
m_context << eth::Instruction::SUB;
|
|
|
|
// stack: target+length remainder <target + length - remainder>
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD;
|
|
|
|
// Now we AND it with ~(2**(8 * (32 - remainder)) - 1)
|
|
|
|
m_context << u256(1);
|
|
|
|
m_context << eth::Instruction::DUP4 << u256(32) << eth::Instruction::SUB;
|
|
|
|
// stack: ...<v> 1 <32 - remainder>
|
|
|
|
m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB;
|
|
|
|
m_context << eth::Instruction::NOT << eth::Instruction::AND;
|
|
|
|
// stack: target+length remainder target+length-remainder <v & ...>
|
|
|
|
m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE;
|
|
|
|
// stack: target+length remainder target+length-remainder
|
|
|
|
m_context << u256(32) << eth::Instruction::ADD;
|
|
|
|
// stack: target+length remainder <new_padded_end>
|
|
|
|
m_context << eth::Instruction::SWAP2 << eth::Instruction::POP;
|
|
|
|
|
|
|
|
if (type.isDynamicallySized())
|
|
|
|
m_context << skip.tag();
|
|
|
|
// stack <target + "length"> <remainder = length % 32>
|
|
|
|
m_context << eth::Instruction::POP;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
// stack: <target> <length>
|
|
|
|
m_context << eth::Instruction::ADD;
|
|
|
|
}
|
2015-02-13 00:29:21 +00:00
|
|
|
else
|
|
|
|
{
|
2015-06-17 10:01:39 +00:00
|
|
|
solAssert(type.location() == DataLocation::Storage, "");
|
2015-03-31 12:59:38 +00:00
|
|
|
m_context << eth::Instruction::POP; // remove offset, arrays always start new slot
|
2015-02-13 00:29:21 +00:00
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
|
|
|
|
// stack here: memory_offset storage_offset length_bytes
|
|
|
|
// jump to end if length is zero
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO;
|
|
|
|
eth::AssemblyItem loopEnd = m_context.newTag();
|
|
|
|
m_context.appendConditionalJumpTo(loopEnd);
|
|
|
|
// compute memory end offset
|
|
|
|
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD << eth::Instruction::SWAP2;
|
|
|
|
// actual array data is stored at SHA3(storage_offset)
|
|
|
|
m_context << eth::Instruction::SWAP1;
|
|
|
|
CompilerUtils(m_context).computeHashStatic();
|
|
|
|
m_context << eth::Instruction::SWAP1;
|
2015-02-11 13:32:46 +00:00
|
|
|
|
2015-02-13 00:29:21 +00:00
|
|
|
// stack here: memory_end_offset storage_data_offset memory_offset
|
|
|
|
eth::AssemblyItem loopStart = m_context.newTag();
|
2015-06-16 09:28:35 +00:00
|
|
|
m_context << loopStart;
|
|
|
|
// load and store
|
|
|
|
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD;
|
|
|
|
m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE;
|
|
|
|
// increment storage_data_offset by 1
|
|
|
|
m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD;
|
|
|
|
// increment memory offset by 32
|
|
|
|
m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD;
|
|
|
|
// check for loop condition
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT;
|
2015-02-13 00:29:21 +00:00
|
|
|
m_context.appendConditionalJumpTo(loopStart);
|
2015-06-15 10:10:41 +00:00
|
|
|
// stack here: memory_end_offset storage_data_offset memory_offset
|
|
|
|
if (_padToWordBoundaries)
|
|
|
|
{
|
|
|
|
// memory_end_offset - start is the actual length (we want to compute the ceil of).
|
|
|
|
// memory_offset - start is its next multiple of 32, but it might be off by 32.
|
|
|
|
// so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31
|
|
|
|
m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1 << eth::Instruction::SUB;
|
|
|
|
m_context << u256(31) << eth::Instruction::AND;
|
|
|
|
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD;
|
|
|
|
m_context << eth::Instruction::SWAP2;
|
|
|
|
}
|
2015-02-13 00:29:21 +00:00
|
|
|
m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP;
|
|
|
|
}
|
2015-02-11 13:32:46 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries);
|
|
|
|
if (numBytes > 0)
|
|
|
|
{
|
2015-02-15 00:02:38 +00:00
|
|
|
solAssert(_type.getSizeOnStack() == 1, "Memory store of types with stack size != 1 not implemented.");
|
2015-02-11 13:32:46 +00:00
|
|
|
m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE;
|
|
|
|
m_context << u256(numBytes) << eth::Instruction::ADD;
|
|
|
|
}
|
2014-12-11 13:19:11 +00:00
|
|
|
}
|
2014-12-10 16:15:17 +00:00
|
|
|
}
|
|
|
|
|
2015-06-15 10:10:41 +00:00
|
|
|
void CompilerUtils::encodeToMemory(
|
|
|
|
TypePointers const& _givenTypes,
|
|
|
|
TypePointers const& _targetTypes,
|
|
|
|
bool _padToWordBoundaries,
|
|
|
|
bool _copyDynamicDataInPlace
|
|
|
|
)
|
|
|
|
{
|
|
|
|
// stack: <v1> <v2> ... <vn> <mem>
|
|
|
|
TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes;
|
|
|
|
solAssert(targetTypes.size() == _givenTypes.size(), "");
|
|
|
|
for (TypePointer& t: targetTypes)
|
|
|
|
t = t->mobileType()->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)
|
|
|
|
{
|
|
|
|
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
|
|
|
|
{
|
|
|
|
copyToStackTop(argSize - stackPos + dynPointers + 2, _givenTypes[i]->getSizeOnStack());
|
|
|
|
solAssert(!!targetType, "Externalable type expected.");
|
2015-06-16 15:20:41 +00:00
|
|
|
TypePointer type = targetType;
|
2015-06-17 10:01:39 +00:00
|
|
|
if (
|
|
|
|
_givenTypes[i]->dataStoredIn(DataLocation::Storage) ||
|
|
|
|
_givenTypes[i]->dataStoredIn(DataLocation::CallData)
|
|
|
|
)
|
2015-06-16 15:20:41 +00:00
|
|
|
type = _givenTypes[i]; // delay conversion
|
|
|
|
else
|
|
|
|
convertType(*_givenTypes[i], *targetType, true);
|
|
|
|
storeInMemoryDynamic(*type, _padToWordBoundaries);
|
2015-06-15 10:10:41 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.getSizeOnStack());
|
|
|
|
// stack: ... <end_of_mem> <value...>
|
|
|
|
// copy length to memory
|
|
|
|
m_context << eth::dupInstruction(1 + arrayType.getSizeOnStack());
|
2015-06-17 10:01:39 +00:00
|
|
|
if (arrayType.location() == DataLocation::CallData)
|
2015-06-15 10:10:41 +00:00
|
|
|
m_context << eth::Instruction::DUP2; // length is on stack
|
2015-06-17 10:01:39 +00:00
|
|
|
else if (arrayType.location() == DataLocation::Storage)
|
2015-06-15 10:10:41 +00:00
|
|
|
m_context << eth::Instruction::DUP3 << eth::Instruction::SLOAD;
|
|
|
|
else
|
|
|
|
{
|
2015-06-17 10:01:39 +00:00
|
|
|
solAssert(arrayType.location() == DataLocation::Memory, "");
|
2015-06-15 10:10:41 +00:00
|
|
|
m_context << eth::Instruction::DUP2 << eth::Instruction::MLOAD;
|
|
|
|
}
|
|
|
|
// stack: ... <end_of_mem> <value...> <end_of_mem'> <length>
|
|
|
|
storeInMemoryDynamic(IntegerType(256), true);
|
|
|
|
// stack: ... <end_of_mem> <value...> <end_of_mem''>
|
|
|
|
// copy the new memory pointer
|
|
|
|
m_context << eth::swapInstruction(arrayType.getSizeOnStack() + 1) << eth::Instruction::POP;
|
|
|
|
// stack: ... <end_of_mem''> <value...>
|
|
|
|
// copy data part
|
|
|
|
storeInMemoryDynamic(arrayType, true);
|
|
|
|
// stack: ... <end_of_mem'''>
|
|
|
|
|
|
|
|
thisDynPointer++;
|
|
|
|
}
|
|
|
|
stackPos += _givenTypes[i]->getSizeOnStack();
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove unneeded stack elements (and retain memory pointer)
|
|
|
|
m_context << eth::swapInstruction(argSize + dynPointers + 1);
|
|
|
|
popStackSlots(argSize + dynPointers + 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded)
|
|
|
|
{
|
|
|
|
// For a type extension, we need to remove all higher-order bits that we might have ignored in
|
|
|
|
// previous operations.
|
|
|
|
// @todo: store in the AST whether the operand might have "dirty" higher order bits
|
|
|
|
|
|
|
|
if (_typeOnStack == _targetType && !_cleanupNeeded)
|
|
|
|
return;
|
|
|
|
Type::Category stackTypeCategory = _typeOnStack.getCategory();
|
|
|
|
Type::Category targetTypeCategory = _targetType.getCategory();
|
|
|
|
|
|
|
|
switch (stackTypeCategory)
|
|
|
|
{
|
|
|
|
case Type::Category::FixedBytes:
|
|
|
|
{
|
|
|
|
FixedBytesType const& typeOnStack = dynamic_cast<FixedBytesType const&>(_typeOnStack);
|
|
|
|
if (targetTypeCategory == Type::Category::Integer)
|
|
|
|
{
|
|
|
|
// conversion from bytes to integer. no need to clean the high bit
|
|
|
|
// only to shift right because of opposite alignment
|
|
|
|
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType);
|
|
|
|
m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
|
|
|
|
if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8)
|
|
|
|
convertType(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// clear lower-order bytes for conversion to shorter bytes - we always clean
|
|
|
|
solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
|
|
|
|
FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType);
|
|
|
|
if (targetType.getNumBytes() < typeOnStack.getNumBytes())
|
|
|
|
{
|
|
|
|
if (targetType.getNumBytes() == 0)
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::XOR;
|
|
|
|
else
|
2015-06-16 09:28:35 +00:00
|
|
|
{
|
|
|
|
m_context << (u256(1) << (256 - targetType.getNumBytes() * 8));
|
|
|
|
m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2;
|
|
|
|
m_context << eth::Instruction::DIV << eth::Instruction::MUL;
|
|
|
|
}
|
2015-06-15 10:10:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Type::Category::Enum:
|
|
|
|
solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, "");
|
|
|
|
break;
|
|
|
|
case Type::Category::Integer:
|
|
|
|
case Type::Category::Contract:
|
|
|
|
case Type::Category::IntegerConstant:
|
|
|
|
if (targetTypeCategory == Type::Category::FixedBytes)
|
|
|
|
{
|
|
|
|
solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant,
|
|
|
|
"Invalid conversion to FixedBytesType requested.");
|
|
|
|
// conversion from bytes to string. no need to clean the high bit
|
|
|
|
// only to shift left because of opposite alignment
|
|
|
|
FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType);
|
|
|
|
if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
|
|
|
|
if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits())
|
|
|
|
cleanHigherOrderBits(*typeOnStack);
|
|
|
|
m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL;
|
|
|
|
}
|
|
|
|
else if (targetTypeCategory == Type::Category::Enum)
|
|
|
|
// just clean
|
|
|
|
convertType(_typeOnStack, *_typeOnStack.mobileType(), true);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, "");
|
|
|
|
IntegerType addressType(0, IntegerType::Modifier::Address);
|
|
|
|
IntegerType const& targetType = targetTypeCategory == Type::Category::Integer
|
|
|
|
? dynamic_cast<IntegerType const&>(_targetType) : addressType;
|
|
|
|
if (stackTypeCategory == Type::Category::IntegerConstant)
|
|
|
|
{
|
|
|
|
IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack);
|
|
|
|
// We know that the stack is clean, we only have to clean for a narrowing conversion
|
|
|
|
// where cleanup is forced.
|
|
|
|
if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded)
|
|
|
|
cleanHigherOrderBits(targetType);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer
|
|
|
|
? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType;
|
|
|
|
// Widening: clean up according to source type width
|
|
|
|
// Non-widening and force: clean up according to target type bits
|
|
|
|
if (targetType.getNumBits() > typeOnStack.getNumBits())
|
|
|
|
cleanHigherOrderBits(typeOnStack);
|
|
|
|
else if (_cleanupNeeded)
|
|
|
|
cleanHigherOrderBits(targetType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Type::Category::Array:
|
|
|
|
{
|
|
|
|
solAssert(targetTypeCategory == stackTypeCategory, "");
|
|
|
|
ArrayType const& typeOnStack = dynamic_cast<ArrayType const&>(_typeOnStack);
|
|
|
|
ArrayType const& targetType = dynamic_cast<ArrayType const&>(_targetType);
|
|
|
|
switch (targetType.location())
|
|
|
|
{
|
2015-06-17 10:01:39 +00:00
|
|
|
case DataLocation::Storage:
|
2015-06-15 10:10:41 +00:00
|
|
|
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
|
|
|
|
solAssert(
|
|
|
|
targetType.isPointer() &&
|
2015-06-17 10:01:39 +00:00
|
|
|
typeOnStack.location() == DataLocation::Storage,
|
2015-06-15 10:10:41 +00:00
|
|
|
"Invalid conversion to storage type."
|
|
|
|
);
|
|
|
|
break;
|
2015-06-17 10:01:39 +00:00
|
|
|
case DataLocation::Memory:
|
2015-06-15 10:10:41 +00:00
|
|
|
{
|
|
|
|
// Copy the array to a free position in memory, unless it is already in memory.
|
2015-06-17 10:01:39 +00:00
|
|
|
if (typeOnStack.location() != DataLocation::Memory)
|
2015-06-15 10:10:41 +00:00
|
|
|
{
|
|
|
|
// stack: <source ref> (variably sized)
|
|
|
|
unsigned stackSize = typeOnStack.getSizeOnStack();
|
|
|
|
fetchFreeMemoryPointer();
|
|
|
|
moveIntoStack(stackSize);
|
|
|
|
// stack: <mem start> <source ref> (variably sized)
|
|
|
|
if (targetType.isDynamicallySized())
|
|
|
|
{
|
2015-06-17 10:01:39 +00:00
|
|
|
bool fromStorage = (typeOnStack.location() == DataLocation::Storage);
|
2015-06-15 10:10:41 +00:00
|
|
|
// store length
|
|
|
|
if (fromStorage)
|
|
|
|
{
|
|
|
|
stackSize--;
|
|
|
|
// remove storage offset, as requested by ArrayUtils::retrieveLength
|
|
|
|
m_context << eth::Instruction::POP;
|
|
|
|
}
|
|
|
|
ArrayUtils(m_context).retrieveLength(typeOnStack);
|
|
|
|
// Stack: <mem start> <source ref> <length>
|
|
|
|
m_context << eth::dupInstruction(2 + stackSize) << eth::Instruction::MSTORE;
|
|
|
|
m_context << eth::dupInstruction(1 + stackSize) << u256(0x20);
|
|
|
|
m_context << eth::Instruction::ADD;
|
|
|
|
moveIntoStack(stackSize);
|
|
|
|
if (fromStorage)
|
|
|
|
{
|
|
|
|
m_context << u256(0);
|
|
|
|
stackSize++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_context << eth::dupInstruction(1 + stackSize);
|
|
|
|
moveIntoStack(stackSize);
|
|
|
|
}
|
|
|
|
// Stack: <mem start> <mem data start> <value>
|
|
|
|
// Store data part.
|
|
|
|
storeInMemoryDynamic(typeOnStack);
|
|
|
|
// Stack <mem start> <mem end>
|
|
|
|
storeFreeMemoryPointer();
|
|
|
|
}
|
2015-06-17 10:01:39 +00:00
|
|
|
else if (typeOnStack.location() == DataLocation::CallData)
|
2015-06-15 10:10:41 +00:00
|
|
|
{
|
2015-06-17 10:01:39 +00:00
|
|
|
// Stack: <offset> [<length>]
|
|
|
|
// length is present if dynamically sized
|
|
|
|
fetchFreeMemoryPointer();
|
|
|
|
moveIntoStack(typeOnStack.getSizeOnStack());
|
|
|
|
// stack: memptr calldataoffset [<length>]
|
|
|
|
if (typeOnStack.isDynamicallySized())
|
|
|
|
{
|
|
|
|
solAssert(targetType.isDynamicallySized(), "");
|
|
|
|
m_context << eth::Instruction::DUP3 << eth::Instruction::DUP2;
|
|
|
|
storeInMemoryDynamic(IntegerType(256));
|
|
|
|
moveIntoStack(typeOnStack.getSizeOnStack());
|
|
|
|
}
|
|
|
|
else
|
|
|
|
m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1;
|
|
|
|
// stack: mem_ptr mem_data_ptr calldataoffset [<length>]
|
|
|
|
storeInMemoryDynamic(typeOnStack);
|
|
|
|
storeFreeMemoryPointer();
|
2015-06-15 10:10:41 +00:00
|
|
|
}
|
|
|
|
// nothing to do for memory to memory
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
solAssert(false, "Invalid type conversion requested.");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Category::Struct:
|
|
|
|
{
|
|
|
|
//@todo we can probably use some of the code for arrays here.
|
|
|
|
solAssert(targetTypeCategory == stackTypeCategory, "");
|
|
|
|
auto& targetType = dynamic_cast<StructType const&>(_targetType);
|
|
|
|
auto& stackType = dynamic_cast<StructType const&>(_typeOnStack);
|
|
|
|
solAssert(
|
2015-06-17 10:01:39 +00:00
|
|
|
targetType.location() == DataLocation::Storage &&
|
|
|
|
stackType.location() == DataLocation::Storage,
|
2015-06-15 10:10:41 +00:00
|
|
|
"Non-storage structs not yet implemented."
|
|
|
|
);
|
|
|
|
solAssert(
|
|
|
|
targetType.isPointer(),
|
|
|
|
"Type conversion to non-pointer struct requested."
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// All other types should not be convertible to non-equal types.
|
|
|
|
solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-08 17:52:30 +00:00
|
|
|
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
|
|
|
|
{
|
|
|
|
unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable));
|
|
|
|
unsigned const size = _variable.getType()->getSizeOnStack();
|
2015-03-13 12:14:51 +00:00
|
|
|
solAssert(stackPosition >= size, "Variable size and position mismatch.");
|
2014-12-10 13:33:30 +00:00
|
|
|
// move variable starting from its top end in the stack
|
2014-12-08 21:18:19 +00:00
|
|
|
if (stackPosition - size + 1 > 16)
|
2015-06-05 23:04:55 +00:00
|
|
|
BOOST_THROW_EXCEPTION(
|
|
|
|
CompilerError() <<
|
|
|
|
errinfo_sourceLocation(_variable.getLocation()) <<
|
|
|
|
errinfo_comment("Stack too deep, try removing local variables.")
|
|
|
|
);
|
2014-12-08 17:52:30 +00:00
|
|
|
for (unsigned i = 0; i < size; ++i)
|
|
|
|
m_context << eth::swapInstruction(stackPosition - size + 1) << eth::Instruction::POP;
|
|
|
|
}
|
|
|
|
|
2015-03-03 16:55:28 +00:00
|
|
|
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
|
2014-12-10 11:51:26 +00:00
|
|
|
{
|
2015-06-05 23:04:55 +00:00
|
|
|
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables.");
|
2015-03-03 16:55:28 +00:00
|
|
|
for (unsigned i = 0; i < _itemSize; ++i)
|
2014-12-10 11:51:26 +00:00
|
|
|
m_context << eth::dupInstruction(_stackDepth);
|
|
|
|
}
|
|
|
|
|
2015-04-21 08:59:48 +00:00
|
|
|
void CompilerUtils::moveToStackTop(unsigned _stackDepth)
|
|
|
|
{
|
2015-06-05 23:04:55 +00:00
|
|
|
solAssert(_stackDepth <= 15, "Stack too deep, try removing local variables.");
|
2015-04-21 08:59:48 +00:00
|
|
|
for (unsigned i = 0; i < _stackDepth; ++i)
|
|
|
|
m_context << eth::swapInstruction(1 + i);
|
|
|
|
}
|
|
|
|
|
2015-06-15 10:10:41 +00:00
|
|
|
void CompilerUtils::moveIntoStack(unsigned _stackDepth)
|
|
|
|
{
|
|
|
|
solAssert(_stackDepth <= 16, "Stack too deep, try removing local variables.");
|
|
|
|
for (unsigned i = _stackDepth; i > 0; --i)
|
|
|
|
m_context << eth::swapInstruction(i);
|
|
|
|
}
|
|
|
|
|
2014-12-08 17:52:30 +00:00
|
|
|
void CompilerUtils::popStackElement(Type const& _type)
|
|
|
|
{
|
2015-04-01 12:22:42 +00:00
|
|
|
popStackSlots(_type.getSizeOnStack());
|
|
|
|
}
|
|
|
|
|
|
|
|
void CompilerUtils::popStackSlots(size_t _amount)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < _amount; ++i)
|
2014-12-08 17:52:30 +00:00
|
|
|
m_context << eth::Instruction::POP;
|
|
|
|
}
|
|
|
|
|
2014-12-08 21:18:19 +00:00
|
|
|
unsigned CompilerUtils::getSizeOnStack(vector<shared_ptr<Type const>> const& _variableTypes)
|
|
|
|
{
|
|
|
|
unsigned size = 0;
|
|
|
|
for (shared_ptr<Type const> const& type: _variableTypes)
|
|
|
|
size += type->getSizeOnStack();
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2015-06-25 12:52:13 +00:00
|
|
|
void CompilerUtils::computeHashStatic()
|
2015-02-11 13:24:34 +00:00
|
|
|
{
|
2015-06-25 12:52:13 +00:00
|
|
|
storeInMemory(0);
|
|
|
|
m_context << u256(32) << u256(0) << eth::Instruction::SHA3;
|
2015-02-11 13:24:34 +00:00
|
|
|
}
|
|
|
|
|
2015-02-15 00:02:38 +00:00
|
|
|
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
|
|
|
|
{
|
2015-03-06 16:20:00 +00:00
|
|
|
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);
|
2015-03-05 15:54:55 +00:00
|
|
|
bool leftAligned = _type.getCategory() == Type::Category::FixedBytes;
|
2015-02-15 00:02:38 +00:00
|
|
|
if (numBytes == 0)
|
|
|
|
m_context << eth::Instruction::POP << u256(0);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested.");
|
|
|
|
m_context << (_fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD);
|
|
|
|
if (numBytes != 32)
|
|
|
|
{
|
|
|
|
// add leading or trailing zeros by dividing/multiplying depending on alignment
|
|
|
|
u256 shiftFactor = u256(1) << ((32 - numBytes) * 8);
|
|
|
|
m_context << shiftFactor << eth::Instruction::SWAP1 << eth::Instruction::DIV;
|
|
|
|
if (leftAligned)
|
|
|
|
m_context << shiftFactor << eth::Instruction::MUL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return numBytes;
|
|
|
|
}
|
|
|
|
|
2015-06-15 10:10:41 +00:00
|
|
|
void CompilerUtils::cleanHigherOrderBits(IntegerType const& _typeOnStack)
|
|
|
|
{
|
|
|
|
if (_typeOnStack.getNumBits() == 256)
|
|
|
|
return;
|
|
|
|
else if (_typeOnStack.isSigned())
|
|
|
|
m_context << u256(_typeOnStack.getNumBits() / 8 - 1) << eth::Instruction::SIGNEXTEND;
|
|
|
|
else
|
|
|
|
m_context << ((u256(1) << _typeOnStack.getNumBits()) - 1) << eth::Instruction::AND;
|
|
|
|
}
|
|
|
|
|
2015-02-11 13:32:46 +00:00
|
|
|
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const
|
2015-02-10 16:53:43 +00:00
|
|
|
{
|
2015-03-05 17:22:17 +00:00
|
|
|
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);
|
2015-03-05 15:54:55 +00:00
|
|
|
bool leftAligned = _type.getCategory() == Type::Category::FixedBytes;
|
2015-02-10 16:53:43 +00:00
|
|
|
if (numBytes == 0)
|
|
|
|
m_context << eth::Instruction::POP;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
solAssert(numBytes <= 32, "Memory store of more than 32 bytes requested.");
|
|
|
|
if (numBytes != 32 && !leftAligned && !_padToWordBoundaries)
|
|
|
|
// shift the value accordingly before storing
|
|
|
|
m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL;
|
|
|
|
}
|
|
|
|
return numBytes;
|
|
|
|
}
|
|
|
|
|
2014-12-08 17:52:30 +00:00
|
|
|
}
|
|
|
|
}
|