mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Add InlineArrayType to support literals conversion to statically and dynamically allocated arrays.
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@@ -286,6 +287,16 @@ string ABIFunctions::abiEncodingFunction(
|
||||
|
||||
if (_from.category() == Type::Category::StringLiteral)
|
||||
return abiEncodingFunctionStringLiteral(_from, to, _options);
|
||||
else if (_from.category() == Type::Category::InlineArray)
|
||||
{
|
||||
solAssert(_to.category() == Type::Category::Array);
|
||||
return abiEncodingFunctionInlineArray(
|
||||
dynamic_cast<InlineArrayType const&>(_from),
|
||||
dynamic_cast<ArrayType const&>(_to),
|
||||
_options
|
||||
);
|
||||
|
||||
}
|
||||
else if (auto toArray = dynamic_cast<ArrayType const*>(&to))
|
||||
{
|
||||
ArrayType const* fromArray = nullptr;
|
||||
@@ -632,6 +643,87 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunctionInlineArray(
|
||||
InlineArrayType const& _from,
|
||||
ArrayType const& _to,
|
||||
EncodingOptions const& _options
|
||||
)
|
||||
{
|
||||
string functionName =
|
||||
"abi_encode_" +
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier() +
|
||||
_options.toFunctionNameSuffix();
|
||||
|
||||
return createFunction(functionName, [&]() {
|
||||
bool dynamic = _to.isDynamicallyEncoded();
|
||||
bool dynamicBase = _to.baseType()->isDynamicallyEncoded();
|
||||
bool const usesTail = dynamicBase && !_options.dynamicInplace;
|
||||
EncodingOptions subOptions(_options);
|
||||
subOptions.encodeFunctionFromStack = true;
|
||||
subOptions.padded = true;
|
||||
|
||||
vector<map<string, string>> memberSetValues;
|
||||
unsigned stackItemIndex = 0;
|
||||
|
||||
for (auto const& type: _from.components())
|
||||
{
|
||||
memberSetValues.emplace_back();
|
||||
|
||||
memberSetValues.back()["setMember"] = Whiskers(R"(
|
||||
<?usesTail>
|
||||
mstore(pos, sub(tail, headStart))
|
||||
tail := <encodeToMemoryFun>(<value>, tail)
|
||||
pos := add(pos, 0x20)
|
||||
<!usesTail>
|
||||
pos := <encodeToMemoryFun>(<value>, pos)
|
||||
</usesTail>
|
||||
)")
|
||||
("value", suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack()))
|
||||
("usesTail", usesTail)
|
||||
("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*type, *_to.baseType(), subOptions))
|
||||
.render();
|
||||
|
||||
stackItemIndex += type->sizeOnStack();
|
||||
}
|
||||
|
||||
return Whiskers(R"(
|
||||
// <readableTypeNameFrom> -> <readableTypeNameTo>
|
||||
function <functionName>(<values>, pos) <return> {
|
||||
let length := <length>
|
||||
pos := <storeLength>(pos, length)
|
||||
|
||||
<?usesTail>
|
||||
let headStart := pos
|
||||
let tail := add(pos, mul(length, 0x20))
|
||||
<#member>
|
||||
<setMember>
|
||||
</member>
|
||||
pos := tail
|
||||
<!usesTail>
|
||||
<#member>
|
||||
<setMember>
|
||||
</member>
|
||||
</usesTail>
|
||||
|
||||
<assignEnd>
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("member", std::move(memberSetValues))
|
||||
("length", to_string(_from.components().size()))
|
||||
("readableTypeNameFrom", _from.toString(true))
|
||||
("readableTypeNameTo", _to.toString(true))
|
||||
("return", dynamic ? " -> end " : "")
|
||||
("assignEnd", dynamic ? "end := pos" : "")
|
||||
("storeLength", arrayStoreLengthForEncodingFunction(_to, _options))
|
||||
("usesTail", usesTail)
|
||||
("values", suffixedVariableNameList("var_", 0, _from.sizeOnStack()))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunctionMemoryByteArray(
|
||||
ArrayType const& _from,
|
||||
ArrayType const& _to,
|
||||
|
||||
@@ -190,6 +190,11 @@ private:
|
||||
ArrayType const& _targetType,
|
||||
EncodingOptions const& _options
|
||||
);
|
||||
std::string abiEncodingFunctionInlineArray(
|
||||
InlineArrayType const& _givenType,
|
||||
ArrayType const& _targetType,
|
||||
EncodingOptions const& _options
|
||||
);
|
||||
std::string abiEncodingFunctionMemoryByteArray(
|
||||
ArrayType const& _givenType,
|
||||
ArrayType const& _targetType,
|
||||
|
||||
@@ -36,6 +36,9 @@
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::evmasm;
|
||||
@@ -268,20 +271,13 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
<< swapInstruction(1 + byteOffsetSize);
|
||||
_context.appendJumpTo(copyLoopStart);
|
||||
_context << copyLoopEnd;
|
||||
|
||||
if (haveByteOffsetTarget)
|
||||
{
|
||||
// clear elements that might be left over in the current slot in target
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
|
||||
_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
|
||||
evmasm::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump();
|
||||
_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
|
||||
StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true);
|
||||
utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
|
||||
_context.appendJumpTo(copyLoopEnd);
|
||||
|
||||
_context << copyCleanupLoopEnd;
|
||||
utils.clearLeftoversInSlot(*targetBaseType, byteOffsetSize, 2 + byteOffsetSize);
|
||||
_context << Instruction::POP; // might pop the source, but then target is popped next
|
||||
}
|
||||
|
||||
if (haveByteOffsetSource)
|
||||
_context << Instruction::POP;
|
||||
_context << copyLoopEndWithoutByteOffset;
|
||||
@@ -298,6 +294,188 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
|
||||
}
|
||||
);
|
||||
}
|
||||
void ArrayUtils::clearLeftoversInSlot(Type const& _type, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const
|
||||
{
|
||||
// clear elements that might be left over in the current slot in target
|
||||
// stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
|
||||
|
||||
auto cleanupLoopStart = m_context.newTag();
|
||||
m_context << cleanupLoopStart;
|
||||
|
||||
m_context << dupInstruction(_byteOffsetPosition) << Instruction::ISZERO;
|
||||
evmasm::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
|
||||
m_context << dupInstruction(_storageOffsetPosition) << dupInstruction(_byteOffsetPosition + 1);
|
||||
StorageItem(m_context, _type).setToZero(SourceLocation(), true);
|
||||
incrementByteOffset(_type.storageBytes(), _byteOffsetPosition, _storageOffsetPosition);
|
||||
m_context.appendJumpTo(cleanupLoopStart);
|
||||
|
||||
m_context << copyCleanupLoopEnd;
|
||||
}
|
||||
|
||||
void ArrayUtils::moveInlineArrayToStorage(
|
||||
ArrayType const& _targetType,
|
||||
InlineArrayType const& _sourceType,
|
||||
unsigned _sourcePosition) const
|
||||
{
|
||||
// stack: source... ... target_ref
|
||||
solAssert(!_targetType.containsNestedMapping());
|
||||
solAssert(!_targetType.isByteArrayOrString());
|
||||
solAssert(_sourceType.components().size() > 0);
|
||||
|
||||
Type const* arrayBaseType = _targetType.baseType();
|
||||
bool const hasByteOffset = arrayBaseType->storageBytes() <= 16;
|
||||
|
||||
if (_targetType.isDynamicallySized())
|
||||
{
|
||||
m_context << u256(_sourceType.components().size());
|
||||
// stack: source... ... target_ref source_length
|
||||
m_context << Instruction::DUP2 << Instruction::SSTORE;
|
||||
// stack: source... ... target_ref
|
||||
m_context << Instruction::DUP1;
|
||||
CompilerUtils(m_context).computeHashStatic();
|
||||
++_sourcePosition;
|
||||
// stack: source... ... target_ref target_data_pos
|
||||
}
|
||||
|
||||
for (auto&& [index, sourceComponentType]:
|
||||
_sourceType.components() | ranges::views::enumerate | ranges::views::reverse)
|
||||
{
|
||||
solAssert(arrayBaseType->nameable(), "");
|
||||
|
||||
if (ArrayType const* targetType = dynamic_cast<ArrayType const*>(_targetType.baseType()))
|
||||
{
|
||||
// stack: source... ... target_ref target_data_pos
|
||||
m_context
|
||||
<< Instruction::DUP1
|
||||
<< u256(targetType->storageSize() * index) << Instruction::ADD;
|
||||
// stack: source... ... target_ref target_data_pos component_data_ref
|
||||
|
||||
if (StringLiteralType const* stringLiteralType = dynamic_cast<StringLiteralType const*>(sourceComponentType))
|
||||
copyLiteralToStorage(*stringLiteralType);
|
||||
else
|
||||
{
|
||||
InlineArrayType const* sourceType = dynamic_cast<InlineArrayType const*>(sourceComponentType);
|
||||
solAssert(sourceType);
|
||||
moveInlineArrayToStorage(*targetType, *sourceType, _sourcePosition + 1);
|
||||
// stack: source... ... target_ref target_data_pos component_data_ref
|
||||
}
|
||||
|
||||
m_context << Instruction::POP;
|
||||
// stack: source... ... target_ref target_data_pos
|
||||
}
|
||||
else
|
||||
{
|
||||
// stack: source... ... target_ref target_data_pos
|
||||
CompilerUtils(m_context).moveToStackTop(
|
||||
_sourcePosition,
|
||||
sourceComponentType->sizeOnStack()
|
||||
);
|
||||
|
||||
// stack: source... ... target_ref target_data_pos value...
|
||||
m_context << dupInstruction(1 + sourceComponentType->sizeOnStack());
|
||||
|
||||
Type const* stackType = sourceComponentType;
|
||||
if (RationalNumberType const* rType = dynamic_cast<RationalNumberType const*>(sourceComponentType))
|
||||
{
|
||||
solUnimplementedAssert(!rType->isFractional(), "Not yet implemented - FixedPointType.");
|
||||
stackType = rType->integerType();
|
||||
CompilerUtils(m_context).convertType(*sourceComponentType, *stackType);
|
||||
}
|
||||
|
||||
// stack: source... ... target_ref target_data_pos value... target_data_pos
|
||||
computeStoragePosition(static_cast<unsigned>(index), arrayBaseType->storageBytes());
|
||||
// stack: source... ... target_ref target_data_pos value... target_data_pos offset
|
||||
|
||||
if (index == _sourceType.components().size() && hasByteOffset)
|
||||
{
|
||||
m_context << Instruction::DUP2 << Instruction::DUP2;
|
||||
clearLeftoversInSlot(*arrayBaseType, 1, 2);
|
||||
m_context << Instruction::POP << Instruction::POP;
|
||||
}
|
||||
|
||||
StorageItem(m_context, *arrayBaseType)
|
||||
.storeValue(*stackType, SourceLocation(), true);
|
||||
// stack: source... ... target_ref target_data_pos
|
||||
}
|
||||
}
|
||||
|
||||
// stack: ... target_ref target_data_pos
|
||||
if (_targetType.isDynamicallySized())
|
||||
m_context << Instruction::POP;
|
||||
// stack: ... target_ref
|
||||
}
|
||||
|
||||
void ArrayUtils::copyLiteralToStorage(StringLiteralType const& _sourceType) const
|
||||
{
|
||||
bytesConstRef data(_sourceType.value());
|
||||
// stack: target_ref
|
||||
if (data.empty())
|
||||
m_context << u256(0) << Instruction::DUP2 << Instruction::SSTORE;
|
||||
else if (data.size() < 32)
|
||||
{
|
||||
// stack: target_ref
|
||||
m_context
|
||||
<< u256(util::h256(data, util::h256::AlignLeft))
|
||||
<< u256(2)
|
||||
<< u256(data.size())
|
||||
<< Instruction::MUL
|
||||
<< Instruction::ADD;
|
||||
// stack: target_ref value
|
||||
m_context << Instruction::DUP2 << Instruction::SSTORE;
|
||||
// stack: target_ref
|
||||
}
|
||||
else
|
||||
{
|
||||
// stack: target_ref
|
||||
m_context << Instruction::DUP1;
|
||||
// stack: target_ref target_ref
|
||||
m_context
|
||||
<< u256(1) << u256(2) << u256(data.size())
|
||||
<< Instruction::MUL << Instruction::ADD;
|
||||
// stack: target_ref target_ref 2*length+1
|
||||
m_context << Instruction::DUP2 << Instruction::SSTORE;
|
||||
// stack: target_ref target_ref
|
||||
CompilerUtils(m_context).computeHashStatic();
|
||||
// stack: target_ref target_data_pos
|
||||
|
||||
for (size_t index = 0; index <= data.size() / 32; ++index)
|
||||
{
|
||||
// stack: target_ref target_data_pos
|
||||
size_t const chunk = min<size_t>(32, data.size() - index * 32);
|
||||
m_context << u256(util::h256(data.cropped(index * 32, chunk), util::h256::AlignLeft));
|
||||
// stack: target_ref target_data_pos value
|
||||
m_context << Instruction::DUP2 << Instruction::SSTORE;
|
||||
// stack: target_ref target_data_pos
|
||||
m_context << u256(1) << Instruction::ADD;
|
||||
// stack: target_ref target_data_pos+1
|
||||
}
|
||||
m_context << Instruction::POP;
|
||||
// stack: target_ref
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayUtils::computeStoragePosition(unsigned _index, unsigned _byteSize) const
|
||||
{
|
||||
// We do the following calculation:
|
||||
// slot = element_index * element_byte_size / 32
|
||||
// offset = element_index * element_byte_size % 32
|
||||
|
||||
// stack: slot
|
||||
m_context << u256(_byteSize) << u256(_index) << Instruction::MUL;
|
||||
// stack: slot byte_pos
|
||||
|
||||
m_context << Instruction::SWAP1;
|
||||
// stack: byte_pos slot
|
||||
|
||||
m_context << u256(32) << Instruction::DUP3 << Instruction::DIV;
|
||||
// stack: byte_pos slot slot_offset
|
||||
|
||||
m_context << Instruction::ADD << Instruction::SWAP1;
|
||||
// stack: target_slot byte_pos
|
||||
|
||||
m_context << u256(32) << Instruction::SWAP1 << Instruction::MOD;
|
||||
// stack: target_slot offset
|
||||
}
|
||||
|
||||
void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const
|
||||
{
|
||||
@@ -535,6 +713,52 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayUtils::moveInlineArrayToMemory(
|
||||
InlineArrayType const& _sourceType,
|
||||
ArrayType const& _targetType,
|
||||
unsigned _sourcePosition,
|
||||
bool _padToWordBoundaries) const
|
||||
{
|
||||
auto const& components = _sourceType.components();
|
||||
u256 const memoryStride =
|
||||
_padToWordBoundaries ?
|
||||
(_targetType.memoryStride() + 31) / 32 * 32 :
|
||||
_targetType.memoryStride();
|
||||
|
||||
// value... ... target_pos
|
||||
m_context << u256(components.size() * memoryStride) << Instruction::ADD;
|
||||
// value... ... target_pos_end
|
||||
m_context << Instruction::DUP1;
|
||||
// value... ... target_pos_end target_pos_end
|
||||
|
||||
CompilerUtils utils(m_context);
|
||||
for (Type const* component: components | ranges::views::reverse)
|
||||
{
|
||||
solAssert(
|
||||
component->category() != Type::Category::InlineArray &&
|
||||
component->category() != Type::Category::Array &&
|
||||
component->category() != Type::Category::ArraySlice);
|
||||
|
||||
// values... ... target_pos_end component_pos_end
|
||||
m_context << memoryStride << Instruction::SWAP1 << Instruction::SUB;
|
||||
// values... ... target_pos_end component_pos
|
||||
m_context << Instruction::DUP1;
|
||||
// values... ... target_pos_end component_pos component_pos
|
||||
utils.moveToStackTop(_sourcePosition + 2, component->sizeOnStack());
|
||||
// values... ... target_pos_end component_pos component_pos value
|
||||
utils.convertType(*component, *_targetType.baseType());
|
||||
// values... ... target_pos_end component_pos component_pos converted_value
|
||||
utils.storeInMemoryDynamic(*_targetType.baseType());
|
||||
// values... ... target_pos_end component_pos component_pos_end
|
||||
m_context << Instruction::POP;
|
||||
// values... ... target_pos_end component_pos
|
||||
}
|
||||
|
||||
// ... target_pos_end component_pos
|
||||
m_context << Instruction::POP;
|
||||
// ... target_pos_end
|
||||
}
|
||||
|
||||
void ArrayUtils::clearArray(ArrayType const& _typeIn) const
|
||||
{
|
||||
Type const* type = &_typeIn;
|
||||
|
||||
@@ -28,9 +28,11 @@
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
class CompilerContext;
|
||||
class Type;
|
||||
class ArrayType;
|
||||
class CompilerContext;
|
||||
class InlineArrayType;
|
||||
class StringLiteralType;
|
||||
class Type;
|
||||
|
||||
/**
|
||||
* Class that provides code generation for handling arrays.
|
||||
@@ -45,6 +47,16 @@ public:
|
||||
/// Stack pre: source_reference [source_length] target_reference
|
||||
/// Stack post: target_reference
|
||||
void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const;
|
||||
|
||||
/// Moves an inline array from the stack to the storage.
|
||||
/// @param sourcePosition the stack offset of the source
|
||||
/// Stack pre: source ... target_reference
|
||||
/// Stack post: ... target_reference
|
||||
void moveInlineArrayToStorage(
|
||||
ArrayType const& _targetType,
|
||||
InlineArrayType const& _sourceType,
|
||||
unsigned _sourcePosition = 1) const;
|
||||
|
||||
/// Copies the data part of an array (which cannot be dynamically nested) from anywhere
|
||||
/// to a given position in memory.
|
||||
/// This always copies contained data as is (i.e. structs and fixed-size arrays are copied in
|
||||
@@ -53,6 +65,17 @@ public:
|
||||
/// Stack pre: memory_offset source_item
|
||||
/// Stack post: memory_offest + length(padded)
|
||||
void copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries = true) const;
|
||||
|
||||
/// Moves inline array from the stack to a given position in memory.
|
||||
/// @param sourcePosition the stack offset of the source
|
||||
/// Stack pre: source ... target_reference
|
||||
/// Stack post: ... target_reference + length(padded)
|
||||
void moveInlineArrayToMemory(
|
||||
InlineArrayType const& _sourceType,
|
||||
ArrayType const& _targetType,
|
||||
unsigned _sourcePosition,
|
||||
bool _padToWordBoundaries = true) const;
|
||||
|
||||
/// Clears the given dynamic or static array.
|
||||
/// Stack pre: storage_ref storage_byte_offset
|
||||
/// Stack post:
|
||||
@@ -114,6 +137,29 @@ private:
|
||||
/// @param storageOffsetPosition the stack offset of the storage slot offset
|
||||
void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const;
|
||||
|
||||
/// Copy a string literal to the storage.
|
||||
/// @param sourcePosition the stack offset of the source
|
||||
/// Stack pre: target_reference
|
||||
/// Stack post: target_reference
|
||||
void copyLiteralToStorage(StringLiteralType const& _sourceType) const;
|
||||
|
||||
/// Appends code that computes a storage position of the array element.
|
||||
/// @param index array element index
|
||||
/// @param byteSize array element size in bytes
|
||||
/// Stack pre: slot
|
||||
/// Stack post: target_slot byte_offset
|
||||
void computeStoragePosition(unsigned _index, unsigned _byteSize) const;
|
||||
|
||||
/// Appends code that set to zero all elements in slot starting at offset.
|
||||
/// Slot and offset are updated to show next slot.
|
||||
/// @param type element type
|
||||
/// @param byteOffsetPosition the stack offset of the storage byte offset
|
||||
/// @param storageOffsetPosition the stack offset of the storage slot offset
|
||||
/// Stack pre: ... slot offset ...
|
||||
/// Stack post: ... slot offset ...
|
||||
void clearLeftoversInSlot(Type const& _type, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const;
|
||||
|
||||
|
||||
CompilerContext& m_context;
|
||||
};
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <libevmasm/Instruction.h>
|
||||
#include <libsolutil/Whiskers.h>
|
||||
#include <libsolutil/StackTooDeepString.h>
|
||||
#include <range/v3/view/reverse.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
@@ -462,6 +463,7 @@ void CompilerUtils::encodeToMemory(
|
||||
// store memory start pointer
|
||||
m_context << Instruction::DUP1;
|
||||
|
||||
ArrayUtils utils(m_context);
|
||||
unsigned argSize = CompilerUtils::sizeOnStack(_givenTypes);
|
||||
unsigned stackPos = 0; // advances through the argument values
|
||||
unsigned dynPointers = 0; // number of dynamic head pointers on the stack
|
||||
@@ -479,6 +481,14 @@ void CompilerUtils::encodeToMemory(
|
||||
StackTooDeepError,
|
||||
util::stackTooDeepString
|
||||
);
|
||||
stackPos += _givenTypes[i]->sizeOnStack();
|
||||
}
|
||||
else if (InlineArrayType const* inlineArrayType = dynamic_cast<InlineArrayType const*>(_givenTypes[i]))
|
||||
{
|
||||
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(_targetTypes[i]);
|
||||
unsigned const sourceStackPosition = argSize - stackPos + dynPointers - inlineArrayType->sizeOnStack() + 2;
|
||||
utils.moveInlineArrayToMemory(*inlineArrayType, *arrayType, sourceStackPosition, _padToWordBoundaries);
|
||||
argSize -= inlineArrayType->sizeOnStack();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -520,8 +530,10 @@ void CompilerUtils::encodeToMemory(
|
||||
}
|
||||
else
|
||||
storeInMemoryDynamic(*type, _padToWordBoundaries, needCleanup);
|
||||
stackPos += _givenTypes[i]->sizeOnStack();
|
||||
}
|
||||
stackPos += _givenTypes[i]->sizeOnStack();
|
||||
|
||||
|
||||
}
|
||||
|
||||
// now copy the dynamic part
|
||||
@@ -545,7 +557,18 @@ void CompilerUtils::encodeToMemory(
|
||||
m_context << dupInstruction(2 + dynPointers - thisDynPointer);
|
||||
m_context << Instruction::MSTORE;
|
||||
// stack: ... <end_of_mem>
|
||||
if (_givenTypes[i]->category() == Type::Category::StringLiteral)
|
||||
if (InlineArrayType const* inlineArrayType = dynamic_cast<InlineArrayType const*>(_givenTypes[i]))
|
||||
{
|
||||
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(_targetTypes[i]);
|
||||
|
||||
m_context << u256(inlineArrayType->components().size());
|
||||
storeInMemoryDynamic(*TypeProvider::uint256(), true);
|
||||
|
||||
unsigned const sourceStackPosition = argSize - stackPos + dynPointers - inlineArrayType->sizeOnStack() + 2;
|
||||
utils.moveInlineArrayToMemory(*inlineArrayType, *arrayType, sourceStackPosition, _padToWordBoundaries);
|
||||
argSize -= inlineArrayType->sizeOnStack();
|
||||
}
|
||||
else if (_givenTypes[i]->category() == Type::Category::StringLiteral)
|
||||
{
|
||||
auto const& strType = dynamic_cast<StringLiteralType const&>(*_givenTypes[i]);
|
||||
auto const size = strType.value().size();
|
||||
@@ -1114,6 +1137,62 @@ void CompilerUtils::convertType(
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::InlineArray:
|
||||
{
|
||||
InlineArrayType const& inlineArray = dynamic_cast<InlineArrayType const&>(_typeOnStack);
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(_targetType);
|
||||
|
||||
solAssert(arrayType.location() == DataLocation::Memory);
|
||||
|
||||
auto const& components = inlineArray.components();
|
||||
m_context << u256(components.size());
|
||||
// stack: <source ref> <length>
|
||||
ArrayUtils(m_context).convertLengthToSize(arrayType, true);
|
||||
|
||||
// stack: <source ref> <size>
|
||||
if (arrayType.isDynamicallySized())
|
||||
m_context << u256(0x20) << Instruction::ADD;
|
||||
// <size> = <size> + 0x20
|
||||
allocateMemory();
|
||||
|
||||
// stack: <source ref> <mem start>
|
||||
m_context << Instruction::DUP1;
|
||||
// stack: <source ref> <mem start> <mem start>
|
||||
if (arrayType.isDynamicallySized())
|
||||
{
|
||||
m_context << u256(components.size());
|
||||
// stack: <source ref> <mem start> <mem start> <length>
|
||||
storeInMemoryDynamic(*TypeProvider::uint256());
|
||||
// memory[<mem start>] = <length>
|
||||
// stack: <source ref> <mem start> <mem data pos>
|
||||
}
|
||||
|
||||
// stack: <source ref> <mem start> <mem data pos>
|
||||
m_context << u256(components.size() * arrayType.baseType()->memoryHeadSize()) << Instruction::ADD;
|
||||
// stack: <source ref> <mem start> <mem data end>
|
||||
for (Type const* component: components | ranges::views::reverse)
|
||||
{
|
||||
// stack: <source ref> <mem start> <component end>
|
||||
m_context << u256(arrayType.memoryStride()) << Instruction::SWAP1 << Instruction::SUB;
|
||||
// stack: <source ref> <mem start> <component pos>
|
||||
m_context << Instruction::DUP1;
|
||||
// stack: <source ref> <mem start> <component pos> <component pos>
|
||||
|
||||
unsigned const componentSize = component->sizeOnStack();
|
||||
moveToStackTop(3, componentSize);
|
||||
// stack: <source ref> <mem start> <component pos> <component pos> <value>
|
||||
convertType(*component, *arrayType.baseType());
|
||||
// stack: <source ref> <mem start> <component pos> <component pos> <converted value>
|
||||
storeInMemoryDynamic(*arrayType.baseType());
|
||||
// stack: <source ref> <mem start> <component pos> <component end>
|
||||
m_context << Instruction::POP;
|
||||
// stack: <source ref> <mem start> <component pos>
|
||||
}
|
||||
// stack: <mem start> <mem data pos>
|
||||
m_context << Instruction::POP;
|
||||
// stack: <mem start>
|
||||
break;
|
||||
}
|
||||
case Type::Category::ArraySlice:
|
||||
{
|
||||
auto& typeOnStack = dynamic_cast<ArraySliceType const&>(_typeOnStack);
|
||||
@@ -1287,6 +1366,7 @@ void CompilerUtils::convertType(
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Type::Category::Bool:
|
||||
solAssert(_targetType == _typeOnStack, "Invalid conversion for bool.");
|
||||
if (_cleanupNeeded)
|
||||
|
||||
@@ -73,6 +73,23 @@ Type const* closestType(Type const* _type, Type const* _targetType, bool _isShif
|
||||
}
|
||||
return TypeProvider::tuple(move(tempComponents));
|
||||
}
|
||||
else if (auto const* inlineArrayType = dynamic_cast<InlineArrayType const*>(_type))
|
||||
{
|
||||
auto targetArray = dynamic_cast<ArrayType const*>(_targetType);
|
||||
solAssert(targetArray);
|
||||
|
||||
if (targetArray->isDynamicallySized())
|
||||
return TypeProvider::array(
|
||||
DataLocation::Memory,
|
||||
targetArray->baseType()
|
||||
);
|
||||
else
|
||||
return TypeProvider::array(
|
||||
DataLocation::Memory,
|
||||
targetArray->baseType(),
|
||||
inlineArrayType->components().size()
|
||||
);
|
||||
}
|
||||
else
|
||||
return _targetType->dataStoredIn(DataLocation::Storage) ? _type->mobileType() : _targetType;
|
||||
}
|
||||
@@ -93,18 +110,21 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
|
||||
_varDecl.value()->accept(*this);
|
||||
|
||||
if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage))
|
||||
if (type->category() != Type::Category::InlineArray)
|
||||
{
|
||||
// reference type, only convert value to mobile type and do final conversion in storeValue.
|
||||
auto mt = type->mobileType();
|
||||
solAssert(mt, "");
|
||||
utils().convertType(*type, *mt);
|
||||
type = mt;
|
||||
}
|
||||
else
|
||||
{
|
||||
utils().convertType(*type, *_varDecl.annotation().type);
|
||||
type = _varDecl.annotation().type;
|
||||
if (_varDecl.annotation().type->dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
// reference type, only convert value to mobile type and do final conversion in storeValue.
|
||||
auto mt = type->mobileType();
|
||||
solAssert(mt, "");
|
||||
utils().convertType(*type, *mt);
|
||||
type = mt;
|
||||
}
|
||||
else
|
||||
{
|
||||
utils().convertType(*type, *_varDecl.annotation().type);
|
||||
type = _varDecl.annotation().type;
|
||||
}
|
||||
}
|
||||
if (_varDecl.immutable())
|
||||
ImmutableItem(m_context, _varDecl).storeValue(*type, _varDecl.location(), true);
|
||||
@@ -365,44 +385,25 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
|
||||
|
||||
bool ExpressionCompiler::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
if (_tuple.isInlineArray())
|
||||
{
|
||||
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
|
||||
|
||||
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
|
||||
utils().allocateMemory(max(u256(32u), arrayType.memoryDataSize()));
|
||||
m_context << Instruction::DUP1;
|
||||
|
||||
for (auto const& component: _tuple.components())
|
||||
vector<unique_ptr<LValue>> lvalues;
|
||||
for (auto const& component: _tuple.components())
|
||||
if (component)
|
||||
{
|
||||
acceptAndConvert(*component, *arrayType.baseType(), true);
|
||||
utils().storeInMemoryDynamic(*arrayType.baseType(), true);
|
||||
}
|
||||
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
else
|
||||
{
|
||||
vector<unique_ptr<LValue>> lvalues;
|
||||
for (auto const& component: _tuple.components())
|
||||
if (component)
|
||||
component->accept(*this);
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
component->accept(*this);
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
solAssert(!!m_currentLValue, "");
|
||||
lvalues.push_back(move(m_currentLValue));
|
||||
}
|
||||
solAssert(!!m_currentLValue, "");
|
||||
lvalues.push_back(move(m_currentLValue));
|
||||
}
|
||||
else if (_tuple.annotation().willBeWrittenTo)
|
||||
lvalues.push_back(unique_ptr<LValue>());
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
if (_tuple.components().size() == 1)
|
||||
m_currentLValue = move(lvalues[0]);
|
||||
else
|
||||
m_currentLValue = make_unique<TupleObject>(m_context, move(lvalues));
|
||||
}
|
||||
else if (_tuple.annotation().willBeWrittenTo)
|
||||
lvalues.push_back(unique_ptr<LValue>());
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
{
|
||||
if (_tuple.components().size() == 1)
|
||||
m_currentLValue = move(lvalues[0]);
|
||||
else
|
||||
m_currentLValue = make_unique<TupleObject>(m_context, move(lvalues));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -2127,6 +2128,27 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Type::Category::InlineArray:
|
||||
{
|
||||
InlineArrayType const& inlineArrayType = dynamic_cast<InlineArrayType const&>(baseType);
|
||||
solAssert(_indexAccess.indexExpression(), "Index expression expected.");
|
||||
|
||||
ArrayType const* arrayType = TypeProvider::array(DataLocation::Memory, inlineArrayType.componentsCommonMobileType(), inlineArrayType.components().size());
|
||||
|
||||
// stack layout: <source ref> (variably sized)
|
||||
acceptAndConvert(_indexAccess.baseExpression(), *arrayType, true);
|
||||
utils().moveIntoStack(inlineArrayType.sizeOnStack());
|
||||
utils().popStackSlots(inlineArrayType.sizeOnStack());
|
||||
// stack layout: <array_ref> [<length>]
|
||||
|
||||
acceptAndConvert(*_indexAccess.indexExpression(), *TypeProvider::uint256(), true);
|
||||
// stack layout: <array_ref> [<length>] <index>
|
||||
|
||||
ArrayUtils(m_context).accessIndex(*arrayType, true);
|
||||
setLValue<MemoryItem>(_indexAccess, *arrayType->baseType());
|
||||
|
||||
break;
|
||||
}
|
||||
case Type::Category::FixedBytes:
|
||||
{
|
||||
FixedBytesType const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);
|
||||
|
||||
@@ -359,86 +359,100 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(
|
||||
_sourceType.category() == m_dataType->category(),
|
||||
"Wrong type conversation for assignment."
|
||||
);
|
||||
if (m_dataType->category() == Type::Category::Array)
|
||||
if (_sourceType.category() == Type::Category::InlineArray)
|
||||
{
|
||||
m_context << Instruction::POP; // remove byte offset
|
||||
ArrayUtils(m_context).copyArrayToStorage(
|
||||
ArrayUtils(m_context).moveInlineArrayToStorage(
|
||||
dynamic_cast<ArrayType const&>(*m_dataType),
|
||||
dynamic_cast<ArrayType const&>(_sourceType)
|
||||
dynamic_cast<InlineArrayType const&>(_sourceType)
|
||||
);
|
||||
if (_move)
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
else if (m_dataType->category() == Type::Category::Struct)
|
||||
{
|
||||
// stack layout: source_ref target_ref target_offset
|
||||
// note that we have structs, so offset should be zero and are ignored
|
||||
m_context << Instruction::POP;
|
||||
auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
|
||||
auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
|
||||
solAssert(
|
||||
structType.structDefinition() == sourceType.structDefinition(),
|
||||
"Struct assignment with conversion."
|
||||
);
|
||||
solAssert(!structType.containsNestedMapping(), "");
|
||||
if (sourceType.location() == DataLocation::CallData)
|
||||
{
|
||||
solAssert(sourceType.sizeOnStack() == 1, "");
|
||||
solAssert(structType.sizeOnStack() == 1, "");
|
||||
m_context << Instruction::DUP2 << Instruction::DUP2;
|
||||
m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const& member: structType.members(nullptr))
|
||||
{
|
||||
// assign each member that can live outside of storage
|
||||
Type const* memberType = member.type;
|
||||
solAssert(memberType->nameable(), "");
|
||||
Type const* sourceMemberType = sourceType.memberType(member.name);
|
||||
if (sourceType.location() == DataLocation::Storage)
|
||||
{
|
||||
// stack layout: source_ref target_ref
|
||||
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
|
||||
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
|
||||
m_context << u256(offsets.second);
|
||||
// stack: source_ref target_ref source_member_ref source_member_off
|
||||
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
|
||||
// stack: source_ref target_ref source_value...
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(sourceType.location() == DataLocation::Memory, "");
|
||||
// stack layout: source_ref target_ref
|
||||
m_context << sourceType.memoryOffsetOfMember(member.name);
|
||||
m_context << Instruction::DUP3 << Instruction::ADD;
|
||||
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
|
||||
// stack layout: source_ref target_ref source_value...
|
||||
}
|
||||
unsigned stackSize = sourceMemberType->sizeOnStack();
|
||||
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
|
||||
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
|
||||
m_context << u256(offsets.second);
|
||||
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
|
||||
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
|
||||
}
|
||||
}
|
||||
// stack layout: source_ref target_ref
|
||||
solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");
|
||||
if (_move)
|
||||
utils.popStackSlots(2);
|
||||
else
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
}
|
||||
else
|
||||
BOOST_THROW_EXCEPTION(
|
||||
InternalCompilerError()
|
||||
<< errinfo_sourceLocation(_location)
|
||||
<< util::errinfo_comment("Invalid non-value type for assignment."));
|
||||
{
|
||||
solAssert(
|
||||
_sourceType.category() == m_dataType->category(),
|
||||
"Wrong type conversation for assignment."
|
||||
);
|
||||
|
||||
if (m_dataType->category() == Type::Category::Array)
|
||||
{
|
||||
m_context << Instruction::POP; // remove byte offset
|
||||
ArrayUtils(m_context).copyArrayToStorage(
|
||||
dynamic_cast<ArrayType const&>(*m_dataType),
|
||||
dynamic_cast<ArrayType const&>(_sourceType)
|
||||
);
|
||||
if (_move)
|
||||
m_context << Instruction::POP;
|
||||
}
|
||||
else if (m_dataType->category() == Type::Category::Struct)
|
||||
{
|
||||
// stack layout: source_ref target_ref target_offset
|
||||
// note that we have structs, so offset should be zero and are ignored
|
||||
m_context << Instruction::POP;
|
||||
auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
|
||||
auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
|
||||
solAssert(
|
||||
structType.structDefinition() == sourceType.structDefinition(),
|
||||
"Struct assignment with conversion."
|
||||
);
|
||||
solAssert(!structType.containsNestedMapping(), "");
|
||||
if (sourceType.location() == DataLocation::CallData)
|
||||
{
|
||||
solAssert(sourceType.sizeOnStack() == 1, "");
|
||||
solAssert(structType.sizeOnStack() == 1, "");
|
||||
m_context << Instruction::DUP2 << Instruction::DUP2;
|
||||
m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, 0), 2, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto const& member: structType.members(nullptr))
|
||||
{
|
||||
// assign each member that can live outside of storage
|
||||
Type const* memberType = member.type;
|
||||
solAssert(memberType->nameable(), "");
|
||||
Type const* sourceMemberType = sourceType.memberType(member.name);
|
||||
if (sourceType.location() == DataLocation::Storage)
|
||||
{
|
||||
// stack layout: source_ref target_ref
|
||||
pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
|
||||
m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
|
||||
m_context << u256(offsets.second);
|
||||
// stack: source_ref target_ref source_member_ref source_member_off
|
||||
StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
|
||||
// stack: source_ref target_ref source_value...
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(sourceType.location() == DataLocation::Memory, "");
|
||||
// stack layout: source_ref target_ref
|
||||
m_context << sourceType.memoryOffsetOfMember(member.name);
|
||||
m_context << Instruction::DUP3 << Instruction::ADD;
|
||||
MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
|
||||
// stack layout: source_ref target_ref source_value...
|
||||
}
|
||||
unsigned stackSize = sourceMemberType->sizeOnStack();
|
||||
pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
|
||||
m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
|
||||
m_context << u256(offsets.second);
|
||||
// stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
|
||||
StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
|
||||
}
|
||||
}
|
||||
// stack layout: source_ref target_ref
|
||||
solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");
|
||||
if (_move)
|
||||
utils.popStackSlots(2);
|
||||
else
|
||||
m_context << Instruction::SWAP1 << Instruction::POP;
|
||||
}
|
||||
else
|
||||
BOOST_THROW_EXCEPTION(
|
||||
InternalCompilerError()
|
||||
<< errinfo_sourceLocation(_location)
|
||||
<< util::errinfo_comment("Invalid non-value type for assignment."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#include <libsolutil/StringUtils.h>
|
||||
#include <libsolidity/ast/TypeProvider.h>
|
||||
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity;
|
||||
using namespace solidity::util;
|
||||
@@ -1926,6 +1928,78 @@ string YulUtilFunctions::copyArrayToStorageFunction(ArrayType const& _fromType,
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::copyInlineArrayToStorageFunction(InlineArrayType const& _fromType, ArrayType const& _toType)
|
||||
{
|
||||
if (!_toType.isDynamicallySized())
|
||||
solAssert(_fromType.components().size() <= _toType.length(), "");
|
||||
|
||||
string const functionName = "copy_inline_array_to_storage_from_" + _fromType.identifier() + "_to_" + _toType.identifier();
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&](){
|
||||
|
||||
vector<map<string, string>> memberSetValues;
|
||||
unsigned stackItemIndex = 0;
|
||||
for (Type const* type: _fromType.components())
|
||||
{
|
||||
memberSetValues.emplace_back();
|
||||
memberSetValues.back()["setMember"] = Whiskers(R"({
|
||||
<updateStorageValue>(elementSlot, elementOffset<value>)
|
||||
|
||||
<?multipleItemsPerSlot>
|
||||
elementOffset := add(elementOffset, <storageStride>)
|
||||
if gt(elementOffset, sub(32, <storageStride>)) {
|
||||
elementOffset := 0
|
||||
elementSlot := add(elementSlot, 1)
|
||||
}
|
||||
<!multipleItemsPerSlot>
|
||||
elementSlot := add(elementSlot, <storageSize>)
|
||||
</multipleItemsPerSlot>
|
||||
})")
|
||||
("value", _fromType.sizeOnStack() ?
|
||||
", " + suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack()) : "")
|
||||
("multipleItemsPerSlot", _toType.storageStride() <= 16)
|
||||
("storageStride", to_string(_toType.storageStride()))
|
||||
("storageSize", _toType.baseType()->storageSize().str())
|
||||
("updateStorageValue", updateStorageValueFunction(*type, *_toType.baseType()))
|
||||
.render();
|
||||
|
||||
stackItemIndex += type->sizeOnStack();
|
||||
}
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot<values>) {
|
||||
let length := <arrayLength>
|
||||
<resizeArray>(slot, length)
|
||||
|
||||
let elementSlot := <dstDataLocation>(slot)
|
||||
let elementOffset := 0
|
||||
|
||||
<#member>
|
||||
<setMember>
|
||||
</member>
|
||||
|
||||
<?multipleItemsPerSlot>
|
||||
if gt(elementOffset, 0) {
|
||||
<partialClearStorageSlotFunction>(elementSlot, elementOffset)
|
||||
}
|
||||
</multipleItemsPerSlot>
|
||||
}
|
||||
)");
|
||||
if (_fromType.dataStoredIn(DataLocation::Storage))
|
||||
solAssert(!_fromType.isValueType(), "");
|
||||
templ("functionName", functionName);
|
||||
templ("values", _fromType.sizeOnStack() ?
|
||||
", " + suffixedVariableNameList("var_", 0, _fromType.sizeOnStack()) : "");
|
||||
templ("arrayLength", to_string(_fromType.components().size()));
|
||||
templ("resizeArray", resizeArrayFunction(_toType));
|
||||
templ("dstDataLocation", arrayDataAreaFunction(_toType));
|
||||
templ("member", move(memberSetValues));
|
||||
templ("multipleItemsPerSlot", _toType.storageStride() <= 16);
|
||||
templ("partialClearStorageSlotFunction", partialClearStorageSlotFunction());
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType)
|
||||
{
|
||||
@@ -2820,6 +2894,31 @@ string YulUtilFunctions::updateStorageValueFunction(
|
||||
auto const* fromReferenceType = dynamic_cast<ReferenceType const*>(&_fromType);
|
||||
solAssert(toReferenceType, "");
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot<?dynamicOffset>,offset </dynamicOffset><extraParams>) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
<copyToStorage>(slot<extraParams>)
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("dynamicOffset", !_offset.has_value());
|
||||
templ("panic", panicFunction(PanicCode::Generic));
|
||||
|
||||
if (_fromType.category() == Type::Category::InlineArray)
|
||||
{
|
||||
solAssert(_toType.category() == Type::Category::Array, "");
|
||||
solAssert(!dynamic_cast<ArrayType const&>(*toReferenceType).isByteArrayOrString(), "");
|
||||
|
||||
templ("extraParams", _fromType.sizeOnStack() ?
|
||||
", " + suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()) : "");
|
||||
templ("copyToStorage", copyInlineArrayToStorageFunction(
|
||||
dynamic_cast<InlineArrayType const&>(_fromType),
|
||||
dynamic_cast<ArrayType const&>(_toType)
|
||||
));
|
||||
|
||||
return templ.render();
|
||||
}
|
||||
|
||||
if (!fromReferenceType)
|
||||
{
|
||||
solAssert(_fromType.category() == Type::Category::StringLiteral, "");
|
||||
@@ -2827,17 +2926,10 @@ string YulUtilFunctions::updateStorageValueFunction(
|
||||
auto const& toArrayType = dynamic_cast<ArrayType const&>(*toReferenceType);
|
||||
solAssert(toArrayType.isByteArrayOrString(), "");
|
||||
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot<?dynamicOffset>, offset</dynamicOffset>) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
<copyToStorage>(slot)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("dynamicOffset", !_offset.has_value())
|
||||
("panic", panicFunction(PanicCode::Generic))
|
||||
("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()))
|
||||
.render();
|
||||
templ("extraParams", "");
|
||||
templ("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()));
|
||||
|
||||
return templ.render();
|
||||
}
|
||||
|
||||
solAssert(*toReferenceType->copyForLocation(
|
||||
@@ -2851,16 +2943,7 @@ string YulUtilFunctions::updateStorageValueFunction(
|
||||
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
|
||||
solAssert(_offset.value_or(0) == 0, "");
|
||||
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(slot, <?dynamicOffset>offset, </dynamicOffset><value>) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
<copyToStorage>(slot, <value>)
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("dynamicOffset", !_offset.has_value());
|
||||
templ("panic", panicFunction(PanicCode::Generic));
|
||||
templ("value", suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
|
||||
templ("extraParams", ", " + suffixedVariableNameList("value_", 0, _fromType.sizeOnStack()));
|
||||
if (_fromType.category() == Type::Category::Array)
|
||||
templ("copyToStorage", copyArrayToStorageFunction(
|
||||
dynamic_cast<ArrayType const&>(_fromType),
|
||||
@@ -3332,6 +3415,11 @@ string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
solAssert(_to.category() == Type::Category::Array, "");
|
||||
return arrayConversionFunction(fromArrayType, dynamic_cast<ArrayType const&>(_to));
|
||||
}
|
||||
else if (_from.category() == Type::Category::InlineArray)
|
||||
{
|
||||
solAssert(_to.category() == Type::Category::Array, "");
|
||||
return inlineArrayConversionFunction(dynamic_cast<InlineArrayType const&>(_from), dynamic_cast<ArrayType const&>(_to));
|
||||
}
|
||||
|
||||
if (_from.sizeOnStack() != 1 || _to.sizeOnStack() != 1)
|
||||
return conversionFunctionSpecial(_from, _to);
|
||||
@@ -3781,6 +3869,64 @@ string YulUtilFunctions::arrayConversionFunction(ArrayType const& _from, ArrayTy
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::inlineArrayConversionFunction(InlineArrayType const& _from, ArrayType const& _to)
|
||||
{
|
||||
if (_to.dataStoredIn(DataLocation::CallData))
|
||||
solAssert(false);
|
||||
|
||||
if (!_to.isDynamicallySized())
|
||||
solAssert(_to.length() == _from.components().size());
|
||||
|
||||
string functionName =
|
||||
"convert_inline_array_" +
|
||||
_from.identifier() +
|
||||
"_to_" +
|
||||
_to.identifier();
|
||||
|
||||
vector<map<string, string>> memberSetValues;
|
||||
unsigned stackItemIndex = 0;
|
||||
|
||||
for (auto&& [index, type]: _from.components() | ranges::views::enumerate)
|
||||
{
|
||||
memberSetValues.emplace_back();
|
||||
memberSetValues.back()["setMember"] = Whiskers(R"(
|
||||
let <memberValues> := <conversionFunction>(<value>)
|
||||
<writeToMemory>(add(mpos, <offset>), <memberValues>)
|
||||
)")
|
||||
("memberValues", suffixedVariableNameList("memberValue_", 0, _to.baseType()->stackItems().size()))
|
||||
("offset", to_string(0x20 * index))
|
||||
("value", suffixedVariableNameList("var_", stackItemIndex, stackItemIndex + type->sizeOnStack()))
|
||||
("conversionFunction", conversionFunction(*type, *_to.baseType()))
|
||||
("writeToMemory", writeToMemoryFunction(*_to.baseType()))
|
||||
.render();
|
||||
|
||||
stackItemIndex += type->sizeOnStack();
|
||||
}
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(<values>) -> converted {
|
||||
converted := <allocateArray>(<length>)
|
||||
let mpos := converted
|
||||
<?toDynamic>mpos := add(mpos, 0x20)</toDynamic>
|
||||
<#member>
|
||||
{
|
||||
<setMember>
|
||||
}
|
||||
</member>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("allocateArray", allocateMemoryArrayFunction(_to));
|
||||
templ("length", toCompactHexWithPrefix(_from.components().size()));
|
||||
templ("toDynamic", _to.isDynamicallySized());
|
||||
templ("values", suffixedVariableNameList("var_", 0, _from.sizeOnStack()));
|
||||
templ("member", move(memberSetValues));
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::cleanupFunction(Type const& _type)
|
||||
{
|
||||
if (auto userDefinedValueType = dynamic_cast<UserDefinedValueType const*>(&_type))
|
||||
|
||||
@@ -255,6 +255,9 @@ public:
|
||||
/// signature (to_slot, from_ptr) ->
|
||||
std::string copyArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
|
||||
|
||||
std::string copyInlineArrayToStorageFunction(InlineArrayType const& _fromType, ArrayType const& _toType);
|
||||
|
||||
|
||||
/// @returns the name of a function that will copy a byte array to storage
|
||||
/// signature (to_slot, from_ptr) ->
|
||||
std::string copyByteArrayToStorageFunction(ArrayType const& _fromType, ArrayType const& _toType);
|
||||
@@ -538,6 +541,8 @@ private:
|
||||
/// Special case of conversion functions - handles all array conversions.
|
||||
std::string arrayConversionFunction(ArrayType const& _from, ArrayType const& _to);
|
||||
|
||||
std::string inlineArrayConversionFunction(InlineArrayType const& _from, ArrayType const& _to);
|
||||
|
||||
/// Special case of conversionFunction - handles everything that does not
|
||||
/// use exactly one variable to hold the value.
|
||||
std::string conversionFunctionSpecial(Type const& _from, Type const& _to);
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include <libsolutil/FunctionSelector.h>
|
||||
#include <libsolutil/Visitor.h>
|
||||
|
||||
#include <range/v3/view/enumerate.hpp>
|
||||
#include <range/v3/view/transform.hpp>
|
||||
|
||||
using namespace std;
|
||||
@@ -492,75 +493,49 @@ bool IRGeneratorForStatements::visit(TupleExpression const& _tuple)
|
||||
{
|
||||
setLocation(_tuple);
|
||||
|
||||
if (_tuple.isInlineArray())
|
||||
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
|
||||
if (willBeWrittenTo)
|
||||
solAssert(!m_currentLValue);
|
||||
if (!_tuple.isInlineArray() && _tuple.components().size() == 1)
|
||||
{
|
||||
auto const& arrayType = dynamic_cast<ArrayType const&>(*_tuple.annotation().type);
|
||||
solAssert(!arrayType.isDynamicallySized(), "Cannot create dynamically sized inline array.");
|
||||
define(_tuple) <<
|
||||
m_utils.allocateMemoryArrayFunction(arrayType) <<
|
||||
"(" <<
|
||||
_tuple.components().size() <<
|
||||
")\n";
|
||||
|
||||
string mpos = IRVariable(_tuple).part("mpos").name();
|
||||
Type const& baseType = *arrayType.baseType();
|
||||
for (size_t i = 0; i < _tuple.components().size(); i++)
|
||||
{
|
||||
Expression const& component = *_tuple.components()[i];
|
||||
component.accept(*this);
|
||||
setLocation(_tuple);
|
||||
IRVariable converted = convert(component, baseType);
|
||||
appendCode() <<
|
||||
m_utils.writeToMemoryFunction(baseType) <<
|
||||
"(" <<
|
||||
("add(" + mpos + ", " + to_string(i * arrayType.memoryStride()) + ")") <<
|
||||
", " <<
|
||||
converted.commaSeparatedList() <<
|
||||
")\n";
|
||||
}
|
||||
solAssert(_tuple.components().front());
|
||||
_tuple.components().front()->accept(*this);
|
||||
setLocation(_tuple);
|
||||
if (willBeWrittenTo)
|
||||
solAssert(!!m_currentLValue);
|
||||
else
|
||||
define(_tuple, *_tuple.components().front());
|
||||
}
|
||||
else
|
||||
{
|
||||
bool willBeWrittenTo = _tuple.annotation().willBeWrittenTo;
|
||||
if (willBeWrittenTo)
|
||||
solAssert(!m_currentLValue);
|
||||
if (_tuple.components().size() == 1)
|
||||
{
|
||||
solAssert(_tuple.components().front());
|
||||
_tuple.components().front()->accept(*this);
|
||||
setLocation(_tuple);
|
||||
if (willBeWrittenTo)
|
||||
solAssert(!!m_currentLValue);
|
||||
else
|
||||
define(_tuple, *_tuple.components().front());
|
||||
}
|
||||
else
|
||||
{
|
||||
vector<optional<IRLValue>> lvalues;
|
||||
for (size_t i = 0; i < _tuple.components().size(); ++i)
|
||||
if (auto const& component = _tuple.components()[i])
|
||||
{
|
||||
component->accept(*this);
|
||||
setLocation(_tuple);
|
||||
if (willBeWrittenTo)
|
||||
{
|
||||
solAssert(!!m_currentLValue);
|
||||
lvalues.emplace_back(std::move(m_currentLValue));
|
||||
m_currentLValue.reset();
|
||||
}
|
||||
else
|
||||
define(IRVariable(_tuple).tupleComponent(i), *component);
|
||||
}
|
||||
else if (willBeWrittenTo)
|
||||
lvalues.emplace_back();
|
||||
vector<optional<IRLValue>> lvalues;
|
||||
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
m_currentLValue.emplace(IRLValue{
|
||||
*_tuple.annotation().type,
|
||||
IRLValue::Tuple{std::move(lvalues)}
|
||||
});
|
||||
for (auto&& [index, component]: _tuple.components() | ranges::views::enumerate)
|
||||
{
|
||||
if (component)
|
||||
{
|
||||
component->accept(*this);
|
||||
setLocation(_tuple);
|
||||
if (willBeWrittenTo)
|
||||
{
|
||||
solAssert(!!m_currentLValue);
|
||||
lvalues.emplace_back(std::move(m_currentLValue));
|
||||
m_currentLValue.reset();
|
||||
}
|
||||
else
|
||||
define(IRVariable(_tuple).tupleComponent(index), *component);
|
||||
}
|
||||
else if (willBeWrittenTo)
|
||||
lvalues.emplace_back();
|
||||
}
|
||||
|
||||
if (_tuple.annotation().willBeWrittenTo)
|
||||
m_currentLValue.emplace(IRLValue{
|
||||
*_tuple.annotation().type,
|
||||
IRLValue::Tuple{std::move(lvalues)}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2284,6 +2259,29 @@ void IRGeneratorForStatements::endVisit(IndexAccess const& _indexAccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (baseType.category() == Type::Category::InlineArray)
|
||||
{
|
||||
InlineArrayType const& inlineArrayType = dynamic_cast<InlineArrayType const&>(baseType);
|
||||
|
||||
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(inlineArrayType.mobileType());
|
||||
solAssert(arrayType);
|
||||
|
||||
IRVariable irArray = convert(IRVariable(_indexAccess.baseExpression()), *arrayType);
|
||||
|
||||
string const memAddress =
|
||||
m_utils.memoryArrayIndexAccessFunction(*arrayType) +
|
||||
"(" +
|
||||
irArray.part("mpos").name() +
|
||||
", " +
|
||||
expressionAsType(*_indexAccess.indexExpression(), *TypeProvider::uint256()) +
|
||||
")";
|
||||
|
||||
setLValue(_indexAccess, IRLValue{
|
||||
*arrayType->baseType(),
|
||||
IRLValue::Memory{memAddress}
|
||||
});
|
||||
}
|
||||
|
||||
else if (baseType.category() == Type::Category::FixedBytes)
|
||||
{
|
||||
auto const& fixedBytesType = dynamic_cast<FixedBytesType const&>(baseType);
|
||||
@@ -2532,7 +2530,7 @@ void IRGeneratorForStatements::appendExternalFunctionCall(
|
||||
}
|
||||
|
||||
// NOTE: When the expected size of returndata is static, we pass that in to the call opcode and it gets copied automatically.
|
||||
// When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy().
|
||||
// When it's dynamic, we get zero from estimatedReturnSize() instead and then we need an explicit returndatacopy().
|
||||
Whiskers templ(R"(
|
||||
<?checkExtcodesize>
|
||||
if iszero(extcodesize(<address>)) { <revertNoCode>() }
|
||||
@@ -3024,6 +3022,12 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
|
||||
m_utils.copyLiteralToMemoryFunction(literalType->value()) + "()" <<
|
||||
")\n";
|
||||
}
|
||||
else if (dynamic_cast<InlineArrayType const*>(&_value.type()))
|
||||
{
|
||||
solAssert(dynamic_cast<ArrayType const*>(&_lvalue.type));
|
||||
IRVariable value = convert(_value, _lvalue.type);
|
||||
writeToLValue(_lvalue, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(_lvalue.type.sizeOnStack() == 1);
|
||||
|
||||
@@ -102,7 +102,7 @@ string IRVariable::name() const
|
||||
IRVariable IRVariable::tupleComponent(size_t _i) const
|
||||
{
|
||||
solAssert(
|
||||
m_type.category() == Type::Category::Tuple,
|
||||
m_type.category() == Type::Category::Tuple || m_type.category() == Type::Category::InlineArray,
|
||||
"Requested tuple component of non-tuple IR variable."
|
||||
);
|
||||
return part(IRNames::tupleComponent(_i));
|
||||
|
||||
Reference in New Issue
Block a user