From 51ccb1519fdb454ef9b4dc88d14da0b9a77d0f0e Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 2 Apr 2020 20:06:52 +0200 Subject: [PATCH] Yul codegen for immutables. --- .../codegen/ir/IRGenerationContext.cpp | 31 ++++++++ libsolidity/codegen/ir/IRGenerationContext.h | 17 +++++ libsolidity/codegen/ir/IRGenerator.cpp | 76 +++++++++++++++---- .../codegen/ir/IRGeneratorForStatements.cpp | 53 +++++++++---- libsolidity/codegen/ir/IRLValue.h | 8 +- .../immutable/assign_at_declaration.sol | 2 + .../semanticTests/immutable/getter.sol | 2 + .../semanticTests/immutable/inheritance.sol | 2 + .../immutable/internal_function_pointer.sol | 2 + .../semanticTests/immutable/stub.sol | 2 + .../semanticTests/immutable/use_scratch.sol | 2 + 11 files changed, 166 insertions(+), 31 deletions(-) diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 43e9f95e2..ed3b989d0 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.cpp +++ b/libsolidity/codegen/ir/IRGenerationContext.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -76,6 +77,36 @@ IRVariable const& IRGenerationContext::localVariable(VariableDeclaration const& return m_localVariables.at(&_varDecl); } +void IRGenerationContext::registerImmutableVariable(VariableDeclaration const& _variable) +{ + solAssert(_variable.immutable(), "Attempted to register a non-immutable variable as immutable."); + solUnimplementedAssert( + _variable.annotation().type->isValueType(), + "Only immutable variables of value type are supported." + ); + solAssert(m_reservedMemory.has_value(), "Reserved memory has already been reset."); + m_immutableVariables[&_variable] = CompilerUtils::generalPurposeMemoryStart + *m_reservedMemory; + solAssert(_variable.annotation().type->memoryHeadSize() == 32, "Memory writes might overlap."); + *m_reservedMemory += _variable.annotation().type->memoryHeadSize(); +} + +size_t IRGenerationContext::immutableMemoryOffset(VariableDeclaration const& _variable) const +{ + solAssert( + m_immutableVariables.count(&_variable), + "Unknown immutable variable: " + _variable.name() + ); + return m_immutableVariables.at(&_variable); +} + +size_t IRGenerationContext::reservedMemory() +{ + solAssert(m_reservedMemory.has_value(), "Reserved memory was used before."); + size_t reservedMemory = *m_reservedMemory; + m_reservedMemory = std::nullopt; + return reservedMemory; +} + void IRGenerationContext::addStateVariable( VariableDeclaration const& _declaration, u256 _storageOffset, diff --git a/libsolidity/codegen/ir/IRGenerationContext.h b/libsolidity/codegen/ir/IRGenerationContext.h index 0a07f2f29..a7eab7ceb 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -81,6 +81,17 @@ public: bool isLocalVariable(VariableDeclaration const& _varDecl) const { return m_localVariables.count(&_varDecl); } IRVariable const& localVariable(VariableDeclaration const& _varDecl); + /// Registers an immutable variable of the contract. + /// Should only be called at construction time. + void registerImmutableVariable(VariableDeclaration const& _varDecl); + /// @returns the reserved memory for storing the value of the + /// immutable @a _variable during contract creation. + size_t immutableMemoryOffset(VariableDeclaration const& _variable) const; + /// @returns the reserved memory and resets it to mark it as used. + /// Intended to be used only once for initializing the free memory pointer + /// to after the area used for immutables. + size_t reservedMemory(); + void addStateVariable(VariableDeclaration const& _varDecl, u256 _storageOffset, unsigned _byteOffset); bool isStateVariable(VariableDeclaration const& _varDecl) const { return m_stateVariables.count(&_varDecl); } std::pair storageLocationOfVariable(VariableDeclaration const& _varDecl) const @@ -123,6 +134,12 @@ private: OptimiserSettings m_optimiserSettings; ContractDefinition const* m_mostDerivedContract = nullptr; std::map m_localVariables; + /// Memory offsets reserved for the values of immutable variables during contract creation. + /// This map is empty in the runtime context. + std::map m_immutableVariables; + /// Total amount of reserved memory. Reserved memory is used to store + /// immutable variables during contract creation. + std::optional m_reservedMemory = {0}; /// Storage offsets of state variables std::map> m_stateVariables; MultiUseYulFunctionCollector m_functions; diff --git a/libsolidity/codegen/ir/IRGenerator.cpp b/libsolidity/codegen/ir/IRGenerator.cpp index aed373c10..fb466f3ba 100644 --- a/libsolidity/codegen/ir/IRGenerator.cpp +++ b/libsolidity/codegen/ir/IRGenerator.cpp @@ -114,6 +114,8 @@ string IRGenerator::generate( )"); resetContext(_contract); + for (VariableDeclaration const* var: ContractType(_contract).immutableVariables()) + m_context.registerImmutableVariable(*var); t("CreationObject", m_context.creationObjectName(_contract)); t("memoryInit", memoryInit()); @@ -142,6 +144,7 @@ string IRGenerator::generate( t("subObjects", subObjectSources(m_context.subObjectsCreated())); resetContext(_contract); + // Do not register immutables to avoid assignment. t("RuntimeObject", m_context.runtimeObjectName(_contract)); t("dispatch", dispatchRoutine(_contract)); generateQueuedFunctions(); @@ -200,7 +203,6 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) Type const* type = _varDecl.annotation().type; solAssert(!_varDecl.isConstant(), ""); - solAssert(!_varDecl.immutable(), ""); solAssert(_varDecl.isStateVariable(), ""); if (auto const* mappingType = dynamic_cast(type)) @@ -254,17 +256,32 @@ string IRGenerator::generateGetter(VariableDeclaration const& _varDecl) solUnimplementedAssert(type->isValueType(), ""); return m_context.functionCollector().createFunction(functionName, [&]() { - pair slot_offset = m_context.storageLocationOfVariable(_varDecl); + if (_varDecl.immutable()) + { + solUnimplementedAssert(type->sizeOnStack() == 1, ""); + return Whiskers(R"( + function () -> rval { + rval := loadimmutable("") + } + )") + ("functionName", functionName) + ("id", to_string(_varDecl.id())) + .render(); + } + else + { + pair slot_offset = m_context.storageLocationOfVariable(_varDecl); - return Whiskers(R"( - function () -> rval { - rval := () - } - )") - ("functionName", functionName) - ("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false)) - ("slot", slot_offset.first.str()) - .render(); + return Whiskers(R"( + function () -> rval { + rval := () + } + )") + ("functionName", functionName) + ("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false)) + ("slot", slot_offset.first.str()) + .render(); + } }); } } @@ -325,7 +342,7 @@ string IRGenerator::initStateVariables(ContractDefinition const& _contract) { IRGeneratorForStatements generator{m_context, m_utils}; for (VariableDeclaration const* variable: _contract.stateVariables()) - if (!variable->isConstant() && !variable->immutable()) + if (!variable->isConstant()) generator.initializeStateVar(*variable); return generator.code(); @@ -391,10 +408,41 @@ void IRGenerator::generateImplicitConstructors(ContractDefinition const& _contra string IRGenerator::deployCode(ContractDefinition const& _contract) { Whiskers t(R"X( + <#loadImmutables> + let := mload() + + codecopy(0, dataoffset(""), datasize("")) + + <#storeImmutables> + setimmutable("", ) + + return(0, datasize("")) )X"); t("object", m_context.runtimeObjectName(_contract)); + + vector> loadImmutables; + vector> storeImmutables; + + for (VariableDeclaration const* immutable: ContractType(_contract).immutableVariables()) + { + solUnimplementedAssert(immutable->type()->isValueType(), ""); + solUnimplementedAssert(immutable->type()->sizeOnStack() == 1, ""); + string yulVar = m_context.newYulVariable(); + loadImmutables.emplace_back(map{ + {"var"s, yulVar}, + {"memoryOffset"s, to_string(m_context.immutableMemoryOffset(*immutable))} + }); + storeImmutables.emplace_back(map{ + {"var"s, yulVar}, + {"immutableName"s, to_string(immutable->id())} + }); + } + t("loadImmutables", std::move(loadImmutables)); + // reverse order to ease stack strain + reverse(storeImmutables.begin(), storeImmutables.end()); + t("storeImmutables", std::move(storeImmutables)); return t.render(); } @@ -489,9 +537,9 @@ string IRGenerator::memoryInit() // and thus can assume all memory to be zero, including the contents of // the "zero memory area" (the position CompilerUtils::zeroPointer points to). return - Whiskers{"mstore(, )"} + Whiskers{"mstore(, )"} ("memPtr", to_string(CompilerUtils::freeMemoryPointer)) - ("generalPurposeStart", to_string(CompilerUtils::generalPurposeMemoryStart)) + ("freeMemoryStart", to_string(CompilerUtils::generalPurposeMemoryStart + m_context.reservedMemory())) .render(); } diff --git a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp index 755110c27..84cf53cd2 100644 --- a/libsolidity/codegen/ir/IRGeneratorForStatements.cpp +++ b/libsolidity/codegen/ir/IRGeneratorForStatements.cpp @@ -140,20 +140,21 @@ string IRGeneratorForStatements::code() const void IRGeneratorForStatements::initializeStateVar(VariableDeclaration const& _varDecl) { - solAssert(m_context.isStateVariable(_varDecl), "Must be a state variable."); + solAssert(_varDecl.immutable() || m_context.isStateVariable(_varDecl), "Must be immutable or a state variable."); solAssert(!_varDecl.isConstant(), ""); - solAssert(!_varDecl.immutable(), ""); - if (_varDecl.value()) - { - _varDecl.value()->accept(*this); - writeToLValue(IRLValue{ - *_varDecl.annotation().type, - IRLValue::Storage{ - util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first), - m_context.storageLocationOfVariable(_varDecl).second - } - }, *_varDecl.value()); - } + if (!_varDecl.value()) + return; + + _varDecl.value()->accept(*this); + writeToLValue( + _varDecl.immutable() ? + IRLValue{*_varDecl.annotation().type, IRLValue::Immutable{&_varDecl}} : + IRLValue{*_varDecl.annotation().type, IRLValue::Storage{ + util::toCompactHexWithPrefix(m_context.storageLocationOfVariable(_varDecl).first), + m_context.storageLocationOfVariable(_varDecl).second + }}, + *_varDecl.value() + ); } void IRGeneratorForStatements::initializeLocalVar(VariableDeclaration const& _varDecl) @@ -1517,8 +1518,12 @@ void IRGeneratorForStatements::handleVariableReference( // If the value is visited twice, `defineExpression` is called twice on // the same expression. solUnimplementedAssert(!_variable.isConstant(), ""); - solUnimplementedAssert(!_variable.immutable(), ""); - if (m_context.isLocalVariable(_variable)) + if (_variable.isStateVariable() && _variable.immutable()) + setLValue(_referencingExpression, IRLValue{ + *_variable.annotation().type, + IRLValue::Immutable{&_variable} + }); + else if (m_context.isLocalVariable(_variable)) setLValue(_referencingExpression, IRLValue{ *_variable.annotation().type, IRLValue::Stack{m_context.localVariable(_variable)} @@ -1939,6 +1944,18 @@ void IRGeneratorForStatements::writeToLValue(IRLValue const& _lvalue, IRVariable } }, [&](IRLValue::Stack const& _stack) { assign(_stack.variable, _value); }, + [&](IRLValue::Immutable const& _immutable) + { + solUnimplementedAssert(_lvalue.type.isValueType(), ""); + solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1, ""); + solAssert(_lvalue.type == *_immutable.variable->type(), ""); + size_t memOffset = m_context.immutableMemoryOffset(*_immutable.variable); + + IRVariable prepared(m_context.newYulVariable(), _lvalue.type); + define(prepared, _value); + + m_code << "mstore(" << to_string(memOffset) << ", " << prepared.commaSeparatedList() << ")\n"; + }, [&](IRLValue::Tuple const& _tuple) { auto components = std::move(_tuple.components); for (size_t i = 0; i < components.size(); i++) @@ -1994,6 +2011,12 @@ IRVariable IRGeneratorForStatements::readFromLValue(IRLValue const& _lvalue) [&](IRLValue::Stack const& _stack) { define(result, _stack.variable); }, + [&](IRLValue::Immutable const& _immutable) { + solUnimplementedAssert(_lvalue.type.isValueType(), ""); + solUnimplementedAssert(_lvalue.type.sizeOnStack() == 1, ""); + solAssert(_lvalue.type == *_immutable.variable->type(), ""); + define(result) << "loadimmutable(\"" << to_string(_immutable.variable->id()) << "\")\n"; + }, [&](IRLValue::Tuple const&) { solAssert(false, "Attempted to read from tuple lvalue."); } diff --git a/libsolidity/codegen/ir/IRLValue.h b/libsolidity/codegen/ir/IRLValue.h index 02d46b6e4..c5eadc9b2 100644 --- a/libsolidity/codegen/ir/IRLValue.h +++ b/libsolidity/codegen/ir/IRLValue.h @@ -35,6 +35,10 @@ struct IRLValue { IRVariable variable; }; + struct Immutable + { + VariableDeclaration const* variable = nullptr; + }; struct Storage { std::string const slot; @@ -59,7 +63,7 @@ struct IRLValue { std::vector> components; }; - std::variant kind; + std::variant kind; }; -} \ No newline at end of file +} diff --git a/test/libsolidity/semanticTests/immutable/assign_at_declaration.sol b/test/libsolidity/semanticTests/immutable/assign_at_declaration.sol index 3f71a31e0..030a3544c 100644 --- a/test/libsolidity/semanticTests/immutable/assign_at_declaration.sol +++ b/test/libsolidity/semanticTests/immutable/assign_at_declaration.sol @@ -4,5 +4,7 @@ contract A { return a; } } +// ==== +// compileViaYul: also // ---- // f() -> 2 diff --git a/test/libsolidity/semanticTests/immutable/getter.sol b/test/libsolidity/semanticTests/immutable/getter.sol index bb4b191cf..c0997744e 100644 --- a/test/libsolidity/semanticTests/immutable/getter.sol +++ b/test/libsolidity/semanticTests/immutable/getter.sol @@ -1,5 +1,7 @@ contract C { uint immutable public x = 1; } +// ==== +// compileViaYul: also // ---- // x() -> 1 diff --git a/test/libsolidity/semanticTests/immutable/inheritance.sol b/test/libsolidity/semanticTests/immutable/inheritance.sol index f61009f60..bca0ee889 100644 --- a/test/libsolidity/semanticTests/immutable/inheritance.sol +++ b/test/libsolidity/semanticTests/immutable/inheritance.sol @@ -26,5 +26,7 @@ contract D is B, C { return (a, b, c, d); } } +// ==== +// compileViaYul: also // ---- // f() -> 4, 3, 2, 1 diff --git a/test/libsolidity/semanticTests/immutable/internal_function_pointer.sol b/test/libsolidity/semanticTests/immutable/internal_function_pointer.sol index 0673aafb5..0119a90a2 100644 --- a/test/libsolidity/semanticTests/immutable/internal_function_pointer.sol +++ b/test/libsolidity/semanticTests/immutable/internal_function_pointer.sol @@ -10,6 +10,8 @@ contract C { return z(); } } +// ==== +// compileViaYul: also // ---- // f() -> 7 // callZ() -> 7 diff --git a/test/libsolidity/semanticTests/immutable/stub.sol b/test/libsolidity/semanticTests/immutable/stub.sol index 387541066..a800ab904 100644 --- a/test/libsolidity/semanticTests/immutable/stub.sol +++ b/test/libsolidity/semanticTests/immutable/stub.sol @@ -9,5 +9,7 @@ contract C { return (x+x,y); } } +// ==== +// compileViaYul: also // ---- // f() -> 84, 23 diff --git a/test/libsolidity/semanticTests/immutable/use_scratch.sol b/test/libsolidity/semanticTests/immutable/use_scratch.sol index d83da476d..442ef5636 100644 --- a/test/libsolidity/semanticTests/immutable/use_scratch.sol +++ b/test/libsolidity/semanticTests/immutable/use_scratch.sol @@ -13,6 +13,8 @@ contract C { return (x+x,y); } } +// ==== +// compileViaYul: also // ---- // constructor(): 3 -> // f() -> 84, 23