/*
This file is part of solidity.
solidity 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.
solidity 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 solidity. If not, see .
*/
/**
* @author Christian
* @date 2014
* Routines used by both the compiler and the expression compiler.
*/
#include
#include
#include
#include
#include
using namespace std;
namespace dev
{
namespace solidity
{
const unsigned CompilerUtils::dataStartOffset = 4;
const size_t CompilerUtils::freeMemoryPointer = 64;
const unsigned CompilerUtils::identityContractAddress = 4;
void CompilerUtils::initialiseFreeMemoryPointer()
{
m_context << u256(freeMemoryPointer + 32);
storeFreeMemoryPointer();
}
void CompilerUtils::fetchFreeMemoryPointer()
{
m_context << u256(freeMemoryPointer) << Instruction::MLOAD;
}
void CompilerUtils::storeFreeMemoryPointer()
{
m_context << u256(freeMemoryPointer) << Instruction::MSTORE;
}
void CompilerUtils::allocateMemory()
{
fetchFreeMemoryPointer();
m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD;
storeFreeMemoryPointer();
}
void CompilerUtils::toSizeAfterFreeMemoryPointer()
{
fetchFreeMemoryPointer();
m_context << Instruction::DUP1 << Instruction::SWAP2 << Instruction::SUB;
m_context << Instruction::SWAP1;
}
unsigned CompilerUtils::loadFromMemory(
unsigned _offset,
Type const& _type,
bool _fromCalldata,
bool _padToWordBoundaries
)
{
solAssert(_type.category() != Type::Category::Array, "Unable to statically load dynamic type.");
m_context << u256(_offset);
return loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries);
}
void CompilerUtils::loadFromMemoryDynamic(
Type const& _type,
bool _fromCalldata,
bool _padToWordBoundaries,
bool _keepUpdatedMemoryOffset
)
{
if (_keepUpdatedMemoryOffset)
m_context << Instruction::DUP1;
if (auto arrayType = dynamic_cast(&_type))
{
solAssert(!arrayType->isDynamicallySized(), "");
solAssert(!_fromCalldata, "");
solAssert(_padToWordBoundaries, "");
if (_keepUpdatedMemoryOffset)
m_context << arrayType->memorySize() << Instruction::ADD;
}
else
{
unsigned numBytes = loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries);
if (_keepUpdatedMemoryOffset)
{
// update memory counter
moveToStackTop(_type.sizeOnStack());
m_context << u256(numBytes) << Instruction::ADD;
}
}
}
void CompilerUtils::storeInMemory(unsigned _offset)
{
unsigned numBytes = prepareMemoryStore(IntegerType(256), true);
if (numBytes > 0)
m_context << u256(_offset) << Instruction::MSTORE;
}
void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries)
{
if (auto ref = dynamic_cast(&_type))
{
solAssert(ref->location() == DataLocation::Memory, "");
storeInMemoryDynamic(IntegerType(256), _padToWordBoundaries);
}
else if (auto str = dynamic_cast(&_type))
{
m_context << Instruction::DUP1;
storeStringData(bytesConstRef(str->value()));
if (_padToWordBoundaries)
m_context << u256(((str->value().size() + 31) / 32) * 32);
else
m_context << u256(str->value().size());
m_context << Instruction::ADD;
}
else if (
_type.category() == Type::Category::Function &&
dynamic_cast(_type).kind() == FunctionType::Kind::External
)
{
solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented.");
combineExternalFunctionType(true);
m_context << Instruction::DUP2 << Instruction::MSTORE;
m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD;
}
else
{
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries);
if (numBytes > 0)
{
solUnimplementedAssert(
_type.sizeOnStack() == 1,
"Memory store of types with stack size != 1 not implemented."
);
m_context << Instruction::DUP2 << Instruction::MSTORE;
m_context << u256(numBytes) << Instruction::ADD;
}
}
}
void CompilerUtils::encodeToMemory(
TypePointers const& _givenTypes,
TypePointers const& _targetTypes,
bool _padToWordBoundaries,
bool _copyDynamicDataInPlace,
bool _encodeAsLibraryTypes
)
{
// stack: ...
TypePointers targetTypes = _targetTypes.empty() ? _givenTypes : _targetTypes;
solAssert(targetTypes.size() == _givenTypes.size(), "");
for (TypePointer& t: targetTypes)
{
solUnimplementedAssert(
t->mobileType() &&
t->mobileType()->interfaceType(_encodeAsLibraryTypes) &&
t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(),
"Encoding type \"" + t->toString() + "\" not yet implemented."
);
t = t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType();
}
// Stack during operation:
// ... ...
// 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 << Instruction::DUP1;
unsigned argSize = CompilerUtils::sizeOnStack(_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 << Instruction::DUP1 << u256(32) << Instruction::ADD;
dynPointers++;
solAssert((argSize + dynPointers) < 16, "Stack too deep, try using less variables.");
}
else
{
copyToStackTop(argSize - stackPos + dynPointers + 2, _givenTypes[i]->sizeOnStack());
solAssert(!!targetType, "Externalable type expected.");
TypePointer type = targetType;
if (_givenTypes[i]->dataStoredIn(DataLocation::Storage) && targetType->isValueType())
{
// special case: convert storage reference type to value type - this is only
// possible for library calls where we just forward the storage reference
solAssert(_encodeAsLibraryTypes, "");
solAssert(_givenTypes[i]->sizeOnStack() == 1, "");
}
else if (
_givenTypes[i]->dataStoredIn(DataLocation::Storage) ||
_givenTypes[i]->dataStoredIn(DataLocation::CallData) ||
_givenTypes[i]->category() == Type::Category::StringLiteral ||
_givenTypes[i]->category() == Type::Category::Function
)
type = _givenTypes[i]; // delay conversion
else
convertType(*_givenTypes[i], *targetType, true);
if (auto arrayType = dynamic_cast(type.get()))
ArrayUtils(m_context).copyArrayToMemory(*arrayType, _padToWordBoundaries);
else
storeInMemoryDynamic(*type, _padToWordBoundaries);
}
stackPos += _givenTypes[i]->sizeOnStack();
}
// now copy the dynamic part
// Stack: ... ...
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)
{
// copy tail pointer (=mem_end - mem_start) to memory
m_context << dupInstruction(2 + dynPointers) << Instruction::DUP2;
m_context << Instruction::SUB;
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
m_context << Instruction::MSTORE;
// stack: ...
if (_givenTypes[i]->category() == Type::Category::StringLiteral)
{
auto const& strType = dynamic_cast(*_givenTypes[i]);
m_context << u256(strType.value().size());
storeInMemoryDynamic(IntegerType(256), true);
// stack: ...
storeInMemoryDynamic(strType, _padToWordBoundaries);
}
else
{
solAssert(_givenTypes[i]->category() == Type::Category::Array, "Unknown dynamic type.");
auto const& arrayType = dynamic_cast(*_givenTypes[i]);
// now copy the array
copyToStackTop(argSize - stackPos + dynPointers + 2, arrayType.sizeOnStack());
// stack: ...
// copy length to memory
m_context << dupInstruction(1 + arrayType.sizeOnStack());
ArrayUtils(m_context).retrieveLength(arrayType, 1);
// stack: ...
storeInMemoryDynamic(IntegerType(256), true);
// stack: ...
// copy the new memory pointer
m_context << swapInstruction(arrayType.sizeOnStack() + 1) << Instruction::POP;
// stack: ...
// copy data part
ArrayUtils(m_context).copyArrayToMemory(arrayType, _padToWordBoundaries);
// stack: ...
}
thisDynPointer++;
}
stackPos += _givenTypes[i]->sizeOnStack();
}
// remove unneeded stack elements (and retain memory pointer)
m_context << swapInstruction(argSize + dynPointers + 1);
popStackSlots(argSize + dynPointers + 1);
}
void CompilerUtils::zeroInitialiseMemoryArray(ArrayType const& _type)
{
auto repeat = m_context.newTag();
m_context << repeat;
pushZeroValue(*_type.baseType());
storeInMemoryDynamic(*_type.baseType());
m_context << Instruction::SWAP1 << u256(1) << Instruction::SWAP1;
m_context << Instruction::SUB << Instruction::SWAP1;
m_context << Instruction::DUP2;
m_context.appendConditionalJumpTo(repeat);
m_context << Instruction::SWAP1 << Instruction::POP;
}
void CompilerUtils::memoryCopy32()
{
// Stack here: size target source
m_context.appendInlineAssembly(R"(
{
jumpi(end, eq(len, 0))
start:
mstore(dst, mload(src))
jumpi(end, iszero(gt(len, 32)))
dst := add(dst, 32)
src := add(src, 32)
len := sub(len, 32)
jump(start)
end:
}
)",
{ "len", "dst", "src" }
);
m_context << Instruction::POP << Instruction::POP << Instruction::POP;
}
void CompilerUtils::memoryCopy()
{
// Stack here: size target source
m_context.appendInlineAssembly(R"(
{
// copy 32 bytes at once
start32:
jumpi(end32, lt(len, 32))
mstore(dst, mload(src))
dst := add(dst, 32)
src := add(src, 32)
len := sub(len, 32)
jump(start32)
end32:
// copy the remainder (0 < len < 32)
let mask := sub(exp(256, sub(32, len)), 1)
let srcpart := and(mload(src), not(mask))
let dstpart := and(mload(dst), mask)
mstore(dst, or(srcpart, dstpart))
}
)",
{ "len", "dst", "src" }
);
m_context << Instruction::POP << Instruction::POP << Instruction::POP;
}
void CompilerUtils::splitExternalFunctionType(bool _leftAligned)
{
// We have to split the left-aligned into two stack slots:
// address (right aligned), function identifier (right aligned)
if (_leftAligned)
{
m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV;
//
m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV;
}
else
{
m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV;
m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1;
}
m_context << u256(0xffffffffUL) << Instruction::AND;
}
void CompilerUtils::combineExternalFunctionType(bool _leftAligned)
{
//
m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1;
if (!_leftAligned)
m_context << ((u256(1) << 160) - 1) << Instruction::AND;
m_context << (u256(1) << 32) << Instruction::MUL;
m_context << Instruction::OR;
if (_leftAligned)
m_context << (u256(1) << 64) << Instruction::MUL;
}
void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function)
{
m_context << m_context.functionEntryLabel(_function).pushTag();
// If there is a runtime context, we have to merge both labels into the same
// stack slot in case we store it in storage.
if (CompilerContext* rtc = m_context.runtimeContext())
m_context <<
(u256(1) << 32) <<
Instruction::MUL <<
rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) <<
Instruction::OR;
}
void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded, bool _chopSignBits)
{
// 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.category();
Type::Category targetTypeCategory = _targetType.category();
bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum || stackTypeCategory == Type::Category::Enum);
bool chopSignBitsPending = _chopSignBits && targetTypeCategory == Type::Category::Integer;
if (chopSignBitsPending)
{
const IntegerType& targetIntegerType = dynamic_cast(_targetType);
chopSignBitsPending = targetIntegerType.isSigned();
}
switch (stackTypeCategory)
{
case Type::Category::FixedBytes:
{
FixedBytesType const& typeOnStack = dynamic_cast(_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(_targetType);
m_context << (u256(1) << (256 - typeOnStack.numBytes() * 8)) << Instruction::SWAP1 << Instruction::DIV;
if (targetIntegerType.numBits() < typeOnStack.numBytes() * 8)
convertType(IntegerType(typeOnStack.numBytes() * 8), _targetType, _cleanupNeeded);
}
else
{
// clear for conversion to longer bytes
solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
FixedBytesType const& targetType = dynamic_cast(_targetType);
if (targetType.numBytes() > typeOnStack.numBytes() || _cleanupNeeded)
{
if (typeOnStack.numBytes() == 0)
m_context << Instruction::POP << u256(0);
else
{
m_context << ((u256(1) << (256 - typeOnStack.numBytes() * 8)) - 1);
m_context << Instruction::NOT << Instruction::AND;
}
}
}
}
break;
case Type::Category::Enum:
solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, "");
if (enumOverflowCheckPending)
{
EnumType const& enumType = dynamic_cast(_typeOnStack);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
break;
case Type::Category::FixedPoint:
solUnimplemented("Not yet implemented - FixedPointType.");
case Type::Category::Integer:
case Type::Category::Contract:
case Type::Category::RationalNumber:
if (targetTypeCategory == Type::Category::FixedBytes)
{
solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::RationalNumber,
"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(_targetType);
if (auto typeOnStack = dynamic_cast(&_typeOnStack))
if (targetBytesType.numBytes() * 8 > typeOnStack->numBits())
cleanHigherOrderBits(*typeOnStack);
m_context << (u256(1) << (256 - targetBytesType.numBytes() * 8)) << Instruction::MUL;
}
else if (targetTypeCategory == Type::Category::Enum)
{
solAssert(_typeOnStack.mobileType(), "");
// just clean
convertType(_typeOnStack, *_typeOnStack.mobileType(), true);
EnumType const& enumType = dynamic_cast(_targetType);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
else if (targetTypeCategory == Type::Category::FixedPoint)
{
solAssert(
stackTypeCategory == Type::Category::Integer ||
stackTypeCategory == Type::Category::RationalNumber ||
stackTypeCategory == Type::Category::FixedPoint,
"Invalid conversion to FixedMxNType requested."
);
//shift all integer bits onto the left side of the fixed type
FixedPointType const& targetFixedPointType = dynamic_cast(_targetType);
if (auto typeOnStack = dynamic_cast(&_typeOnStack))
if (targetFixedPointType.integerBits() > typeOnStack->numBits())
cleanHigherOrderBits(*typeOnStack);
solUnimplemented("Not yet implemented - FixedPointType.");
}
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(_targetType) : addressType;
if (stackTypeCategory == Type::Category::RationalNumber)
{
RationalNumberType const& constType = dynamic_cast(_typeOnStack);
// We know that the stack is clean, we only have to clean for a narrowing conversion
// where cleanup is forced.
solUnimplementedAssert(!constType.isFractional(), "Not yet implemented - FixedPointType.");
if (targetType.numBits() < constType.integerType()->numBits() && _cleanupNeeded)
cleanHigherOrderBits(targetType);
}
else
{
IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer
? dynamic_cast(_typeOnStack) : addressType;
// Widening: clean up according to source type width
// Non-widening and force: clean up according to target type bits
if (targetType.numBits() > typeOnStack.numBits())
cleanHigherOrderBits(typeOnStack);
else if (_cleanupNeeded)
cleanHigherOrderBits(targetType);
if (chopSignBitsPending)
{
if (typeOnStack.numBits() < 256)
m_context
<< ((u256(1) << typeOnStack.numBits()) - 1)
<< Instruction::AND;
chopSignBitsPending = false;
}
}
}
break;
case Type::Category::StringLiteral:
{
auto const& literalType = dynamic_cast(_typeOnStack);
string const& value = literalType.value();
bytesConstRef data(value);
if (targetTypeCategory == Type::Category::FixedBytes)
{
solAssert(data.size() <= 32, "");
m_context << h256::Arith(h256(data, h256::AlignLeft));
}
else if (targetTypeCategory == Type::Category::Array)
{
auto const& arrayType = dynamic_cast(_targetType);
solAssert(arrayType.isByteArray(), "");
u256 storageSize(32 + ((data.size() + 31) / 32) * 32);
m_context << storageSize;
allocateMemory();
// stack: mempos
m_context << Instruction::DUP1 << u256(data.size());
storeInMemoryDynamic(IntegerType(256));
// stack: mempos datapos
storeStringData(data);
break;
}
else
solAssert(
false,
"Invalid conversion from string literal to " + _targetType.toString(false) + " requested."
);
break;
}
case Type::Category::Array:
{
solAssert(targetTypeCategory == stackTypeCategory, "");
ArrayType const& typeOnStack = dynamic_cast(_typeOnStack);
ArrayType const& targetType = dynamic_cast(_targetType);
switch (targetType.location())
{
case DataLocation::Storage:
// Other cases are done explicitly in LValue::storeValue, and only possible by assignment.
solAssert(
(targetType.isPointer() || (typeOnStack.isByteArray() && targetType.isByteArray())) &&
typeOnStack.location() == DataLocation::Storage,
"Invalid conversion to storage type."
);
break;
case DataLocation::Memory:
{
// Copy the array to a free position in memory, unless it is already in memory.
if (typeOnStack.location() != DataLocation::Memory)
{
// stack: