Merge pull request from ethereum/extractYulUtils

[REF] Extract utility functions from ABI encoder.
This commit is contained in:
chriseth 2019-03-08 00:51:58 +01:00 committed by GitHub
commit 4d8c57006b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 705 additions and 497 deletions

View File

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

View File

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

View File

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

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

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

View 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();
});
}

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