Merge pull request #6909 from ethereum/yul-delete-operation

[Sol->Yul] Make IRStorageItem work with dynamic offsets
This commit is contained in:
Daniel Kirchner 2019-06-12 16:46:00 +02:00 committed by GitHub
commit 2849169bff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 260 additions and 67 deletions

View File

@ -224,34 +224,45 @@ string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
solAssert(_numBits < 256, ""); solAssert(_numBits < 256, "");
string functionName = "shift_left_" + to_string(_numBits); string functionName = "shift_left_" + to_string(_numBits);
if (m_evmVersion.hasBitwiseShifting()) return m_functionCollector->createFunction(functionName, [&]() {
{ return
return m_functionCollector->createFunction(functionName, [&]() { Whiskers(R"(
return function <functionName>(value) -> newValue {
Whiskers(R"( newValue :=
function <functionName>(value) -> newValue { <?hasShifts>
newValue := shl(<numBits>, value) shl(<numBits>, value)
} <!hasShifts>
)") mul(value, <multiplier>)
("functionName", functionName) </hasShifts>
("numBits", to_string(_numBits)) }
.render(); )")
}); ("functionName", functionName)
} ("numBits", to_string(_numBits))
else ("hasShifts", m_evmVersion.hasBitwiseShifting())
{ ("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
return m_functionCollector->createFunction(functionName, [&]() { .render();
return });
Whiskers(R"( }
function <functionName>(value) -> newValue {
newValue := mul(value, <multiplier>) string YulUtilFunctions::dynamicShiftLeftFunction()
} {
)") string functionName = "shift_left";
("functionName", functionName) return m_functionCollector->createFunction(functionName, [&]() {
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits)) return
.render(); Whiskers(R"(
}); function <functionName>(bits, value) -> newValue {
} newValue :=
<?hasShifts>
shl(bits, value)
<!hasShifts>
mul(value, exp(2, bits))
</hasShifts>
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
} }
string YulUtilFunctions::shiftRightFunction(size_t _numBits) string YulUtilFunctions::shiftRightFunction(size_t _numBits)
@ -261,7 +272,7 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
// Note that if this is extended with signed shifts, // Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding! // the opcodes SAR and SDIV behave differently with regards to rounding!
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned_" + m_evmVersion.name(); string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
return m_functionCollector->createFunction(functionName, [&]() { return m_functionCollector->createFunction(functionName, [&]() {
return return
Whiskers(R"( Whiskers(R"(
@ -282,6 +293,30 @@ string YulUtilFunctions::shiftRightFunction(size_t _numBits)
}); });
} }
string YulUtilFunctions::dynamicShiftRightFunction()
{
// Note that if this is extended with signed shifts,
// the opcodes SAR and SDIV behave differently with regards to rounding!
string const functionName = "shift_right_unsigned";
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(bits, value) -> newValue {
newValue :=
<?hasShifts>
shr(bits, value)
<!hasShifts>
div(value, exp(2, bits))
</hasShifts>
}
)")
("functionName", functionName)
("hasShifts", m_evmVersion.hasBitwiseShifting())
.render();
});
}
string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes) string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes)
{ {
solAssert(_numBytes <= 32, ""); solAssert(_numBytes <= 32, "");
@ -306,6 +341,29 @@ string YulUtilFunctions::updateByteSliceFunction(size_t _numBytes, size_t _shift
}); });
} }
string YulUtilFunctions::dynamicUpdateByteSliceFunction(size_t _numBytes)
{
solAssert(_numBytes <= 32, "");
size_t numBits = _numBytes * 8;
string functionName = "update_byte_slice_" + to_string(_numBytes);
return m_functionCollector->createFunction(functionName, [&]() {
return
Whiskers(R"(
function <functionName>(value, shiftBytes, toInsert) -> result {
let shiftBits := mul(shiftBytes, 8)
let mask := <shl>(shiftBits, <mask>)
toInsert := <shl>(shiftBits, toInsert)
value := and(value, not(mask))
result := or(value, and(toInsert, mask))
}
)")
("functionName", functionName)
("mask", formatNumber((bigint(1) << numBits) - 1))
("shl", dynamicShiftLeftFunction())
.render();
});
}
string YulUtilFunctions::roundUpFunction() string YulUtilFunctions::roundUpFunction()
{ {
string functionName = "round_up_to_mul_of_32"; string functionName = "round_up_to_mul_of_32";
@ -613,6 +671,89 @@ string YulUtilFunctions::readFromStorage(Type const& _type, size_t _offset, bool
}); });
} }
string YulUtilFunctions::dynamicReadFromStorage(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"read_from_storage_" +
string(_splitFunctionTypes ? "split_" : "") +
"_" +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
solAssert(_type.sizeOnStack() == 1, "");
return Whiskers(R"(
function <functionName>(slot, offset) -> value {
value := <extract>(sload(slot), offset)
}
)")
("functionName", functionName)
("extract", dynamicExtractFromStorageValue(_type, false))
.render();
});
}
string YulUtilFunctions::updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset)
{
string const functionName =
"update_storage_value_" +
(_offset.is_initialized() ? ("offset_" + to_string(*_offset)) : "") +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
if (_type.isValueType())
{
solAssert(_type.storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(_type.storageBytes() > 0, "Invalid storage bytes size.");
return Whiskers(R"(
function <functionName>(slot, <offset>value) {
sstore(slot, <update>(sload(slot), <offset><prepare>(value)))
}
)")
("functionName", functionName)
("update",
_offset.is_initialized() ?
updateByteSliceFunction(_type.storageBytes(), *_offset) :
dynamicUpdateByteSliceFunction(_type.storageBytes())
)
("offset", _offset.is_initialized() ? "" : "offset, ")
("prepare", prepareStoreFunction(_type))
.render();
}
else
{
if (_type.category() == Type::Category::Array)
solUnimplementedAssert(false, "");
else if (_type.category() == Type::Category::Struct)
solUnimplementedAssert(false, "");
else
solAssert(false, "Invalid non-value type for assignment.");
}
});
}
string YulUtilFunctions::dynamicExtractFromStorageValue(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(!_splitFunctionTypes, "");
string functionName =
"extract_from_storage_value_" +
string(_splitFunctionTypes ? "split_" : "") +
_type.identifier();
return m_functionCollector->createFunction(functionName, [&] {
return Whiskers(R"(
function <functionName>(slot_value, offset) -> value {
value := <cleanupStorage>(<shr>(mul(offset, 8), slot_value))
}
)")
("functionName", functionName)
("shr", dynamicShiftRightFunction())
("cleanupStorage", cleanupFromStorageFunction(_type, false))
.render();
});
}
string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes) string YulUtilFunctions::extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes)
{ {
solUnimplementedAssert(!_splitFunctionTypes, ""); solUnimplementedAssert(!_splitFunctionTypes, "");

View File

@ -74,13 +74,18 @@ public:
std::string leftAlignFunction(Type const& _type); std::string leftAlignFunction(Type const& _type);
std::string shiftLeftFunction(size_t _numBits); std::string shiftLeftFunction(size_t _numBits);
std::string dynamicShiftLeftFunction();
std::string shiftRightFunction(size_t _numBits); std::string shiftRightFunction(size_t _numBits);
std::string dynamicShiftRightFunction();
/// @returns the name of a function f(value, toInsert) -> newValue which replaces the /// @returns the name of a function f(value, toInsert) -> newValue which replaces the
/// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant /// _numBytes bytes starting at byte position _shiftBytes (counted from the least significant
/// byte) by the _numBytes least significant bytes of `toInsert`. /// byte) by the _numBytes least significant bytes of `toInsert`.
std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes); std::string updateByteSliceFunction(size_t _numBytes, size_t _shiftBytes);
/// signature: (value, shiftBytes, toInsert) -> result
std::string dynamicUpdateByteSliceFunction(size_t _numBytes);
/// @returns the name of a function that rounds its input to the next multiple /// @returns the name of a function that rounds its input to the next multiple
/// of 32 or the input if it is a multiple of 32. /// of 32 or the input if it is a multiple of 32.
std::string roundUpFunction(); std::string roundUpFunction();
@ -121,6 +126,7 @@ public:
/// @param _splitFunctionTypes if false, returns the address and function signature in a /// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable. /// single variable.
std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes); std::string readFromStorage(Type const& _type, size_t _offset, bool _splitFunctionTypes);
std::string dynamicReadFromStorage(Type const& _type, bool _splitFunctionTypes);
/// @returns a function that extracts a value type from storage slot that has been /// @returns a function that extracts a value type from storage slot that has been
/// retrieved already. /// retrieved already.
@ -128,6 +134,13 @@ public:
/// @param _splitFunctionTypes if false, returns the address and function signature in a /// @param _splitFunctionTypes if false, returns the address and function signature in a
/// single variable. /// single variable.
std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes); std::string extractFromStorageValue(Type const& _type, size_t _offset, bool _splitFunctionTypes);
std::string dynamicExtractFromStorageValue(Type const& _type, bool _splitFunctionTypes);
/// Returns the name of a function will write the given value to
/// the specified slot and offset. If offset is not given, it is expected as
/// runtime parameter.
/// signature: (slot, [offset,] value)
std::string updateStorageValueFunction(Type const& _type, boost::optional<unsigned> const _offset = boost::optional<unsigned>());
/// Performs cleanup after reading from a potentially compressed storage slot. /// Performs cleanup after reading from a potentially compressed storage slot.
/// The function does not perform any validation, it just masks or sign-extends /// The function does not perform any validation, it just masks or sign-extends

View File

@ -54,26 +54,37 @@ string IRLocalVariable::setToZero() const
IRStorageItem::IRStorageItem( IRStorageItem::IRStorageItem(
IRGenerationContext& _context, IRGenerationContext& _context,
VariableDeclaration const& _varDecl VariableDeclaration const& _varDecl
): )
IRLValue(_context, _varDecl.annotation().type) :IRStorageItem(
_context,
*_varDecl.annotation().type,
_context.storageLocationOfVariable(_varDecl)
)
{ }
IRStorageItem::IRStorageItem(
IRGenerationContext& _context,
Type const& _type,
std::pair<u256, unsigned> slot_offset
)
: IRLValue(_context, &_type),
m_slot(toCompactHexWithPrefix(slot_offset.first)),
m_offset(slot_offset.second)
{ {
u256 slot;
unsigned offset;
std::tie(slot, offset) = _context.storageLocationOfVariable(_varDecl);
m_slot = toCompactHexWithPrefix(slot);
m_offset = offset;
} }
IRStorageItem::IRStorageItem( IRStorageItem::IRStorageItem(
IRGenerationContext& _context, IRGenerationContext& _context,
string _slot, string _slot,
unsigned _offset, boost::variant<string, unsigned> _offset,
Type const& _type Type const& _type
): ):
IRLValue(_context, &_type), IRLValue(_context, &_type),
m_slot(move(_slot)), m_slot(move(_slot)),
m_offset(_offset) m_offset(std::move(_offset))
{ {
solAssert(!m_offset.empty(), "");
solAssert(!m_slot.empty(), "");
} }
string IRStorageItem::retrieveValue() const string IRStorageItem::retrieveValue() const
@ -81,39 +92,45 @@ string IRStorageItem::retrieveValue() const
if (!m_type->isValueType()) if (!m_type->isValueType())
return m_slot; return m_slot;
solUnimplementedAssert(m_type->category() != Type::Category::Function, ""); solUnimplementedAssert(m_type->category() != Type::Category::Function, "");
return m_context.utils().readFromStorage(*m_type, m_offset, false) + "(" + m_slot + ")"; if (m_offset.type() == typeid(string))
return
m_context.utils().dynamicReadFromStorage(*m_type, false) +
"(" +
m_slot +
", " +
boost::get<string>(m_offset) +
")";
else if (m_offset.type() == typeid(unsigned))
return
m_context.utils().readFromStorage(*m_type, boost::get<unsigned>(m_offset), false) +
"(" +
m_slot +
")";
solAssert(false, "");
} }
string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const string IRStorageItem::storeValue(string const& _value, Type const& _sourceType) const
{ {
if (m_type->isValueType()) if (m_type->isValueType())
{
solAssert(m_type->storageBytes() <= 32, "Invalid storage bytes size.");
solAssert(m_type->storageBytes() > 0, "Invalid storage bytes size.");
solAssert(m_type->storageBytes() + m_offset <= 32, "");
solAssert(_sourceType == *m_type, "Different type, but might not be an error."); solAssert(_sourceType == *m_type, "Different type, but might not be an error.");
return Whiskers("sstore(<slot>, <update>(sload(<slot>), <prepare>(<value>)))\n") boost::optional<unsigned> offset;
("slot", m_slot)
("update", m_context.utils().updateByteSliceFunction(m_type->storageBytes(), m_offset)) if (m_offset.type() == typeid(unsigned))
("prepare", m_context.utils().prepareStoreFunction(*m_type)) offset = get<unsigned>(m_offset);
("value", _value)
.render(); return
} m_context.utils().updateStorageValueFunction(*m_type, offset) +
else "(" +
{ m_slot +
solAssert( (m_offset.type() == typeid(string) ?
_sourceType.category() == m_type->category(), (", " + get<string>(m_offset)) :
"Wrong type conversation for assignment." ""
); ) +
if (m_type->category() == Type::Category::Array) ", " +
solUnimplementedAssert(false, ""); _value +
else if (m_type->category() == Type::Category::Struct) ")\n";
solUnimplementedAssert(false, "");
else
solAssert(false, "Invalid non-value type for assignment.");
}
} }
string IRStorageItem::setToZero() const string IRStorageItem::setToZero() const

View File

@ -20,8 +20,11 @@
#pragma once #pragma once
#include <libdevcore/Common.h>
#include <string> #include <string>
#include <ostream> #include <ostream>
#include <boost/variant.hpp>
namespace dev namespace dev
{ {
@ -83,7 +86,7 @@ public:
IRStorageItem( IRStorageItem(
IRGenerationContext& _context, IRGenerationContext& _context,
std::string _slot, std::string _slot,
unsigned _offset, boost::variant<std::string, unsigned> _offset,
Type const& _type Type const& _type
); );
std::string retrieveValue() const override; std::string retrieveValue() const override;
@ -91,8 +94,14 @@ public:
std::string setToZero() const override; std::string setToZero() const override;
private: private:
std::string m_slot; IRStorageItem(
unsigned m_offset; IRGenerationContext& _context,
Type const& _type,
std::pair<u256, unsigned> slot_offset
);
std::string const m_slot;
boost::variant<std::string, unsigned> const m_offset;
}; };

View File

@ -9,5 +9,7 @@ contract C {
} }
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f() -> 6 // f() -> 6

View File

@ -9,5 +9,7 @@ contract C {
} }
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f() -> 5 // f() -> 5

View File

@ -13,6 +13,8 @@ contract C {
} }
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f(uint256): 0 -> 2 // f(uint256): 0 -> 2
// f(uint256): 1 -> 18 // f(uint256): 1 -> 18

View File

@ -24,6 +24,8 @@ contract C {
return 500; return 500;
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// call(uint256): 0 -> 0 // call(uint256): 0 -> 0
// call(uint256): 1 -> 1 // call(uint256): 1 -> 1

View File

@ -4,6 +4,7 @@ contract C {
} }
} }
// ==== // ====
// compileViaYul: also
// EVMVersion: >=constantinople // EVMVersion: >=constantinople
// ---- // ----
// f(uint256): 7 -> 28 // f(uint256): 7 -> 28

View File

@ -3,6 +3,8 @@ contract C {
return a + b + c + d + e; return a + b + c + d + e;
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1 // f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1
// -> 5 // -> 5

View File

@ -3,6 +3,8 @@ contract C {
return a + b + c + d + e; return a + b + c + d + e;
} }
} }
// ====
// compileViaYul: also
// ---- // ----
// f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1 // f(uint256,uint256,uint256,uint256,uint256): 1, 1, 1, 1, 1
// # A comment on the function parameters. # // # A comment on the function parameters. #