mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10768 from ethereum/copyLiteralToStorageSol2Yul
[Sol->Yul] Adding util function to copy literal to storage.
This commit is contained in:
commit
518629a8f6
@ -2613,23 +2613,6 @@ Type const* TupleType::mobileType() const
|
||||
return TypeProvider::tuple(move(mobiles));
|
||||
}
|
||||
|
||||
Type const* TupleType::closestTemporaryType(Type const* _targetType) const
|
||||
{
|
||||
solAssert(!!_targetType, "");
|
||||
TypePointers const& targetComponents = dynamic_cast<TupleType const&>(*_targetType).components();
|
||||
solAssert(components().size() == targetComponents.size(), "");
|
||||
TypePointers tempComponents(targetComponents.size());
|
||||
for (size_t i = 0; i < targetComponents.size(); ++i)
|
||||
{
|
||||
if (components()[i] && targetComponents[i])
|
||||
{
|
||||
tempComponents[i] = components()[i]->closestTemporaryType(targetComponents[i]);
|
||||
solAssert(tempComponents[i], "");
|
||||
}
|
||||
}
|
||||
return TypeProvider::tuple(move(tempComponents));
|
||||
}
|
||||
|
||||
FunctionType::FunctionType(FunctionDefinition const& _function, Kind _kind):
|
||||
m_kind(_kind),
|
||||
m_stateMutability(_function.stateMutability()),
|
||||
|
@ -324,13 +324,6 @@ public:
|
||||
/// @returns true if this is a non-value type and the data of this type is stored at the
|
||||
/// given location.
|
||||
virtual bool dataStoredIn(DataLocation) const { return false; }
|
||||
/// @returns the type of a temporary during assignment to a variable of the given type.
|
||||
/// Specifically, returns the requested itself if it can be dynamically allocated (or is a value type)
|
||||
/// and the mobile type otherwise.
|
||||
virtual Type const* closestTemporaryType(Type const* _targetType) const
|
||||
{
|
||||
return _targetType->dataStoredIn(DataLocation::Storage) ? mobileType() : _targetType;
|
||||
}
|
||||
|
||||
/// Returns the list of all members of this type. Default implementation: no members apart from bound.
|
||||
/// @param _currentScope scope in which the members are accessed.
|
||||
@ -1103,8 +1096,6 @@ public:
|
||||
u256 storageSize() const override;
|
||||
bool hasSimpleZeroValueInMemory() const override { return false; }
|
||||
Type const* mobileType() const override;
|
||||
/// Converts components to their temporary types and performs some wildcard matching.
|
||||
Type const* closestTemporaryType(Type const* _targetType) const override;
|
||||
|
||||
std::vector<Type const*> const& components() const { return m_components; }
|
||||
|
||||
|
@ -49,6 +49,34 @@ using namespace solidity::frontend;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::util;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
Type const* closestType(Type const* _type, Type const* _targetType, bool _isShiftOp)
|
||||
{
|
||||
if (_isShiftOp)
|
||||
return _type->mobileType();
|
||||
else if (auto const* tupleType = dynamic_cast<TupleType const*>(_type))
|
||||
{
|
||||
solAssert(_targetType, "");
|
||||
TypePointers const& targetComponents = dynamic_cast<TupleType const&>(*_targetType).components();
|
||||
solAssert(tupleType->components().size() == targetComponents.size(), "");
|
||||
TypePointers tempComponents(targetComponents.size());
|
||||
for (size_t i = 0; i < targetComponents.size(); ++i)
|
||||
{
|
||||
if (tupleType->components()[i] && targetComponents[i])
|
||||
{
|
||||
tempComponents[i] = closestType(tupleType->components()[i], targetComponents[i], _isShiftOp);
|
||||
solAssert(tempComponents[i], "");
|
||||
}
|
||||
}
|
||||
return TypeProvider::tuple(move(tempComponents));
|
||||
}
|
||||
else
|
||||
return _targetType->dataStoredIn(DataLocation::Storage) ? _type->mobileType() : _targetType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ExpressionCompiler::compile(Expression const& _expression)
|
||||
{
|
||||
@ -280,13 +308,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
|
||||
_assignment.rightHandSide().accept(*this);
|
||||
// Perform some conversion already. This will convert storage types to memory and literals
|
||||
// to their actual type, but will not convert e.g. memory to storage.
|
||||
Type const* rightIntermediateType;
|
||||
if (op != Token::Assign && TokenTraits::isShiftOp(binOp))
|
||||
rightIntermediateType = _assignment.rightHandSide().annotation().type->mobileType();
|
||||
else
|
||||
rightIntermediateType = _assignment.rightHandSide().annotation().type->closestTemporaryType(
|
||||
_assignment.leftHandSide().annotation().type
|
||||
);
|
||||
Type const* rightIntermediateType = closestType(
|
||||
_assignment.rightHandSide().annotation().type,
|
||||
_assignment.leftHandSide().annotation().type,
|
||||
op != Token::Assign && TokenTraits::isShiftOp(binOp)
|
||||
);
|
||||
|
||||
solAssert(rightIntermediateType, "");
|
||||
utils().convertType(*_assignment.rightHandSide().annotation().type, *rightIntermediateType, cleanupNeeded);
|
||||
|
||||
@ -1016,7 +1043,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
|
||||
// stack: argValue storageSlot slotOffset
|
||||
utils().moveToStackTop(2, argType->sizeOnStack());
|
||||
// stack: storageSlot slotOffset argValue
|
||||
Type const* type = arguments[0]->annotation().type->closestTemporaryType(arrayType->baseType());
|
||||
Type const* type =
|
||||
arrayType->baseType()->dataStoredIn(DataLocation::Storage) ?
|
||||
arguments[0]->annotation().type->mobileType() :
|
||||
arrayType->baseType();
|
||||
solAssert(type, "");
|
||||
utils().convertType(*argType, *type);
|
||||
utils().moveToStackTop(1 + type->sizeOnStack());
|
||||
|
@ -152,6 +152,54 @@ string YulUtilFunctions::storeLiteralInMemoryFunction(string const& _literal)
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::copyLiteralToStorageFunction(string const& _literal)
|
||||
{
|
||||
string functionName = "copy_literal_to_storage_" + util::toHex(util::keccak256(_literal).asBytes());
|
||||
|
||||
return m_functionCollector.createFunction(functionName, [&](vector<string>& _args, vector<string>&) {
|
||||
_args = {"slot"};
|
||||
|
||||
if (_literal.size() >= 32)
|
||||
{
|
||||
size_t words = (_literal.length() + 31) / 32;
|
||||
vector<map<string, string>> wordParams(words);
|
||||
for (size_t i = 0; i < words; ++i)
|
||||
{
|
||||
wordParams[i]["offset"] = to_string(i);
|
||||
wordParams[i]["wordValue"] = formatAsStringOrNumber(_literal.substr(32 * i, 32));
|
||||
}
|
||||
return Whiskers(R"(
|
||||
let oldLen := <byteArrayLength>(sload(slot))
|
||||
<cleanUpArrayEnd>(slot, oldLen, <length>)
|
||||
sstore(slot, <encodedLen>)
|
||||
let dstPtr := <dataArea>(slot)
|
||||
<#word>
|
||||
sstore(add(dstPtr, <offset>), <wordValue>)
|
||||
</word>
|
||||
)")
|
||||
("byteArrayLength", extractByteArrayLengthFunction())
|
||||
("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage()))
|
||||
("dataArea", arrayDataAreaFunction(*TypeProvider::bytesStorage()))
|
||||
("word", wordParams)
|
||||
("length", to_string(_literal.size()))
|
||||
("encodedLen", to_string(2 * _literal.size() + 1))
|
||||
.render();
|
||||
}
|
||||
else
|
||||
return Whiskers(R"(
|
||||
let oldLen := <byteArrayLength>(sload(slot))
|
||||
<cleanUpArrayEnd>(slot, oldLen, <length>)
|
||||
sstore(slot, add(<wordValue>, <encodedLen>))
|
||||
)")
|
||||
("byteArrayLength", extractByteArrayLengthFunction())
|
||||
("cleanUpArrayEnd", cleanUpDynamicByteArrayEndSlotsFunction(*TypeProvider::bytesStorage()))
|
||||
("wordValue", formatAsStringOrNumber(_literal))
|
||||
("length", to_string(_literal.size()))
|
||||
("encodedLen", to_string(2 * _literal.size()))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::requireOrAssertFunction(bool _assert, Type const* _messageType)
|
||||
{
|
||||
string functionName =
|
||||
@ -2693,15 +2741,13 @@ string YulUtilFunctions::updateStorageValueFunction(
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot<?dynamicOffset>, offset</dynamicOffset>) {
|
||||
<?dynamicOffset>if offset { <panic>() }</dynamicOffset>
|
||||
let value := <copyLiteralToMemory>()
|
||||
<copyToStorage>(slot, value)
|
||||
<copyToStorage>(slot)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("dynamicOffset", !_offset.has_value())
|
||||
("panic", panicFunction(PanicCode::Generic))
|
||||
("copyLiteralToMemory", copyLiteralToMemoryFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()))
|
||||
("copyToStorage", copyArrayToStorageFunction(*TypeProvider::bytesMemory(), toArrayType))
|
||||
("copyToStorage", copyLiteralToStorageFunction(dynamic_cast<StringLiteralType const&>(_fromType).value()))
|
||||
.render();
|
||||
}
|
||||
|
||||
@ -2710,7 +2756,10 @@ string YulUtilFunctions::updateStorageValueFunction(
|
||||
fromReferenceType->isPointer()
|
||||
).get() == *fromReferenceType, "");
|
||||
|
||||
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
|
||||
if (fromReferenceType->category() == Type::Category::ArraySlice)
|
||||
solAssert(toReferenceType->category() == Type::Category::Array, "");
|
||||
else
|
||||
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
|
||||
solAssert(_offset.value_or(0) == 0, "");
|
||||
|
||||
Whiskers templ(R"(
|
||||
@ -2728,6 +2777,17 @@ string YulUtilFunctions::updateStorageValueFunction(
|
||||
dynamic_cast<ArrayType const&>(_fromType),
|
||||
dynamic_cast<ArrayType const&>(_toType)
|
||||
));
|
||||
else if (_fromType.category() == Type::Category::ArraySlice)
|
||||
{
|
||||
solAssert(
|
||||
_fromType.dataStoredIn(DataLocation::CallData),
|
||||
"Currently only calldata array slices are supported!"
|
||||
);
|
||||
templ("copyToStorage", copyArrayToStorageFunction(
|
||||
dynamic_cast<ArraySliceType const&>(_fromType).arrayType(),
|
||||
dynamic_cast<ArrayType const&>(_toType)
|
||||
));
|
||||
}
|
||||
else
|
||||
templ("copyToStorage", copyStructToStorageFunction(
|
||||
dynamic_cast<StructType const&>(_fromType),
|
||||
|
@ -81,6 +81,10 @@ public:
|
||||
/// signature: (memPtr) ->
|
||||
std::string storeLiteralInMemoryFunction(std::string const& _literal);
|
||||
|
||||
/// @returns the name of a function that stores a string literal at a specific location in storage
|
||||
/// signature: (slot) ->
|
||||
std::string copyLiteralToStorageFunction(std::string const& _literal);
|
||||
|
||||
// @returns the name of a function that has the equivalent logic of an
|
||||
// `assert` or `require` call.
|
||||
std::string requireOrAssertFunction(bool _assert, Type const* _messageType = nullptr);
|
||||
|
@ -238,9 +238,6 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
|
||||
|
||||
_varDecl.value()->accept(*this);
|
||||
|
||||
Type const* rightIntermediateType = _varDecl.value()->annotation().type->closestTemporaryType(_varDecl.type());
|
||||
solAssert(rightIntermediateType, "");
|
||||
IRVariable value = convert(*_varDecl.value(), *rightIntermediateType);
|
||||
writeToLValue(
|
||||
_varDecl.immutable() ?
|
||||
IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} :
|
||||
@ -248,7 +245,7 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
|
||||
util::toCompactHexWithPrefix(m_context.storageLocationOfStateVariable(_varDecl).first),
|
||||
m_context.storageLocationOfStateVariable(_varDecl).second
|
||||
}},
|
||||
value
|
||||
*_varDecl.value()
|
||||
);
|
||||
}
|
||||
catch (langutil::UnimplementedFeatureError const& _error)
|
||||
@ -407,55 +404,49 @@ bool IRGeneratorForStatements::visit(Assignment const& _assignment)
|
||||
assignmentOperator :
|
||||
TokenTraits::AssignmentToBinaryOp(assignmentOperator);
|
||||
|
||||
Type const* rightIntermediateType =
|
||||
TokenTraits::isShiftOp(binaryOperator) ?
|
||||
type(_assignment.rightHandSide()).mobileType() :
|
||||
type(_assignment.rightHandSide()).closestTemporaryType(
|
||||
&type(_assignment.leftHandSide())
|
||||
);
|
||||
solAssert(rightIntermediateType, "");
|
||||
IRVariable value = convert(_assignment.rightHandSide(), *rightIntermediateType);
|
||||
if (TokenTraits::isShiftOp(binaryOperator))
|
||||
solAssert(type(_assignment.rightHandSide()).mobileType(), "");
|
||||
IRVariable value =
|
||||
type(_assignment.leftHandSide()).isValueType() ?
|
||||
convert(
|
||||
_assignment.rightHandSide(),
|
||||
TokenTraits::isShiftOp(binaryOperator) ? *type(_assignment.rightHandSide()).mobileType() : type(_assignment)
|
||||
) :
|
||||
_assignment.rightHandSide();
|
||||
|
||||
_assignment.leftHandSide().accept(*this);
|
||||
|
||||
solAssert(!!m_currentLValue, "LValue not retrieved.");
|
||||
setLocation(_assignment);
|
||||
|
||||
if (assignmentOperator != Token::Assign)
|
||||
{
|
||||
solAssert(type(_assignment.leftHandSide()).isValueType(), "Compound operators only available for value types.");
|
||||
solAssert(rightIntermediateType->isValueType(), "Compound operators only available for value types.");
|
||||
IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
|
||||
solAssert(binaryOperator != Token::Exp, "");
|
||||
if (TokenTraits::isShiftOp(binaryOperator))
|
||||
{
|
||||
solAssert(type(_assignment) == leftIntermediate.type(), "");
|
||||
solAssert(type(_assignment) == type(_assignment.leftHandSide()), "");
|
||||
define(_assignment) << shiftOperation(binaryOperator, leftIntermediate, value) << "\n";
|
||||
solAssert(type(_assignment) == type(_assignment.leftHandSide()), "");
|
||||
|
||||
writeToLValue(*m_currentLValue, IRVariable(_assignment));
|
||||
m_currentLValue.reset();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(type(_assignment.leftHandSide()) == *rightIntermediateType, "");
|
||||
m_code << value.name() << " := " << binaryOperation(
|
||||
binaryOperator,
|
||||
*rightIntermediateType,
|
||||
leftIntermediate.name(),
|
||||
value.name()
|
||||
);
|
||||
}
|
||||
IRVariable leftIntermediate = readFromLValue(*m_currentLValue);
|
||||
solAssert(type(_assignment) == leftIntermediate.type(), "");
|
||||
|
||||
define(_assignment) << (
|
||||
TokenTraits::isShiftOp(binaryOperator) ?
|
||||
shiftOperation(binaryOperator, leftIntermediate, value) :
|
||||
binaryOperation(binaryOperator, type(_assignment), leftIntermediate.name(), value.name())
|
||||
) << "\n";
|
||||
|
||||
writeToLValue(*m_currentLValue, IRVariable(_assignment));
|
||||
}
|
||||
else
|
||||
{
|
||||
writeToLValue(*m_currentLValue, value);
|
||||
|
||||
if (dynamic_cast<ReferenceType const*>(&m_currentLValue->type))
|
||||
define(_assignment, readFromLValue(*m_currentLValue));
|
||||
else if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
|
||||
define(_assignment, value);
|
||||
}
|
||||
|
||||
writeToLValue(*m_currentLValue, value);
|
||||
|
||||
if (dynamic_cast<ReferenceType const*>(&m_currentLValue->type))
|
||||
define(_assignment, readFromLValue(*m_currentLValue));
|
||||
else if (*_assignment.annotation().type != *TypeProvider::emptyTuple())
|
||||
define(_assignment, value);
|
||||
|
||||
m_currentLValue.reset();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2857,10 +2848,17 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable
|
||||
prepared.commaSeparatedList() <<
|
||||
")\n";
|
||||
}
|
||||
else if (auto const* literalType = dynamic_cast<StringLiteralType const*>(&_value.type()))
|
||||
m_code <<
|
||||
m_utils.writeToMemoryFunction(*TypeProvider::uint256()) <<
|
||||
"(" <<
|
||||
_memory.address <<
|
||||
", " <<
|
||||
m_utils.copyLiteralToMemoryFunction(literalType->value()) + "()" <<
|
||||
")\n";
|
||||
else
|
||||
{
|
||||
solAssert(_lvalue.type.sizeOnStack() == 1, "");
|
||||
solAssert(dynamic_cast<ReferenceType const*>(&_lvalue.type), "");
|
||||
auto const* valueReferenceType = dynamic_cast<ReferenceType const*>(&_value.type());
|
||||
solAssert(valueReferenceType && valueReferenceType->dataStoredIn(DataLocation::Memory), "");
|
||||
m_code << "mstore(" + _memory.address + ", " + _value.part("mpos").name() + ")\n";
|
||||
|
@ -37,7 +37,7 @@ contract C {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0x40, 0x80, 6, 0x6162636465660000000000000000000000000000000000000000000000000000, 0x49, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738390000000000000000000000000000000000000000000000
|
||||
// gas irOptimized: 172282
|
||||
// gas irOptimized: 172274
|
||||
// gas legacy: 174794
|
||||
// gas legacyOptimized: 174188
|
||||
// g() -> 0x40, 0xc0, 0x49, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738393031323334353637383930313233343536373839303120, 0x3132333435363738390000000000000000000000000000000000000000000000, 0x11, 0x3132333435363738393233343536373839000000000000000000000000000000
|
||||
|
@ -48,6 +48,6 @@ contract C {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0xff
|
||||
// gas irOptimized: 136027
|
||||
// gas irOptimized: 132909
|
||||
// gas legacy: 137645
|
||||
// gas legacyOptimized: 134376
|
||||
|
@ -13,6 +13,6 @@ contract C {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() -> 0x20, 0x02, 0x40, 0x80, 3, 0x6162630000000000000000000000000000000000000000000000000000000000, 0x99, 44048183304486788312148433451363384677562265908331949128489393215789685032262, 32241931068525137014058842823026578386641954854143559838526554899205067598957, 49951309422467613961193228765530489307475214998374779756599339590522149884499, 0x54555658595a6162636465666768696a6b6c6d6e6f707172737475767778797a, 0x4142434445464748494a4b4c4d4e4f5051525354555658595a00000000000000
|
||||
// gas irOptimized: 198253
|
||||
// gas irOptimized: 197063
|
||||
// gas legacy: 199159
|
||||
// gas legacyOptimized: 198137
|
||||
|
@ -4,12 +4,17 @@ contract C {
|
||||
|
||||
function f() public {
|
||||
a.push("abc");
|
||||
a.push("def");
|
||||
a.push("abcdefghabcdefghabcdefghabcdefgh");
|
||||
a.push("abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh");
|
||||
assert(a[0][0] == "a");
|
||||
assert(a[1][0] == "d");
|
||||
assert(a[1][31] == "h");
|
||||
assert(a[2][32] == "a");
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// f() ->
|
||||
// gas irOptimized: 178367
|
||||
// gas legacy: 180320
|
||||
// gas legacyOptimized: 180103
|
||||
|
@ -0,0 +1,22 @@
|
||||
contract C {
|
||||
bytes public s = "abc";
|
||||
bytes public s1 = "abcd";
|
||||
function f() public {
|
||||
s = "abcd";
|
||||
s1 = "abc";
|
||||
}
|
||||
function g() public {
|
||||
(s, s1) = ("abc", "abcd");
|
||||
}
|
||||
}
|
||||
// ====
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// s() -> 0x20, 3, "abc"
|
||||
// s1() -> 0x20, 4, "abcd"
|
||||
// f() ->
|
||||
// s() -> 0x20, 4, "abcd"
|
||||
// s1() -> 0x20, 3, "abc"
|
||||
// g() ->
|
||||
// s() -> 0x20, 3, "abc"
|
||||
// s1() -> 0x20, 4, "abcd"
|
@ -35,10 +35,10 @@ contract test {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// constructor()
|
||||
// gas irOptimized: 590192
|
||||
// gas irOptimized: 591094
|
||||
// gas legacy: 733634
|
||||
// gas legacyOptimized: 498033
|
||||
// prb_pi() -> 3141592656369545286
|
||||
// gas irOptimized: 66098
|
||||
// gas irOptimized: 66108
|
||||
// gas legacy: 98903
|
||||
// gas legacyOptimized: 75735
|
||||
|
@ -22,6 +22,6 @@ contract C {
|
||||
// compileViaYul: also
|
||||
// ----
|
||||
// g() -> 2, 6
|
||||
// gas irOptimized: 169790
|
||||
// gas irOptimized: 169460
|
||||
// gas legacy: 172490
|
||||
// gas legacyOptimized: 171209
|
||||
|
Loading…
Reference in New Issue
Block a user