Merge pull request #10768 from ethereum/copyLiteralToStorageSol2Yul

[Sol->Yul] Adding util function to copy literal to storage.
This commit is contained in:
chriseth 2021-05-06 10:44:39 +02:00 committed by GitHub
commit 518629a8f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 182 additions and 89 deletions

View File

@ -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()),

View File

@ -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; }

View File

@ -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());

View File

@ -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,6 +2756,9 @@ string YulUtilFunctions::updateStorageValueFunction(
fromReferenceType->isPointer()
).get() == *fromReferenceType, "");
if (fromReferenceType->category() == Type::Category::ArraySlice)
solAssert(toReferenceType->category() == Type::Category::Array, "");
else
solAssert(toReferenceType->category() == fromReferenceType->category(), "");
solAssert(_offset.value_or(0) == 0, "");
@ -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),

View File

@ -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);

View File

@ -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";
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));
m_currentLValue.reset();
return false;
}
else
{
solAssert(type(_assignment.leftHandSide()) == *rightIntermediateType, "");
m_code << value.name() << " := " << binaryOperation(
binaryOperator,
*rightIntermediateType,
leftIntermediate.name(),
value.name()
);
}
}
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";

View File

@ -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

View File

@ -48,6 +48,6 @@ contract C {
// compileViaYul: also
// ----
// f() -> 0xff
// gas irOptimized: 136027
// gas irOptimized: 132909
// gas legacy: 137645
// gas legacyOptimized: 134376

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -22,6 +22,6 @@ contract C {
// compileViaYul: also
// ----
// g() -> 2, 6
// gas irOptimized: 169790
// gas irOptimized: 169460
// gas legacy: 172490
// gas legacyOptimized: 171209