Zero initialize memory arrays

This commit is contained in:
Leonardo Alt
2020-04-02 15:55:18 +02:00
parent 8d28089abc
commit 39ff0deb05
22 changed files with 475 additions and 35 deletions
+1 -1
View File
@@ -2338,7 +2338,7 @@ TypePointers StructType::memoryMemberTypes() const
TypePointers types;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.members())
if (variable->annotation().type->canLiveOutsideStorage())
types.push_back(variable->annotation().type);
types.push_back(TypeProvider::withLocationIfReference(DataLocation::Memory, variable->annotation().type));
return types;
}
+146 -24
View File
@@ -658,6 +658,8 @@ string YulUtilFunctions::storageArrayPushZeroFunction(ArrayType const& _type)
solUnimplementedAssert(!_type.isByteArray(), "Byte Arrays not yet implemented!");
solUnimplementedAssert(_type.baseType()->storageBytes() <= 32, "Base type is not yet implemented.");
solAssert(_type.baseType()->isValueType(), "");
string functionName = "array_push_zero_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
@@ -794,6 +796,7 @@ string YulUtilFunctions::arrayConvertLengthToSize(ArrayType const& _type)
});
}
string YulUtilFunctions::arrayAllocationSizeFunction(ArrayType const& _type)
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
@@ -1178,7 +1181,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, value)
}
}
)")
("functionName", functionName)
.render();
@@ -1202,7 +1205,7 @@ string YulUtilFunctions::writeToMemoryFunction(Type const& _type)
return Whiskers(R"(
function <functionName>(memPtr, value) {
mstore(memPtr, <cleanup>(value))
}
}
)")
("functionName", functionName)
("cleanup", cleanupFunction(_type))
@@ -1334,28 +1337,112 @@ string YulUtilFunctions::allocationFunction()
});
}
string YulUtilFunctions::allocateMemoryArrayFunction(ArrayType const& _type)
string YulUtilFunctions::zeroMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
if (_type.baseType()->hasSimpleZeroValueInMemory())
return zeroMemoryFunction(*_type.baseType());
return zeroComplexMemoryArrayFunction(_type);
}
string functionName = "allocate_memory_array_" + _type.identifier();
string YulUtilFunctions::zeroMemoryFunction(Type const& _type)
{
solAssert(_type.hasSimpleZeroValueInMemory(), "");
string functionName = "zero_memory_chunk_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
memPtr := <alloc>(<allocSize>(length))
<?dynamic>
mstore(memPtr, length)
</dynamic>
function <functionName>(dataStart, dataSizeInBytes) {
calldatacopy(dataStart, calldatasize(), dataSizeInBytes)
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::zeroComplexMemoryArrayFunction(ArrayType const& _type)
{
solAssert(!_type.baseType()->hasSimpleZeroValueInMemory(), "");
string functionName = "zero_complex_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
solAssert(_type.memoryStride() == 32, "");
return Whiskers(R"(
function <functionName>(dataStart, dataSizeInBytes) {
for {let i := 0} lt(i, dataSizeInBytes) { i := add(i, <stride>) } {
mstore(add(dataStart, i), <zeroValue>())
}
}
)")
("functionName", functionName)
("stride", to_string(_type.memoryStride()))
("zeroValue", zeroValueFunction(*_type.baseType(), false))
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryArrayFunction(ArrayType const& _type)
{
solUnimplementedAssert(!_type.isByteArray(), "");
string functionName = "allocate_and_zero_memory_array_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>(length) -> memPtr {
let allocSize := <allocSize>(length)
memPtr := <alloc>(allocSize)
let dataStart := memPtr
let dataSize := allocSize
<?dynamic>
dataStart := add(dataStart, 32)
dataSize := sub(dataSize, 32)
mstore(memPtr, length)
</dynamic>
<zeroArrayFunction>(dataStart, dataSize)
}
)")
("functionName", functionName)
("alloc", allocationFunction())
("allocSize", arrayAllocationSizeFunction(_type))
("zeroArrayFunction", zeroMemoryArrayFunction(_type))
("dynamic", _type.isDynamicallySized())
.render();
});
}
string YulUtilFunctions::allocateAndInitializeMemoryStructFunction(StructType const& _type)
{
string functionName = "allocate_and_initialize_memory_struct_" + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
Whiskers templ(R"(
function <functionName>() -> memPtr {
let allocSize := <allocSize>()
memPtr := <alloc>(allocSize)
let offset := memPtr
<#member>
mstore(offset, <zeroValue>())
offset := add(offset, 32)
</member>
}
)");
templ("functionName", functionName);
templ("alloc", allocationFunction());
TypePointers const& members = _type.memoryMemberTypes();
templ("allocSize", _type.memoryDataSize().str());
vector<map<string, string>> memberParams(members.size());
for (size_t i = 0; i < members.size(); ++i)
{
solAssert(members[i]->memoryHeadSize() == 32, "");
solAssert(members[i]->dataStoredIn(DataLocation::Memory), "");
memberParams[i]["zeroValue"] = zeroValueFunction(*members[i], false);
}
templ("member", memberParams);
return templ.render();
});
}
string YulUtilFunctions::conversionFunction(Type const& _from, Type const& _to)
{
if (_from.category() == Type::Category::Function)
@@ -1884,23 +1971,58 @@ string YulUtilFunctions::negateNumberCheckedFunction(Type const& _type)
});
}
string YulUtilFunctions::zeroValueFunction(Type const& _type)
string YulUtilFunctions::zeroValueFunction(Type const& _type, bool _splitFunctionTypes)
{
solUnimplementedAssert(_type.sizeOnStack() == 1, "Stacksize not yet implemented!");
solUnimplementedAssert(_type.isValueType(), "Zero value for non-value types not yet implemented");
solAssert(_type.category() != Type::Category::Mapping, "");
string const functionName = "zero_value_for_" + _type.identifier();
string const functionName = "zero_value_for_" + string(_splitFunctionTypes ? "split_" : "") + _type.identifier();
return m_functionCollector.createFunction(functionName, [&]() {
return Whiskers(R"(
function <functionName>() -> ret {
<body>
}
)")
FunctionType const* fType = dynamic_cast<FunctionType const*>(&_type);
if (fType && fType->kind() == FunctionType::Kind::External && _splitFunctionTypes)
return Whiskers(R"(
function <functionName>() -> retAddress, retFunction {
retAddress := 0
retFunction := 0
}
)")
("functionName", functionName)
("body", "ret := 0x0")
.render();
});
Whiskers templ(R"(
function <functionName>() -> ret {
ret := <zeroValue>
}
)");
templ("functionName", functionName);
if (_type.isValueType())
{
solAssert((
_type.hasSimpleZeroValueInMemory() ||
(fType && (fType->kind() == FunctionType::Kind::Internal || fType->kind() == FunctionType::Kind::External))
), "");
templ("zeroValue", "0");
}
else
{
solAssert(_type.dataStoredIn(DataLocation::Memory), "");
if (auto const* arrayType = dynamic_cast<ArrayType const*>(&_type))
{
if (_type.isDynamicallySized())
templ("zeroValue", to_string(CompilerUtils::zeroPointer));
else
templ("zeroValue", allocateAndInitializeMemoryArrayFunction(*arrayType) + "(" + to_string(unsigned(arrayType->length())) + ")");
}
else if (auto const* structType = dynamic_cast<StructType const*>(&_type))
templ("zeroValue", allocateAndInitializeMemoryStructFunction(*structType) + "()");
else
solUnimplementedAssert(false, "");
}
return templ.render();
});
}
string YulUtilFunctions::storageSetToZeroFunction(Type const& _type)
+24 -4
View File
@@ -37,6 +37,7 @@ class Type;
class ArrayType;
class MappingType;
class IntegerType;
class StructType;
/**
* Component that can generate various useful Yul functions.
@@ -154,6 +155,7 @@ public:
/// 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
/// a memory pointer or a calldata pointer to the slot number / memory pointer / calldata pointer
/// for the data position of an array which is stored in that slot / memory area / calldata area.
@@ -250,10 +252,27 @@ public:
/// Return value: pointer
std::string allocationFunction();
/// @returns the name of a function that allocates a memory array.
/// @returns the name of a function that zeroes an array.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that zeroes a chunk of memory.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroMemoryFunction(Type const& _type);
/// @returns the name of a function that zeroes an array
/// where the base does not have simple zero value in memory.
/// signature: (dataStart, dataSizeInBytes) ->
std::string zeroComplexMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates and zeroes a memory array.
/// For dynamic arrays it adds space for length and stores it.
/// signature: (length) -> memPtr
std::string allocateMemoryArrayFunction(ArrayType const& _type);
std::string allocateAndInitializeMemoryArrayFunction(ArrayType const& _type);
/// @returns the name of a function that allocates and zeroes a memory struct.
/// signature: (members) -> memPtr
std::string allocateAndInitializeMemoryStructFunction(StructType const& _type);
/// @returns the name of the function that converts a value of type @a _from
/// to a value of type @a _to. The resulting vale is guaranteed to be in range
@@ -288,8 +307,9 @@ public:
std::string negateNumberCheckedFunction(Type const& _type);
/// @returns the name of a function that returns the zero value for the
/// provided type
std::string zeroValueFunction(Type const& _type);
/// provided type.
/// @param _splitFunctionTypes if false, returns two zeroes
std::string zeroValueFunction(Type const& _type, bool _splitFunctionTypes = true);
/// @returns the name of a function that will set the given storage item to
/// zero
@@ -22,6 +22,7 @@
#include <libsolidity/codegen/YulUtilFunctions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/TypeProvider.h>
#include <libsolutil/Whiskers.h>
#include <libsolutil/StringUtils.h>
@@ -112,9 +113,10 @@ string IRGenerationContext::internalDispatch(size_t _in, size_t _out)
for (auto const& contract: mostDerivedContract().annotation().linearizedBaseContracts)
for (FunctionDefinition const* function: contract->definedFunctions())
if (
FunctionType const* functionType = TypeProvider::function(*function)->asCallableFunction(false);
!function->isConstructor() &&
function->parameters().size() == _in &&
function->returnParameters().size() == _out
TupleType(functionType->parameterTypes()).sizeOnStack() == _in &&
TupleType(functionType->returnParameterTypes()).sizeOnStack() == _out
)
{
// 0 is reserved for uninitialized function pointers
+13
View File
@@ -133,6 +133,7 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
return m_context.functionCollector().createFunction(functionName, [&]() {
Whiskers t(R"(
function <functionName>(<params>) <returns> {
<initReturnVariables>
<body>
}
)");
@@ -142,9 +143,14 @@ string IRGenerator::generateFunction(FunctionDefinition const& _function)
params += (params.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
t("params", params);
string retParams;
string retInit;
for (auto const& varDecl: _function.returnParameters())
{
retParams += (retParams.empty() ? "" : ", ") + m_context.addLocalVariable(*varDecl).commaSeparatedList();
retInit += generateInitialAssignment(*varDecl);
}
t("returns", retParams.empty() ? "" : " -> " + retParams);
t("initReturnVariables", retInit);
t("body", generate(_function.body()));
return t.render();
});
@@ -226,6 +232,13 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl)
}
}
string IRGenerator::generateInitialAssignment(VariableDeclaration const& _varDecl)
{
IRGeneratorForStatements generator(m_context, m_utils);
generator.initializeLocalVar(_varDecl);
return generator.code();
}
string IRGenerator::constructorCode(ContractDefinition const& _contract)
{
// Initialization of state variables in base-to-derived order.
+3
View File
@@ -61,6 +61,9 @@ private:
/// Generates a getter for the given declaration and returns its name
std::string generateGetter(VariableDeclaration const& _varDecl);
/// Generates code that assigns the initial value of the respective type.
std::string generateInitialAssignment(VariableDeclaration const& _varDecl);
std::string constructorCode(ContractDefinition const& _contract);
std::string deployCode(ContractDefinition const& _contract);
std::string callValueCheck();
@@ -154,6 +154,19 @@ void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _va
}
}
void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl)
{
solAssert(m_context.isLocalVariable(_varDecl), "Must be a local variable.");
auto const* type = _varDecl.type();
if (auto const* refType = dynamic_cast<ReferenceType const*>(type))
if (refType->dataStoredIn(DataLocation::Storage) && refType->isPointer())
return;
IRVariable zero = zeroValue(*type);
assign(m_context.localVariable(_varDecl), zero);
}
void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _varDeclStatement)
{
if (Expression const* expression = _varDeclStatement.initialValue())
@@ -179,7 +192,10 @@ void IRGeneratorForStatements::endVisit(VariableDeclarationStatement const& _var
else
for (auto const& decl: _varDeclStatement.declarations())
if (decl)
{
declare(m_context.addLocalVariable(*decl));
initializeLocalVar(*decl);
}
}
bool IRGeneratorForStatements::visit(Conditional const& _conditional)
@@ -670,7 +686,7 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
IRVariable value = convert(*arguments[0], *TypeProvider::uint256());
define(_functionCall) <<
m_utils.allocateMemoryArrayFunction(arrayType) <<
m_utils.allocateAndInitializeMemoryArrayFunction(arrayType) <<
"(" <<
value.commaSeparatedList() <<
")\n";
@@ -1439,6 +1455,12 @@ std::ostream& IRGeneratorForStatements::define(IRVariable const& _var)
return m_code;
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
{
if (_var.type().sizeOnStack() > 0)
m_code << "let " << _var.commaSeparatedList() << "\n";
}
void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable const& _rhs, bool _declare)
{
string output;
@@ -1458,10 +1480,15 @@ void IRGeneratorForStatements::declareAssign(IRVariable const& _lhs, IRVariable
_rhs.commaSeparatedList() <<
")\n";
}
void IRGeneratorForStatements::declare(IRVariable const& _var)
IRVariable IRGeneratorForStatements::zeroValue(Type const& _type, bool _splitFunctionTypes)
{
if (_var.type().sizeOnStack() > 0)
m_code << "let " << _var.commaSeparatedList() << "\n";
IRVariable irVar{
"zero_value_for_type_" + _type.identifier() + m_context.newYulVariable(),
_type
};
define(irVar) << m_utils.zeroValueFunction(_type, _splitFunctionTypes) << "()\n";
return irVar;
}
void IRGeneratorForStatements::appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr)
@@ -46,6 +46,8 @@ public:
/// Generates code to initialize the given state variable.
void initializeStateVar(VariableDeclaration const& _varDecl);
/// Generates code to initialize the given local variable.
void initializeLocalVar(VariableDeclaration const& _varDecl);
void endVisit(VariableDeclarationStatement const& _variableDeclaration) override;
bool visit(Conditional const& _conditional) override;
@@ -100,6 +102,11 @@ private:
void declareAssign(IRVariable const& _var, IRVariable const& _value, bool _define);
/// @returns an IRVariable with the zero
/// value of @a _type.
/// @param _splitFunctionTypes if false, returns two zeroes
IRVariable zeroValue(Type const& _type, bool _splitFunctionTypes = true);
void appendAndOrOperatorCode(BinaryOperation const& _binOp);
void appendSimpleUnaryOperation(UnaryOperation const& _operation, Expression const& _expr);