mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #6195 from ethereum/extractYulUtils
[REF] Extract utility functions from ABI encoder.
This commit is contained in:
commit
4d8c57006b
@ -63,6 +63,10 @@ set(sources
|
||||
codegen/ExpressionCompiler.h
|
||||
codegen/LValue.cpp
|
||||
codegen/LValue.h
|
||||
codegen/MultiUseYulFunctionCollector.h
|
||||
codegen/MultiUseYulFunctionCollector.cpp
|
||||
codegen/YulUtilFunctions.h
|
||||
codegen/YulUtilFunctions.cpp
|
||||
formal/SMTChecker.cpp
|
||||
formal/SMTChecker.h
|
||||
formal/SMTLib2Interface.cpp
|
||||
|
@ -22,7 +22,6 @@
|
||||
|
||||
#include <libsolidity/codegen/ABIFunctions.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libdevcore/Whiskers.h>
|
||||
|
||||
@ -247,11 +246,9 @@ string ABIFunctions::tupleDecoder(TypePointers const& _types, bool _fromMemory)
|
||||
|
||||
pair<string, set<string>> ABIFunctions::requestedFunctions()
|
||||
{
|
||||
string result;
|
||||
for (auto const& f: m_requestedFunctions)
|
||||
result += f.second;
|
||||
m_requestedFunctions.clear();
|
||||
return make_pair(result, std::move(m_externallyUsedFunctions));
|
||||
std::set<string> empty;
|
||||
swap(empty, m_externallyUsedFunctions);
|
||||
return make_pair(m_functionCollector->requestedFunctions(), std::move(empty));
|
||||
}
|
||||
|
||||
string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
||||
@ -268,6 +265,7 @@ string ABIFunctions::EncodingOptions::toFunctionNameSuffix() const
|
||||
return suffix;
|
||||
}
|
||||
|
||||
|
||||
string ABIFunctions::cleanupFunction(Type const& _type, bool _revertOnFailure)
|
||||
{
|
||||
string functionName = string("cleanup_") + (_revertOnFailure ? "revert_" : "assert_") + _type.identifier();
|
||||
@ -398,7 +396,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
FixedBytesType const& toBytesType = dynamic_cast<FixedBytesType const&>(_to);
|
||||
body =
|
||||
Whiskers("converted := <shiftLeft>(<clean>(value))")
|
||||
("shiftLeft", shiftLeftFunction(256 - toBytesType.numBytes() * 8))
|
||||
("shiftLeft", m_utils.shiftLeftFunction(256 - toBytesType.numBytes() * 8))
|
||||
("clean", cleanupFunction(_from))
|
||||
.render();
|
||||
}
|
||||
@ -474,7 +472,7 @@ string ABIFunctions::conversionFunction(Type const& _from, Type const& _to)
|
||||
if (toCategory == Type::Category::Integer)
|
||||
body =
|
||||
Whiskers("converted := <convert>(<shift>(value))")
|
||||
("shift", shiftRightFunction(256 - from.numBytes() * 8))
|
||||
("shift", m_utils.shiftRightFunction(256 - from.numBytes() * 8))
|
||||
("convert", conversionFunction(IntegerType(from.numBytes() * 8), _to))
|
||||
.render();
|
||||
else if (toCategory == Type::Category::Address)
|
||||
@ -538,40 +536,6 @@ string ABIFunctions::cleanupCombinedExternalFunctionIdFunction()
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::combineExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "combine_external_function_id";
|
||||
return createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(addr, selector) -> combined {
|
||||
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shl32", shiftLeftFunction(32))
|
||||
("shl64", shiftLeftFunction(64))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::splitExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "split_external_function_id";
|
||||
return createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(combined) -> addr, selector {
|
||||
combined := <shr64>(combined)
|
||||
selector := and(combined, 0xffffffff)
|
||||
addr := <shr32>(combined)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr32", shiftRightFunction(32))
|
||||
("shr64", shiftRightFunction(64))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::abiEncodingFunction(
|
||||
Type const& _from,
|
||||
Type const& _to,
|
||||
@ -652,7 +616,7 @@ string ABIFunctions::abiEncodingFunction(
|
||||
else
|
||||
cleanupConvert = conversionFunction(_from, to) + "(value)";
|
||||
if (!_options.padded)
|
||||
cleanupConvert = leftAlignFunction(to) + "(" + cleanupConvert + ")";
|
||||
cleanupConvert = m_utils.leftAlignFunction(to) + "(" + cleanupConvert + ")";
|
||||
templ("cleanupConvert", cleanupConvert);
|
||||
}
|
||||
return templ.render();
|
||||
@ -742,8 +706,8 @@ string ABIFunctions::abiEncodingFunctionCalldataArray(
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeNameFrom", _from.toString(true));
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("copyFun", copyToMemoryFunction(true));
|
||||
templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
|
||||
templ("copyFun", m_utils.copyToMemoryFunction(true));
|
||||
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
@ -813,16 +777,16 @@ string ABIFunctions::abiEncodingFunctionSimpleArray(
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("return", dynamic ? " -> end " : "");
|
||||
templ("assignEnd", dynamic ? "end := pos" : "");
|
||||
templ("lengthFun", arrayLengthFunction(_from));
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("dataAreaFun", arrayDataAreaFunction(_from));
|
||||
templ("dataAreaFun", m_utils.arrayDataAreaFunction(_from));
|
||||
|
||||
EncodingOptions subOptions(_options);
|
||||
subOptions.encodeFunctionFromStack = false;
|
||||
subOptions.padded = true;
|
||||
templ("encodeToMemoryFun", abiEncodeAndReturnUpdatedPosFunction(*_from.baseType(), *_to.baseType(), subOptions));
|
||||
templ("arrayElementAccess", inMemory ? "mload(srcPtr)" : _from.baseType()->isValueType() ? "sload(srcPtr)" : "srcPtr" );
|
||||
templ("nextArrayElement", nextArrayElementFunction(_from));
|
||||
templ("nextArrayElement", m_utils.nextArrayElementFunction(_from));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
@ -856,10 +820,10 @@ string ABIFunctions::abiEncodingFunctionMemoryByteArray(
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("lengthFun", arrayLengthFunction(_from));
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("copyFun", copyToMemoryFunction(false));
|
||||
templ("lengthPadded", _options.padded ? roundUpFunction() + "(length)" : "length");
|
||||
templ("copyFun", m_utils.copyToMemoryFunction(false));
|
||||
templ("lengthPadded", _options.padded ? m_utils.roundUpFunction() + "(length)" : "length");
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
@ -917,7 +881,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("lengthPaddedShort", _options.padded ? "0x20" : "length");
|
||||
templ("lengthPaddedLong", _options.padded ? "i" : "length");
|
||||
templ("arrayDataSlot", arrayDataAreaFunction(_from));
|
||||
templ("arrayDataSlot", m_utils.arrayDataAreaFunction(_from));
|
||||
return templ.render();
|
||||
}
|
||||
else
|
||||
@ -958,9 +922,9 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("readableTypeNameTo", _to.toString(true));
|
||||
templ("return", dynamic ? " -> end " : "");
|
||||
templ("assignEnd", dynamic ? "end := pos" : "");
|
||||
templ("lengthFun", arrayLengthFunction(_from));
|
||||
templ("lengthFun", m_utils.arrayLengthFunction(_from));
|
||||
templ("storeLength", arrayStoreLengthForEncodingFunction(_to, _options));
|
||||
templ("dataArea", arrayDataAreaFunction(_from));
|
||||
templ("dataArea", m_utils.arrayDataAreaFunction(_from));
|
||||
templ("itemsPerSlot", to_string(itemsPerSlot));
|
||||
// We use padded size because array elements are always padded.
|
||||
string elementEncodedSize = toCompactHexWithPrefix(_to.baseType()->calldataEncodedSize());
|
||||
@ -977,7 +941,7 @@ string ABIFunctions::abiEncodingFunctionCompactStorageArray(
|
||||
templ("encodeToMemoryFun", encodeToMemoryFun);
|
||||
std::vector<std::map<std::string, std::string>> items(itemsPerSlot);
|
||||
for (size_t i = 0; i < itemsPerSlot; ++i)
|
||||
items[i]["shiftRightFun"] = shiftRightFunction(i * storageBytes * 8);
|
||||
items[i]["shiftRightFun"] = m_utils.shiftRightFunction(i * storageBytes * 8);
|
||||
templ("items", items);
|
||||
return templ.render();
|
||||
}
|
||||
@ -1063,7 +1027,7 @@ string ABIFunctions::abiEncodingFunctionStruct(
|
||||
members.back()["preprocess"] = "slotValue := sload(add(value, " + toCompactHexWithPrefix(storageSlotOffset) + "))";
|
||||
previousSlotOffset = storageSlotOffset;
|
||||
}
|
||||
members.back()["retrieveValue"] = shiftRightFunction(intraSlotOffset * 8) + "(slotValue)";
|
||||
members.back()["retrieveValue"] = m_utils.shiftRightFunction(intraSlotOffset * 8) + "(slotValue)";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1206,7 +1170,7 @@ string ABIFunctions::abiEncodingFunctionFunctionType(
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("combineExtFun", combineExternalFunctionIdFunction())
|
||||
("combineExtFun", m_utils.combineExternalFunctionIdFunction())
|
||||
.render();
|
||||
});
|
||||
else
|
||||
@ -1328,8 +1292,8 @@ string ABIFunctions::abiDecodingFunctionArray(ArrayType const& _type, bool _from
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeName", _type.toString(true));
|
||||
templ("retrieveLength", !_type.isDynamicallySized() ? toCompactHexWithPrefix(_type.length()) : load + "(offset)");
|
||||
templ("allocate", allocationFunction());
|
||||
templ("allocationSize", arrayAllocationSizeFunction(_type));
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
|
||||
if (_type.isDynamicallySized())
|
||||
templ("storeLength", "mstore(array, length) offset := add(offset, 0x20) dst := add(dst, 0x20)");
|
||||
else
|
||||
@ -1421,9 +1385,9 @@ string ABIFunctions::abiDecodingFunctionByteArray(ArrayType const& _type, bool _
|
||||
);
|
||||
templ("functionName", functionName);
|
||||
templ("load", _fromMemory ? "mload" : "calldataload");
|
||||
templ("allocate", allocationFunction());
|
||||
templ("allocationSize", arrayAllocationSizeFunction(_type));
|
||||
templ("copyToMemFun", copyToMemoryFunction(!_fromMemory));
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
templ("allocationSize", m_utils.arrayAllocationSizeFunction(_type));
|
||||
templ("copyToMemFun", m_utils.copyToMemoryFunction(!_fromMemory));
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
@ -1475,7 +1439,7 @@ string ABIFunctions::abiDecodingFunctionStruct(StructType const& _type, bool _fr
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
templ("readableTypeName", _type.toString(true));
|
||||
templ("allocate", allocationFunction());
|
||||
templ("allocate", m_utils.allocationFunction());
|
||||
solAssert(_type.memorySize() < u256("0xffffffffffffffff"), "");
|
||||
templ("memorySize", toCompactHexWithPrefix(_type.memorySize()));
|
||||
size_t headPos = 0;
|
||||
@ -1535,7 +1499,7 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("load", _fromMemory ? "mload" : "calldataload")
|
||||
("splitExtFun", splitExternalFunctionIdFunction())
|
||||
("splitExtFun", m_utils.splitExternalFunctionIdFunction())
|
||||
.render();
|
||||
}
|
||||
else
|
||||
@ -1553,357 +1517,6 @@ string ABIFunctions::abiDecodingFunctionFunctionType(FunctionType const& _type,
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::copyToMemoryFunction(bool _fromCalldata)
|
||||
{
|
||||
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_fromCalldata)
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(src, dst, length) {
|
||||
calldatacopy(dst, src, length)
|
||||
// clear end
|
||||
mstore(add(dst, length), 0)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(src, dst, length) {
|
||||
let i := 0
|
||||
for { } lt(i, length) { i := add(i, 32) }
|
||||
{
|
||||
mstore(add(dst, i), mload(add(src, i)))
|
||||
}
|
||||
if gt(i, length)
|
||||
{
|
||||
// clear end
|
||||
mstore(add(dst, length), 0)
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::leftAlignFunction(Type const& _type)
|
||||
{
|
||||
string functionName = string("leftAlign_") + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> aligned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
switch (_type.category())
|
||||
{
|
||||
case Type::Category::Address:
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)");
|
||||
break;
|
||||
case Type::Category::Integer:
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
if (type.numBits() == 256)
|
||||
templ("body", "aligned := value");
|
||||
else
|
||||
templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::RationalNumber:
|
||||
solAssert(false, "Left align requested for rational number.");
|
||||
break;
|
||||
case Type::Category::Bool:
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)");
|
||||
break;
|
||||
case Type::Category::FixedPoint:
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
case Type::Category::Struct:
|
||||
solAssert(false, "Left align requested for non-value type.");
|
||||
break;
|
||||
case Type::Category::FixedBytes:
|
||||
templ("body", "aligned := value");
|
||||
break;
|
||||
case Type::Category::Contract:
|
||||
templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)");
|
||||
break;
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
unsigned storageBytes = dynamic_cast<EnumType const&>(_type).storageBytes();
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::InaccessibleDynamic:
|
||||
solAssert(false, "Left align requested for inaccessible dynamic type.");
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Left align of type " + _type.identifier() + " requested.");
|
||||
}
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::shiftLeftFunction(size_t _numBits)
|
||||
{
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
string functionName = "shift_left_" + to_string(_numBits);
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shl(<numBits>, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := mul(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
string ABIFunctions::shiftRightFunction(size_t _numBits)
|
||||
{
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
// Note that if this is extended with signed shifts,
|
||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||
|
||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shr(<numBits>, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := div(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
string ABIFunctions::roundUpFunction()
|
||||
{
|
||||
string functionName = "round_up_to_mul_of_32";
|
||||
return createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> result {
|
||||
result := and(add(value, 31), not(31))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_length_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(value) -> length {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
string body;
|
||||
if (!_type.isDynamicallySized())
|
||||
body = "length := " + toCompactHexWithPrefix(_type.length());
|
||||
else
|
||||
{
|
||||
switch (_type.location())
|
||||
{
|
||||
case DataLocation::CallData:
|
||||
solAssert(false, "called regular array length function on calldata array");
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
body = "length := mload(value)";
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
if (_type.isByteArray())
|
||||
{
|
||||
// Retrieve length both for in-place strings and off-place strings:
|
||||
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
|
||||
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
|
||||
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
|
||||
body = R"(
|
||||
length := sload(value)
|
||||
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
|
||||
length := div(and(length, mask), 2)
|
||||
)";
|
||||
}
|
||||
else
|
||||
body = "length := sload(value)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
solAssert(!body.empty(), "");
|
||||
w("body", body);
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||
string functionName = "array_allocation_size_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(length) -> size {
|
||||
// Make sure we can allocate memory without overflow
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
size := <allocationSize>
|
||||
<addLengthSlot>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
if (_type.isByteArray())
|
||||
// Round up
|
||||
w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
|
||||
else
|
||||
w("allocationSize", "mul(length, 0x20)");
|
||||
if (_type.isDynamicallySized())
|
||||
w("addLengthSlot", "size := add(size, 0x20)");
|
||||
else
|
||||
w("addLengthSlot", "");
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_dataslot_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_type.dataStoredIn(DataLocation::Memory))
|
||||
{
|
||||
if (_type.isDynamicallySized())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := memPtr
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else if (_type.dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
if (_type.isDynamicallySized())
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
mstore(0, slot)
|
||||
dataSlot := keccak256(0, 0x20)
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
dataSlot := slot
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not used for calldata
|
||||
solAssert(false, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(!_type.isByteArray(), "");
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.location() == DataLocation::Storage,
|
||||
""
|
||||
);
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.baseType()->storageBytes() > 16,
|
||||
""
|
||||
);
|
||||
string functionName = "array_nextElement_" + _type.identifier();
|
||||
return createFunction(functionName, [&]() {
|
||||
if (_type.location() == DataLocation::Memory)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> nextPtr {
|
||||
nextPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else if (_type.location() == DataLocation::Storage)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> nextSlot {
|
||||
nextSlot := add(slot, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
solAssert(false, "");
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options)
|
||||
{
|
||||
string functionName = "array_storeLengthForEncoding_" + _type.identifier() + _options.toFunctionNameSuffix();
|
||||
@ -1928,34 +1541,9 @@ string ABIFunctions::arrayStoreLengthForEncodingFunction(ArrayType const& _type,
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::allocationFunction()
|
||||
{
|
||||
string functionName = "allocateMemory";
|
||||
return createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(size) -> memPtr {
|
||||
memPtr := mload(<freeMemoryPointer>)
|
||||
let newFreePtr := add(memPtr, size)
|
||||
// protect against overflow
|
||||
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
|
||||
mstore(<freeMemoryPointer>, newFreePtr)
|
||||
}
|
||||
)")
|
||||
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string ABIFunctions::createFunction(string const& _name, function<string ()> const& _creator)
|
||||
{
|
||||
if (!m_requestedFunctions.count(_name))
|
||||
{
|
||||
auto fun = _creator();
|
||||
solAssert(!fun.empty(), "");
|
||||
m_requestedFunctions[_name] = fun;
|
||||
}
|
||||
return _name;
|
||||
return m_functionCollector->createFunction(_name, _creator);
|
||||
}
|
||||
|
||||
string ABIFunctions::createExternallyUsedFunction(string const& _name, function<string ()> const& _creator)
|
||||
|
@ -22,7 +22,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTForward.h>
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <functional>
|
||||
@ -30,8 +32,10 @@
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace dev {
|
||||
namespace solidity {
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class Type;
|
||||
class ArrayType;
|
||||
@ -40,17 +44,25 @@ class FunctionType;
|
||||
using TypePointer = std::shared_ptr<Type const>;
|
||||
using TypePointers = std::vector<TypePointer>;
|
||||
|
||||
///
|
||||
/// Class to generate encoding and decoding functions. Also maintains a collection
|
||||
/// of "functions to be generated" in order to avoid generating the same function
|
||||
/// multiple times.
|
||||
///
|
||||
/// Make sure to include the result of ``requestedFunctions()`` to a block that
|
||||
/// is visible from the code that was generated here, or use named labels.
|
||||
/**
|
||||
* Class to generate encoding and decoding functions. Also maintains a collection
|
||||
* of "functions to be generated" in order to avoid generating the same function
|
||||
* multiple times.
|
||||
*
|
||||
* Make sure to include the result of ``requestedFunctions()`` to a block that
|
||||
* is visible from the code that was generated here, or use named labels.
|
||||
*/
|
||||
class ABIFunctions
|
||||
{
|
||||
public:
|
||||
explicit ABIFunctions(langutil::EVMVersion _evmVersion = langutil::EVMVersion{}) : m_evmVersion(_evmVersion) {}
|
||||
explicit ABIFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector = std::make_shared<MultiUseYulFunctionCollector>()
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_functionCollector(std::move(_functionCollector)),
|
||||
m_utils(_evmVersion, m_functionCollector)
|
||||
{}
|
||||
|
||||
/// @returns name of an assembly function to ABI-encode values of @a _givenTypes
|
||||
/// into memory, converting the types to @a _targetTypes on the fly.
|
||||
@ -129,14 +141,6 @@ private:
|
||||
|
||||
std::string cleanupCombinedExternalFunctionIdFunction();
|
||||
|
||||
/// @returns a function that combines the address and selector to a single value
|
||||
/// for use in the ABI.
|
||||
std::string combineExternalFunctionIdFunction();
|
||||
|
||||
/// @returns a function that splits the address and selector from a single value
|
||||
/// for use in the ABI.
|
||||
std::string splitExternalFunctionIdFunction();
|
||||
|
||||
/// @returns the name of the ABI encoding function with the given type
|
||||
/// and queues the generation of the function to the requested functions.
|
||||
/// @param _fromStack if false, the input value was just loaded from storage
|
||||
@ -229,34 +233,6 @@ private:
|
||||
/// Part of @a abiDecodingFunction for array types.
|
||||
std::string abiDecodingFunctionFunctionType(FunctionType const& _type, bool _fromMemory, bool _forUseOnStack);
|
||||
|
||||
/// @returns a function that copies raw bytes of dynamic length from calldata
|
||||
/// or memory to memory.
|
||||
/// Pads with zeros and might write more than exactly length.
|
||||
std::string copyToMemoryFunction(bool _fromCalldata);
|
||||
|
||||
/// @returns the name of a function that takes a (cleaned) value of the given value type and
|
||||
/// left-aligns it, usually for use in non-padded encoding.
|
||||
std::string leftAlignFunction(Type const& _type);
|
||||
|
||||
std::string shiftLeftFunction(size_t _numBits);
|
||||
std::string shiftRightFunction(size_t _numBits);
|
||||
/// @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.
|
||||
std::string roundUpFunction();
|
||||
|
||||
std::string arrayLengthFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that computes the number of bytes required
|
||||
/// to store an array in memory given its length (internally encoded, not ABI encoded).
|
||||
/// The function reverts for too large lengths.
|
||||
std::string arrayAllocationSizeFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that converts a storage slot number
|
||||
/// or a memory pointer to the slot number / memory pointer for the data position of an array
|
||||
/// which is stored in that slot / memory area.
|
||||
std::string arrayDataAreaFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that advances an array data pointer to the next element.
|
||||
/// Only works for memory arrays and storage arrays that store one item per slot.
|
||||
std::string nextArrayElementFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function used during encoding that stores the length
|
||||
/// if the array is dynamically sized (and the options do not request in-place encoding).
|
||||
/// It returns the new encoding position.
|
||||
@ -264,12 +240,6 @@ private:
|
||||
/// does nothing and just returns the position again.
|
||||
std::string arrayStoreLengthForEncodingFunction(ArrayType const& _type, EncodingOptions const& _options);
|
||||
|
||||
/// @returns the name of a function that allocates memory.
|
||||
/// Modifies the "free memory pointer"
|
||||
/// Arguments: size
|
||||
/// Return value: pointer
|
||||
std::string allocationFunction();
|
||||
|
||||
/// Helper function that uses @a _creator to create a function and add it to
|
||||
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
|
||||
/// cases.
|
||||
@ -283,10 +253,10 @@ private:
|
||||
/// @returns the size of the static part of the encoding of the given types.
|
||||
static size_t headSize(TypePointers const& _targetTypes);
|
||||
|
||||
/// Map from function name to code for a multi-use function.
|
||||
std::map<std::string, std::string> m_requestedFunctions;
|
||||
std::set<std::string> m_externallyUsedFunctions;
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
std::set<std::string> m_externallyUsedFunctions;
|
||||
YulUtilFunctions m_utils;
|
||||
};
|
||||
|
||||
}
|
||||
|
52
libsolidity/codegen/MultiUseYulFunctionCollector.cpp
Normal file
52
libsolidity/codegen/MultiUseYulFunctionCollector.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Container of (unparsed) Yul functions identified by name which are meant to be generated
|
||||
* only once.
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
string MultiUseYulFunctionCollector::requestedFunctions()
|
||||
{
|
||||
string result;
|
||||
for (auto const& f: m_requestedFunctions)
|
||||
result += f.second;
|
||||
m_requestedFunctions.clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
string MultiUseYulFunctionCollector::createFunction(string const& _name, function<string ()> const& _creator)
|
||||
{
|
||||
if (!m_requestedFunctions.count(_name))
|
||||
{
|
||||
string fun = _creator();
|
||||
solAssert(!fun.empty(), "");
|
||||
solAssert(fun.find("function " + _name) != string::npos, "Function not properly named.");
|
||||
m_requestedFunctions[_name] = std::move(fun);
|
||||
}
|
||||
return _name;
|
||||
}
|
56
libsolidity/codegen/MultiUseYulFunctionCollector.h
Normal file
56
libsolidity/codegen/MultiUseYulFunctionCollector.h
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Container of (unparsed) Yul functions identified by name which are meant to be generated
|
||||
* only once.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
/**
|
||||
* Container of (unparsed) Yul functions identified by name which are meant to be generated
|
||||
* only once.
|
||||
*/
|
||||
class MultiUseYulFunctionCollector
|
||||
{
|
||||
public:
|
||||
/// Helper function that uses @a _creator to create a function and add it to
|
||||
/// @a m_requestedFunctions if it has not been created yet and returns @a _name in both
|
||||
/// cases.
|
||||
std::string createFunction(std::string const& _name, std::function<std::string()> const& _creator);
|
||||
|
||||
/// @returns concatenation of all generated functions.
|
||||
/// Clears the internal list, i.e. calling it again will result in an
|
||||
/// empty return value.
|
||||
std::string requestedFunctions();
|
||||
|
||||
private:
|
||||
/// Map from function name to code for a multi-use function.
|
||||
std::map<std::string, std::string> m_requestedFunctions;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
438
libsolidity/codegen/YulUtilFunctions.cpp
Normal file
438
libsolidity/codegen/YulUtilFunctions.cpp
Normal file
@ -0,0 +1,438 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Component that can generate various useful Yul functions.
|
||||
*/
|
||||
|
||||
#include <libsolidity/codegen/YulUtilFunctions.h>
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/codegen/CompilerUtils.h>
|
||||
#include <libdevcore/Whiskers.h>
|
||||
|
||||
#include <boost/algorithm/string/join.hpp>
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace dev::solidity;
|
||||
|
||||
string YulUtilFunctions::combineExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "combine_external_function_id";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(addr, selector) -> combined {
|
||||
combined := <shl64>(or(<shl32>(addr), and(selector, 0xffffffff)))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shl32", shiftLeftFunction(32))
|
||||
("shl64", shiftLeftFunction(64))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::splitExternalFunctionIdFunction()
|
||||
{
|
||||
string functionName = "split_external_function_id";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(combined) -> addr, selector {
|
||||
combined := <shr64>(combined)
|
||||
selector := and(combined, 0xffffffff)
|
||||
addr := <shr32>(combined)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("shr32", shiftRightFunction(32))
|
||||
("shr64", shiftRightFunction(64))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::copyToMemoryFunction(bool _fromCalldata)
|
||||
{
|
||||
string functionName = "copy_" + string(_fromCalldata ? "calldata" : "memory") + "_to_memory";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_fromCalldata)
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(src, dst, length) {
|
||||
calldatacopy(dst, src, length)
|
||||
// clear end
|
||||
mstore(add(dst, length), 0)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Whiskers(R"(
|
||||
function <functionName>(src, dst, length) {
|
||||
let i := 0
|
||||
for { } lt(i, length) { i := add(i, 32) }
|
||||
{
|
||||
mstore(add(dst, i), mload(add(src, i)))
|
||||
}
|
||||
if gt(i, length)
|
||||
{
|
||||
// clear end
|
||||
mstore(add(dst, length), 0)
|
||||
}
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::leftAlignFunction(Type const& _type)
|
||||
{
|
||||
string functionName = string("leftAlign_") + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers templ(R"(
|
||||
function <functionName>(value) -> aligned {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
templ("functionName", functionName);
|
||||
switch (_type.category())
|
||||
{
|
||||
case Type::Category::Address:
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(160)) + "(value)");
|
||||
break;
|
||||
case Type::Category::Integer:
|
||||
{
|
||||
IntegerType const& type = dynamic_cast<IntegerType const&>(_type);
|
||||
if (type.numBits() == 256)
|
||||
templ("body", "aligned := value");
|
||||
else
|
||||
templ("body", "aligned := " + shiftLeftFunction(256 - type.numBits()) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::RationalNumber:
|
||||
solAssert(false, "Left align requested for rational number.");
|
||||
break;
|
||||
case Type::Category::Bool:
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(8)) + "(value)");
|
||||
break;
|
||||
case Type::Category::FixedPoint:
|
||||
solUnimplemented("Fixed point types not implemented.");
|
||||
break;
|
||||
case Type::Category::Array:
|
||||
case Type::Category::Struct:
|
||||
solAssert(false, "Left align requested for non-value type.");
|
||||
break;
|
||||
case Type::Category::FixedBytes:
|
||||
templ("body", "aligned := value");
|
||||
break;
|
||||
case Type::Category::Contract:
|
||||
templ("body", "aligned := " + leftAlignFunction(AddressType::address()) + "(value)");
|
||||
break;
|
||||
case Type::Category::Enum:
|
||||
{
|
||||
unsigned storageBytes = dynamic_cast<EnumType const&>(_type).storageBytes();
|
||||
templ("body", "aligned := " + leftAlignFunction(IntegerType(8 * storageBytes)) + "(value)");
|
||||
break;
|
||||
}
|
||||
case Type::Category::InaccessibleDynamic:
|
||||
solAssert(false, "Left align requested for inaccessible dynamic type.");
|
||||
break;
|
||||
default:
|
||||
solAssert(false, "Left align of type " + _type.identifier() + " requested.");
|
||||
}
|
||||
|
||||
return templ.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::shiftLeftFunction(size_t _numBits)
|
||||
{
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
string functionName = "shift_left_" + to_string(_numBits);
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shl(<numBits>, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := mul(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
string YulUtilFunctions::shiftRightFunction(size_t _numBits)
|
||||
{
|
||||
solAssert(_numBits < 256, "");
|
||||
|
||||
// Note that if this is extended with signed shifts,
|
||||
// the opcodes SAR and SDIV behave differently with regards to rounding!
|
||||
|
||||
string functionName = "shift_right_" + to_string(_numBits) + "_unsigned";
|
||||
if (m_evmVersion.hasBitwiseShifting())
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := shr(<numBits>, value)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("numBits", to_string(_numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> newValue {
|
||||
newValue := div(value, <multiplier>)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
("multiplier", toCompactHexWithPrefix(u256(1) << _numBits))
|
||||
.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
string YulUtilFunctions::roundUpFunction()
|
||||
{
|
||||
string functionName = "round_up_to_mul_of_32";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return
|
||||
Whiskers(R"(
|
||||
function <functionName>(value) -> result {
|
||||
result := and(add(value, 31), not(31))
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayLengthFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_length_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(value) -> length {
|
||||
<body>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
string body;
|
||||
if (!_type.isDynamicallySized())
|
||||
body = "length := " + toCompactHexWithPrefix(_type.length());
|
||||
else
|
||||
{
|
||||
switch (_type.location())
|
||||
{
|
||||
case DataLocation::CallData:
|
||||
solAssert(false, "called regular array length function on calldata array");
|
||||
break;
|
||||
case DataLocation::Memory:
|
||||
body = "length := mload(value)";
|
||||
break;
|
||||
case DataLocation::Storage:
|
||||
if (_type.isByteArray())
|
||||
{
|
||||
// Retrieve length both for in-place strings and off-place strings:
|
||||
// Computes (x & (0x100 * (ISZERO (x & 1)) - 1)) / 2
|
||||
// i.e. for short strings (x & 1 == 0) it does (x & 0xff) / 2 and for long strings it
|
||||
// computes (x & (-1)) / 2, which is equivalent to just x / 2.
|
||||
body = R"(
|
||||
length := sload(value)
|
||||
let mask := sub(mul(0x100, iszero(and(length, 1))), 1)
|
||||
length := div(and(length, mask), 2)
|
||||
)";
|
||||
}
|
||||
else
|
||||
body = "length := sload(value)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
solAssert(!body.empty(), "");
|
||||
w("body", body);
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
|
||||
string functionName = "array_allocation_size_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
Whiskers w(R"(
|
||||
function <functionName>(length) -> size {
|
||||
// Make sure we can allocate memory without overflow
|
||||
if gt(length, 0xffffffffffffffff) { revert(0, 0) }
|
||||
size := <allocationSize>
|
||||
<addLengthSlot>
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
if (_type.isByteArray())
|
||||
// Round up
|
||||
w("allocationSize", "and(add(length, 0x1f), not(0x1f))");
|
||||
else
|
||||
w("allocationSize", "mul(length, 0x20)");
|
||||
if (_type.isDynamicallySized())
|
||||
w("addLengthSlot", "size := add(size, 0x20)");
|
||||
else
|
||||
w("addLengthSlot", "");
|
||||
return w.render();
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::arrayDataAreaFunction(ArrayType const& _type)
|
||||
{
|
||||
string functionName = "array_dataslot_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_type.dataStoredIn(DataLocation::Memory))
|
||||
{
|
||||
if (_type.isDynamicallySized())
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> dataPtr {
|
||||
dataPtr := memPtr
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
}
|
||||
else if (_type.dataStoredIn(DataLocation::Storage))
|
||||
{
|
||||
if (_type.isDynamicallySized())
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
mstore(0, slot)
|
||||
dataSlot := keccak256(0, 0x20)
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
else
|
||||
{
|
||||
Whiskers w(R"(
|
||||
function <functionName>(slot) -> dataSlot {
|
||||
dataSlot := slot
|
||||
}
|
||||
)");
|
||||
w("functionName", functionName);
|
||||
return w.render();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not used for calldata
|
||||
solAssert(false, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::nextArrayElementFunction(ArrayType const& _type)
|
||||
{
|
||||
solAssert(!_type.isByteArray(), "");
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.location() == DataLocation::Storage,
|
||||
""
|
||||
);
|
||||
solAssert(
|
||||
_type.location() == DataLocation::Memory ||
|
||||
_type.baseType()->storageBytes() > 16,
|
||||
""
|
||||
);
|
||||
string functionName = "array_nextElement_" + _type.identifier();
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
if (_type.location() == DataLocation::Memory)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(memPtr) -> nextPtr {
|
||||
nextPtr := add(memPtr, 0x20)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else if (_type.location() == DataLocation::Storage)
|
||||
return Whiskers(R"(
|
||||
function <functionName>(slot) -> nextSlot {
|
||||
nextSlot := add(slot, 1)
|
||||
}
|
||||
)")
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
else
|
||||
solAssert(false, "");
|
||||
});
|
||||
}
|
||||
|
||||
string YulUtilFunctions::allocationFunction()
|
||||
{
|
||||
string functionName = "allocateMemory";
|
||||
return m_functionCollector->createFunction(functionName, [&]() {
|
||||
return Whiskers(R"(
|
||||
function <functionName>(size) -> memPtr {
|
||||
memPtr := mload(<freeMemoryPointer>)
|
||||
let newFreePtr := add(memPtr, size)
|
||||
// protect against overflow
|
||||
if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) { revert(0, 0) }
|
||||
mstore(<freeMemoryPointer>, newFreePtr)
|
||||
}
|
||||
)")
|
||||
("freeMemoryPointer", to_string(CompilerUtils::freeMemoryPointer))
|
||||
("functionName", functionName)
|
||||
.render();
|
||||
});
|
||||
}
|
||||
|
100
libsolidity/codegen/YulUtilFunctions.h
Normal file
100
libsolidity/codegen/YulUtilFunctions.h
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
This file is part of solidity.
|
||||
|
||||
solidity is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
solidity is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Component that can generate various useful Yul functions.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
|
||||
#include <libsolidity/codegen/MultiUseYulFunctionCollector.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
|
||||
class Type;
|
||||
class ArrayType;
|
||||
|
||||
/**
|
||||
* Component that can generate various useful Yul functions.
|
||||
*/
|
||||
class YulUtilFunctions
|
||||
{
|
||||
public:
|
||||
explicit YulUtilFunctions(
|
||||
langutil::EVMVersion _evmVersion,
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> _functionCollector
|
||||
):
|
||||
m_evmVersion(_evmVersion),
|
||||
m_functionCollector(std::move(_functionCollector))
|
||||
{}
|
||||
|
||||
/// @returns a function that combines the address and selector to a single value
|
||||
/// for use in the ABI.
|
||||
std::string combineExternalFunctionIdFunction();
|
||||
|
||||
/// @returns a function that splits the address and selector from a single value
|
||||
/// for use in the ABI.
|
||||
std::string splitExternalFunctionIdFunction();
|
||||
|
||||
/// @returns a function that copies raw bytes of dynamic length from calldata
|
||||
/// or memory to memory.
|
||||
/// Pads with zeros and might write more than exactly length.
|
||||
std::string copyToMemoryFunction(bool _fromCalldata);
|
||||
|
||||
/// @returns the name of a function that takes a (cleaned) value of the given value type and
|
||||
/// left-aligns it, usually for use in non-padded encoding.
|
||||
std::string leftAlignFunction(Type const& _type);
|
||||
|
||||
std::string shiftLeftFunction(size_t _numBits);
|
||||
std::string shiftRightFunction(size_t _numBits);
|
||||
/// @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.
|
||||
std::string roundUpFunction();
|
||||
|
||||
std::string arrayLengthFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that computes the number of bytes required
|
||||
/// to store an array in memory given its length (internally encoded, not ABI encoded).
|
||||
/// The function reverts for too large lengths.
|
||||
std::string arrayAllocationSizeFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that converts a storage slot number
|
||||
/// or a memory pointer to the slot number / memory pointer for the data position of an array
|
||||
/// which is stored in that slot / memory area.
|
||||
std::string arrayDataAreaFunction(ArrayType const& _type);
|
||||
/// @returns the name of a function that advances an array data pointer to the next element.
|
||||
/// Only works for memory arrays and storage arrays that store one item per slot.
|
||||
std::string nextArrayElementFunction(ArrayType const& _type);
|
||||
|
||||
/// @returns the name of a function that allocates memory.
|
||||
/// Modifies the "free memory pointer"
|
||||
/// Arguments: size
|
||||
/// Return value: pointer
|
||||
std::string allocationFunction();
|
||||
|
||||
private:
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
std::shared_ptr<MultiUseYulFunctionCollector> m_functionCollector;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user