[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>
This commit is contained in:
Djordje Mijovic 2021-05-05 08:57:19 +02:00
parent 9d156b52c4
commit f0c5cdca9f
8 changed files with 176 additions and 83 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 =
@ -2680,15 +2728,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();
}
@ -2697,7 +2743,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"(
@ -2715,6 +2764,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";
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";

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: 181480
// 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"