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
13 changed files with 182 additions and 89 deletions
+38 -8
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());
+65 -5
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,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),
+4
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);
@@ -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";