Skip to content

Commit f0c5cdc

Browse files
mijovicchriseth
andcommitted
[Sol->Yul] Adding util function to copy literal to storage.
Co-authored-by: Daniel Kirchner <daniel@ekpyron.org> Co-authored-by: chriseth <chris@ethereum.org>
1 parent 9d156b5 commit f0c5cdc

8 files changed

Lines changed: 175 additions & 82 deletions

File tree

libsolidity/ast/Types.cpp

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2613,23 +2613,6 @@ Type const* TupleType::mobileType() const
26132613
return TypeProvider::tuple(move(mobiles));
26142614
}
26152615

2616-
Type const* TupleType::closestTemporaryType(Type const* _targetType) const
2617-
{
2618-
solAssert(!!_targetType, "");
2619-
TypePointers const& targetComponents = dynamic_cast<TupleType const&>(*_targetType).components();
2620-
solAssert(components().size() == targetComponents.size(), "");
2621-
TypePointers tempComponents(targetComponents.size());
2622-
for (size_t i = 0; i < targetComponents.size(); ++i)
2623-
{
2624-
if (components()[i] && targetComponents[i])
2625-
{
2626-
tempComponents[i] = components()[i]->closestTemporaryType(targetComponents[i]);
2627-
solAssert(tempComponents[i], "");
2628-
}
2629-
}
2630-
return TypeProvider::tuple(move(tempComponents));
2631-
}
2632-
26332616
FunctionType::FunctionType(FunctionDefinition const& _function, Kind _kind):
26342617
m_kind(_kind),
26352618
m_stateMutability(_function.stateMutability()),

libsolidity/ast/Types.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,6 @@ class Type
324324
/// @returns true if this is a non-value type and the data of this type is stored at the
325325
/// given location.
326326
virtual bool dataStoredIn(DataLocation) const { return false; }
327-
/// @returns the type of a temporary during assignment to a variable of the given type.
328-
/// Specifically, returns the requested itself if it can be dynamically allocated (or is a value type)
329-
/// and the mobile type otherwise.
330-
virtual Type const* closestTemporaryType(Type const* _targetType) const
331-
{
332-
return _targetType->dataStoredIn(DataLocation::Storage) ? mobileType() : _targetType;
333-
}
334327

335328
/// Returns the list of all members of this type. Default implementation: no members apart from bound.
336329
/// @param _currentScope scope in which the members are accessed.
@@ -1103,8 +1096,6 @@ class TupleType: public CompositeType
11031096
u256 storageSize() const override;
11041097
bool hasSimpleZeroValueInMemory() const override { return false; }
11051098
Type const* mobileType() const override;
1106-
/// Converts components to their temporary types and performs some wildcard matching.
1107-
Type const* closestTemporaryType(Type const* _targetType) const override;
11081099

11091100
std::vector<Type const*> const& components() const { return m_components; }
11101101

libsolidity/codegen/ExpressionCompiler.cpp

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,34 @@ using namespace solidity::frontend;
4949
using namespace solidity::langutil;
5050
using namespace solidity::util;
5151

52+
namespace
53+
{
54+
55+
Type const* closestType(Type const* _type, Type const* _targetType, bool _isShiftOp)
56+
{
57+
if (_isShiftOp)
58+
return _type->mobileType();
59+
else if (auto const* tupleType = dynamic_cast<TupleType const*>(_type))
60+
{
61+
solAssert(_targetType, "");
62+
TypePointers const& targetComponents = dynamic_cast<TupleType const&>(*_targetType).components();
63+
solAssert(tupleType->components().size() == targetComponents.size(), "");
64+
TypePointers tempComponents(targetComponents.size());
65+
for (size_t i = 0; i < targetComponents.size(); ++i)
66+
{
67+
if (tupleType->components()[i] && targetComponents[i])
68+
{
69+
tempComponents[i] = closestType(tupleType->components()[i], targetComponents[i], _isShiftOp);
70+
solAssert(tempComponents[i], "");
71+
}
72+
}
73+
return TypeProvider::tuple(move(tempComponents));
74+
}
75+
else
76+
return _targetType->dataStoredIn(DataLocation::Storage) ? _type->mobileType() : _targetType;
77+
}
78+
79+
}
5280

5381
void ExpressionCompiler::compile(Expression const& _expression)
5482
{
@@ -280,13 +308,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
280308
_assignment.rightHandSide().accept(*this);
281309
// Perform some conversion already. This will convert storage types to memory and literals
282310
// to their actual type, but will not convert e.g. memory to storage.
283-
Type const* rightIntermediateType;
284-
if (op != Token::Assign && TokenTraits::isShiftOp(binOp))
285-
rightIntermediateType = _assignment.rightHandSide().annotation().type->mobileType();
286-
else
287-
rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType(
288-
_assignment.leftHandSide().annotation().type
289-
);
311+
Type const* rightIntermediateType = closestType(
312+
_assignment.rightHandSide().annotation().type,
313+
_assignment.leftHandSide().annotation().type,
314+
op != Token::Assign && TokenTraits::isShiftOp(binOp)
315+
);
316+
290317
solAssert(rightIntermediateType, "");
291318
utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded);
292319

@@ -1016,7 +1043,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
10161043
// stack: argValue storageSlot slotOffset
10171044
utils().moveToStackTop(2, argType->sizeOnStack());
10181045
// stack: storageSlot slotOffset argValue
1019-
Type const* type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
1046+
Type const* type =
1047+
arrayType->baseType()->dataStoredIn(DataLocation::Storage) ?
1048+
arguments[0]->annotation().type->mobileType() :
1049+
arrayType->baseType();
10201050
solAssert(type, "");
10211051
utils().convertType(*argType, *type);
10221052
utils().moveToStackTop(1 + type->sizeOnStack());

libsolidity/codegen/YulUtilFunctions.cpp

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,54 @@ string YulUtilFunctions::storeLiteralInMemoryFunction(string const& _literal)
152152
});
153153
}
154154

155+
string YulUtilFunctions::copyLiteralToStorageFunction(string const& _literal)
156+
{
157+
string functionName = "copy_literal_to_storage_" + util::toHex(util::keccak256(_literal).asBytes());
158+
159+
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
160+
_args = {"slot"};
161+
162+
if (_literal.size() >= 32)
163+
{
164+
size_t words = (_literal.length() + 31) / 32;
165+
vector<map<string, string>> wordParams(words);
166+
for (size_t i = 0; i < words; ++i)
167+
{
168+
wordParams[i]["offset"] = to_string(i);
169+
wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32));
170+
}
171+
return Whiskers(R"(
172+
let oldLen := <byteArrayLength>(sload(slot))
173+
<cleanUpArrayEnd>(slot, oldLen, <length>)
174+
sstore(slot, <encodedLen>)
175+
let dstPtr := <dataArea>(slot)
176+
<#word>
177+
sstore(add(dstPtr, <offset>), <wordValue>)
178+
</word>
179+
)")
180+
("byteArrayLength", extractByteArrayLengthFunction())
181+
("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage()))
182+
("dataArea", arrayDataAreaFunction(*TypeProvider::bytesStorage()))
183+
("word", wordParams)
184+
("length", to_string(_literal.size()))
185+
("encodedLen", to_string(2 * _literal.size() + 1))
186+
.render();
187+
}
188+
else
189+
return Whiskers(R"(
190+
let oldLen := <byteArrayLength>(sload(slot))
191+
<cleanUpArrayEnd>(slot, oldLen, <length>)
192+
sstore(slot, add(<wordValue>, <encodedLen>))
193+
)")
194+
("byteArrayLength", extractByteArrayLengthFunction())
195+
("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage()))
196+
("wordValue", formatAsStringOrNumber(_literal))
197+
("length", to_string(_literal.size()))
198+
("encodedLen", to_string(2 * _literal.size()))
199+
.render();
200+
});
201+
}
202+
155203
string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType)
156204
{
157205
string functionName =
@@ -2680,15 +2728,13 @@ string YulUtilFunctions::updateStorageValueFunction(
26802728
return Whiskers(R"(
26812729
function <functionName>(slot<?dynamicOffset>, offset</dynamicOffset>) {
26822730
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
2683-
let value := <copyLiteralToMemory>()
2684-
<copyToStorage>(slot, value)
2731+
<copyToStorage>(slot)
26852732
}
26862733
)")
26872734
("functionName", functionName)
26882735
("dynamicOffset", !_offset.has_value())
26892736
("panic", panicFunction(PanicCode::Generic))
2690-
("copyLiteralToMemory", copyLiteralToMemoryFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()))
2691-
("copyToStorage", copyArrayToStorageFunction(*TypeProvider::bytesMemory(), toArrayType))
2737+
("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()))
26922738
.render();
26932739
}
26942740

@@ -2697,7 +2743,10 @@ string YulUtilFunctions::updateStorageValueFunction(
26972743
fromReferenceType->isPointer()
26982744
).get() == *fromReferenceType, "");
26992745

2700-
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
2746+
if (fromReferenceType->category() == Type::Category::ArraySlice)
2747+
solAssert(toReferenceType->category() == Type::Category::Array, "");
2748+
else
2749+
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
27012750
solAssert(_offset.value_or(0) == 0, "");
27022751

27032752
Whiskers templ(R"(
@@ -2715,6 +2764,17 @@ string YulUtilFunctions::updateStorageValueFunction(
27152764
dynamic_cast<ArrayType const&>(_fromType),
27162765
dynamic_cast<ArrayType const&>(_toType)
27172766
));
2767+
else if (_fromType.category() == Type::Category::ArraySlice)
2768+
{
2769+
solAssert(
2770+
_fromType.dataStoredIn(DataLocation::CallData),
2771+
"Currently only calldata array slices are supported!"
2772+
);
2773+
templ("copyToStorage", copyArrayToStorageFunction(
2774+
dynamic_cast<ArraySliceType const&>(_fromType).arrayType(),
2775+
dynamic_cast<ArrayType const&>(_toType)
2776+
));
2777+
}
27182778
else
27192779
templ("copyToStorage", copyStructToStorageFunction(
27202780
dynamic_cast<StructType const&>(_fromType),

libsolidity/codegen/YulUtilFunctions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ class YulUtilFunctions
8181
/// signature: (memPtr) ->
8282
std::string storeLiteralInMemoryFunction(std::string const& _literal);
8383

84+
/// @returns the name of a function that stores a string literal at a specific location in storage
85+
/// signature: (slot) ->
86+
std::string copyLiteralToStorageFunction(std::string const& _literal);
87+
8488
// @returns the name of a function that has the equivalent logic of an
8589
// `assert` or `require` call.
8690
std::string requireOrAssertFunction(bool _assert, Type const* _messageType = nullptr);

libsolidity/codegen/ir/IRGeneratorForStatements.cpp

Lines changed: 39 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -238,17 +238,14 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
238238

239239
_varDecl.value()->accept(*this);
240240

241-
Type const* rightIntermediateType = _varDecl.value()->annotation().type->closestTemporaryType(_varDecl.type());
242-
solAssert(rightIntermediateType, "");
243-
IRVariable value = convert(*_varDecl.value(), *rightIntermediateType);
244241
writeToLValue(
245242
_varDecl.immutable() ?
246243
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
247244
IRLValue{*_varDecl.annotation().type, IRLValue::Storage{
248245
util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
249246
m_context.storageLocationOfStateVariable(_varDecl).second
250247
}},
251-
value
248+
*_varDecl.value()
252249
);
253250
}
254251
catch (langutil::UnimplementedFeatureError const& _error)
@@ -407,55 +404,49 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
407404
assignmentOperator :
408405
TokenTraits::AssignmentToBinaryOp(assignmentOperator);
409406

410-
Type const* rightIntermediateType =
411-
TokenTraits::isShiftOp(binaryOperator) ?
412-
type(_assignment.rightHandSide()).mobileType() :
413-
type(_assignment.rightHandSide()).closestTemporaryType(
414-
&type(_assignment.leftHandSide())
415-
);
416-
solAssert(rightIntermediateType, "");
417-
IRVariable value = convert(_assignment.rightHandSide(), *rightIntermediateType);
407+
if (TokenTraits::isShiftOp(binaryOperator))
408+
solAssert(type(_assignment.rightHandSide()).mobileType(), "");
409+
IRVariable value =
410+
type(_assignment.leftHandSide()).isValueType() ?
411+
convert(
412+
_assignment.rightHandSide(),
413+
TokenTraits::isShiftOp(binaryOperator) ? *type(_assignment.rightHandSide()).mobileType() : type(_assignment)
414+
) :
415+
_assignment.rightHandSide();
416+
418417
_assignment.leftHandSide().accept(*this);
418+
419419
solAssert(!!m_currentLValue, "LValue not retrieved.");
420420
setLocation(_assignment);
421421

422422
if (assignmentOperator != Token::Assign)
423423
{
424424
solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types.");
425-
solAssert(rightIntermediateType->isValueType(), "Compound operators only available for value types.");
426-
IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
427425
solAssert(binaryOperator != Token::Exp, "");
428-
if (TokenTraits::isShiftOp(binaryOperator))
429-
{
430-
solAssert(type(_assignment) == leftIntermediate.type(), "");
431-
solAssert(type(_assignment) == type(_assignment.leftHandSide()), "");
432-
define(_assignment) << shiftOperation(binaryOperator, leftIntermediate, value) << "\n";
426+
solAssert(type(_assignment) == type(_assignment.leftHandSide()), "");
433427

434-
writeToLValue(*m_currentLValue, IRVariable(_assignment));
435-
m_currentLValue.reset();
436-
return false;
437-
}
438-
else
439-
{
440-
solAssert(type(_assignment.leftHandSide()) == *rightIntermediateType, "");
441-
m_code << value.name() << " := " << binaryOperation(
442-
binaryOperator,
443-
*rightIntermediateType,
444-
leftIntermediate.name(),
445-
value.name()
446-
);
447-
}
448-
}
428+
IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
429+
solAssert(type(_assignment) == leftIntermediate.type(), "");
449430

450-
writeToLValue(*m_currentLValue, value);
431+
define(_assignment) << (
432+
TokenTraits::isShiftOp(binaryOperator) ?
433+
shiftOperation(binaryOperator, leftIntermediate, value) :
434+
binaryOperation(binaryOperator, type(_assignment), leftIntermediate.name(), value.name())
435+
) << "\n";
451436

452-
if (dynamic_cast<ReferenceType const*>(&m_currentLValue->type))
453-
define(_assignment, readFromLValue(*m_currentLValue));
454-
else if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
455-
define(_assignment, value);
437+
writeToLValue(*m_currentLValue, IRVariable(_assignment));
438+
}
439+
else
440+
{
441+
writeToLValue(*m_currentLValue, value);
456442

457-
m_currentLValue.reset();
443+
if (dynamic_cast<ReferenceType const*>(&m_currentLValue->type))
444+
define(_assignment, readFromLValue(*m_currentLValue));
445+
else if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
446+
define(_assignment, value);
447+
}
458448

449+
m_currentLValue.reset();
459450
return false;
460451
}
461452

@@ -2857,10 +2848,17 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
28572848
prepared.commaSeparatedList() <<
28582849
")\n";
28592850
}
2851+
else if (auto const* literalType = dynamic_cast<StringLiteralType const*>(&_value.type()))
2852+
m_code <<
2853+
m_utils.writeToMemoryFunction(*TypeProvider::uint256()) <<
2854+
"(" <<
2855+
_memory.address <<
2856+
", " <<
2857+
m_utils.copyLiteralToMemoryFunction(literalType->value()) + "()" <<
2858+
")\n";
28602859
else
28612860
{
28622861
solAssert(_lvalue.type.sizeOnStack() == 1, "");
2863-
solAssert(dynamic_cast<ReferenceType const*>(&_lvalue.type), "");
28642862
auto const* valueReferenceType = dynamic_cast<ReferenceType const*>(&_value.type());
28652863
solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory), "");
28662864
m_code << "mstore(" + _memory.address + ", " + _value.part("mpos").name() + ")\n";

test/libsolidity/semanticTests/array/push/nested_bytes_push.sol

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ contract C {
44

55
function f() public {
66
a.push("abc");
7-
a.push("def");
7+
a.push("abcdefghabcdefghabcdefghabcdefgh");
8+
a.push("abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh");
89
assert(a[0][0] == "a");
9-
assert(a[1][0] == "d");
10+
assert(a[1][31] == "h");
11+
assert(a[2][32] == "a");
1012
}
1113
}
1214
// ====
1315
// compileViaYul: also
1416
// ----
1517
// f() ->
18+
// gas irOptimized: 181480
19+
// gas legacy: 180320
20+
// gas legacyOptimized: 180103
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
contract C {
2+
bytes public s = "abc";
3+
bytes public s1 = "abcd";
4+
function f() public {
5+
s = "abcd";
6+
s1 = "abc";
7+
}
8+
function g() public {
9+
(s, s1) = ("abc", "abcd");
10+
}
11+
}
12+
// ====
13+
// compileViaYul: also
14+
// ----
15+
// s() -> 0x20, 3, "abc"
16+
// s1() -> 0x20, 4, "abcd"
17+
// f() ->
18+
// s() -> 0x20, 4, "abcd"
19+
// s1() -> 0x20, 3, "abc"
20+
// g() ->
21+
// s() -> 0x20, 3, "abc"
22+
// s1() -> 0x20, 4, "abcd"

0 commit comments

Comments
 (0)