Add InlineArrayType to support literals conversion to statically and dynamically allocated arrays.

This commit is contained in:
wechman
2022-08-29 07:18:35 +02:00
parent 7bfec3ba70
commit 1ef2f60049
63 changed files with 1581 additions and 384 deletions
+92
View File
@@ -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,
+5
View File
@@ -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,
+234 -10
View File
@@ -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;
+48 -2
View File
@@ -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;
};
+82 -2
View File
@@ -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)
+67 -45
View File
@@ -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);
+86 -72
View File
@@ -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."));
}
}
}
+167 -21
View File
@@ -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))
+5
View File
@@ -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);
+1 -1
View File
@@ -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));