From debcc8c0564430938c5600629ce1208e080a3cff Mon Sep 17 00:00:00 2001 From: Mathias Baumann Date: Tue, 14 Apr 2020 10:52:27 +0200 Subject: [PATCH 1/4] Add setimmutable and loadimmutable to dialect. --- libyul/AsmAnalysis.cpp | 14 ++-- libyul/backends/evm/AbstractAssembly.h | 5 ++ libyul/backends/evm/AsmCodeGen.cpp | 10 +++ libyul/backends/evm/AsmCodeGen.h | 3 + libyul/backends/evm/EVMAssembly.cpp | 10 +++ libyul/backends/evm/EVMAssembly.h | 3 + libyul/backends/evm/EVMCodeTransform.cpp | 8 +- libyul/backends/evm/EVMDialect.cpp | 83 +++++++++++++++---- libyul/backends/evm/EVMDialect.h | 7 +- libyul/backends/evm/NoOutputAssembly.cpp | 21 ++++- libyul/backends/evm/NoOutputAssembly.h | 3 + .../syntaxTests/inlineAssembly/immutables.sol | 11 +++ 12 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 test/libsolidity/syntaxTests/inlineAssembly/immutables.sol diff --git a/libyul/AsmAnalysis.cpp b/libyul/AsmAnalysis.cpp index ca3f88c753e7..7858b70c4e12 100644 --- a/libyul/AsmAnalysis.cpp +++ b/libyul/AsmAnalysis.cpp @@ -306,11 +306,15 @@ vector AsmAnalyzer::operator()(FunctionCall const& _funCall) _funCall.functionName.location, "Function expects direct literals as arguments." ); - else if (!m_dataNames.count(std::get(arg).value)) - typeError( - _funCall.functionName.location, - "Unknown data object \"" + std::get(arg).value.str() + "\"." - ); + else if ( + _funCall.functionName.name.str() == "datasize" || + _funCall.functionName.name.str() == "dataoffset" + ) + if (!m_dataNames.count(std::get(arg).value)) + typeError( + _funCall.functionName.location, + "Unknown data object \"" + std::get(arg).value.str() + "\"." + ); } } std::reverse(argTypes.begin(), argTypes.end()); diff --git a/libyul/backends/evm/AbstractAssembly.h b/libyul/backends/evm/AbstractAssembly.h index 9187bb88d481..2f86270b0f7e 100644 --- a/libyul/backends/evm/AbstractAssembly.h +++ b/libyul/backends/evm/AbstractAssembly.h @@ -105,6 +105,11 @@ class AbstractAssembly virtual void appendDataSize(SubID _sub) = 0; /// Appends the given data to the assembly and returns its ID. virtual SubID appendData(bytes const& _data) = 0; + + /// Appends loading an immutable variable. + virtual void appendImmutable(std::string const& _identifier) = 0; + /// Appends an assignment to an immutable variable. + virtual void appendImmutableAssignment(std::string const& _identifier) = 0; }; enum class IdentifierContext { LValue, RValue, VariableDeclaration }; diff --git a/libyul/backends/evm/AsmCodeGen.cpp b/libyul/backends/evm/AsmCodeGen.cpp index a864b0a09977..eefff5b18229 100644 --- a/libyul/backends/evm/AsmCodeGen.cpp +++ b/libyul/backends/evm/AsmCodeGen.cpp @@ -172,6 +172,16 @@ AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data) return subID; } +void EthAssemblyAdapter::appendImmutable(std::string const& _identifier) +{ + m_assembly.appendImmutable(_identifier); +} + +void EthAssemblyAdapter::appendImmutableAssignment(std::string const& _identifier) +{ + m_assembly.appendImmutableAssignment(_identifier); +} + EthAssemblyAdapter::LabelID EthAssemblyAdapter::assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag) { u256 id = _tag.data(); diff --git a/libyul/backends/evm/AsmCodeGen.h b/libyul/backends/evm/AsmCodeGen.h index baab37b4c8a9..e126ba2c08d4 100644 --- a/libyul/backends/evm/AsmCodeGen.h +++ b/libyul/backends/evm/AsmCodeGen.h @@ -61,6 +61,9 @@ class EthAssemblyAdapter: public AbstractAssembly void appendDataSize(SubID _sub) override; SubID appendData(bytes const& _data) override; + void appendImmutable(std::string const& _identifier) override; + void appendImmutableAssignment(std::string const& _identifier) override; + private: static LabelID assemblyTagToIdentifier(evmasm::AssemblyItem const& _tag); diff --git a/libyul/backends/evm/EVMAssembly.cpp b/libyul/backends/evm/EVMAssembly.cpp index 70711ffd610f..47f7509a3902 100644 --- a/libyul/backends/evm/EVMAssembly.cpp +++ b/libyul/backends/evm/EVMAssembly.cpp @@ -215,6 +215,16 @@ AbstractAssembly::SubID EVMAssembly::appendData(bytes const&) yulAssert(false, "Data not implemented."); } +void EVMAssembly::appendImmutable(std::string const&) +{ + yulAssert(false, "loadimmutable not implemented."); +} + +void EVMAssembly::appendImmutableAssignment(std::string const&) +{ + yulAssert(false, "setimmutable not implemented."); +} + void EVMAssembly::updateReference(size_t pos, size_t size, u256 value) { yulAssert(m_bytecode.size() >= size && pos <= m_bytecode.size() - size, ""); diff --git a/libyul/backends/evm/EVMAssembly.h b/libyul/backends/evm/EVMAssembly.h index 744d8738b9ab..da4f5c0379f4 100644 --- a/libyul/backends/evm/EVMAssembly.h +++ b/libyul/backends/evm/EVMAssembly.h @@ -83,6 +83,9 @@ class EVMAssembly: public AbstractAssembly void appendDataSize(SubID _sub) override; SubID appendData(bytes const& _data) override; + void appendImmutable(std::string const& _identifier) override; + void appendImmutableAssignment(std::string const& _identifier) override; + /// Resolves references inside the bytecode and returns the linker object. evmasm::LinkerObject finalize(); diff --git a/libyul/backends/evm/EVMCodeTransform.cpp b/libyul/backends/evm/EVMCodeTransform.cpp index 6ca55821d2d4..b9c5cb0ee543 100644 --- a/libyul/backends/evm/EVMCodeTransform.cpp +++ b/libyul/backends/evm/EVMCodeTransform.cpp @@ -257,13 +257,9 @@ void CodeTransform::operator()(FunctionCall const& _call) yulAssert(m_scope, ""); if (BuiltinFunctionForEVM const* builtin = m_dialect.builtin(_call.functionName.name)) - { - builtin->generateCode(_call, m_assembly, m_builtinContext, [&]() { - for (auto const& arg: _call.arguments | boost::adaptors::reversed) - visitExpression(arg); - m_assembly.setSourceLocation(_call.location); + builtin->generateCode(_call, m_assembly, m_builtinContext, [&](Expression const& _expression) { + visitExpression(_expression); }); - } else { m_assembly.setSourceLocation(_call.location); diff --git a/libyul/backends/evm/EVMDialect.cpp b/libyul/backends/evm/EVMDialect.cpp index 52cd1d23f48a..b5b5eda6ed81 100644 --- a/libyul/backends/evm/EVMDialect.cpp +++ b/libyul/backends/evm/EVMDialect.cpp @@ -41,6 +41,20 @@ using namespace solidity::util; namespace { + +void visitArguments( + AbstractAssembly& _assembly, + FunctionCall const& _call, + function _visitExpression +) +{ + for (auto const& arg: _call.arguments | boost::adaptors::reversed) + _visitExpression(arg); + + _assembly.setSourceLocation(_call.location); +} + + pair createEVMFunction( string const& _name, evmasm::Instruction _instruction @@ -58,12 +72,12 @@ pair createEVMFunction( f.literalArguments.reset(); f.instruction = _instruction; f.generateCode = [_instruction]( - FunctionCall const&, + FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&, - std::function _visitArguments + std::function _visitExpression ) { - _visitArguments(); + visitArguments(_assembly, _call, _visitExpression); _assembly.appendInstruction(_instruction); }; @@ -76,7 +90,7 @@ pair createFunction( size_t _returns, SideEffects _sideEffects, vector _literalArguments, - std::function)> _generateCode + std::function)> _generateCode ) { solAssert(_literalArguments.size() == _params || _literalArguments.empty(), ""); @@ -116,7 +130,7 @@ map createBuiltins(langutil::EVMVersion _evmVe FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, - function + std::function ) { yulAssert(_context.currentObject, "No object available."); yulAssert(_call.arguments.size() == 1, ""); @@ -137,7 +151,7 @@ map createBuiltins(langutil::EVMVersion _evmVe FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext& _context, - std::function + std::function ) { yulAssert(_context.currentObject, "No object available."); yulAssert(_call.arguments.size() == 1, ""); @@ -161,21 +175,58 @@ map createBuiltins(langutil::EVMVersion _evmVe SideEffects{false, false, false, false, true}, {}, []( - FunctionCall const&, + FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&, - std::function _visitArguments + std::function _visitExpression ) { - _visitArguments(); + visitArguments(_assembly, _call, _visitExpression); _assembly.appendInstruction(evmasm::Instruction::CODECOPY); } )); + builtins.emplace(createFunction( + "setimmutable", + 2, + 0, + SideEffects{false, false, false, false, true}, + {true, false}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext&, + std::function _visitExpression + ) { + solAssert(_call.arguments.size() == 2, ""); + + _visitExpression(_call.arguments[1]); + _assembly.setSourceLocation(_call.location); + YulString identifier = std::get(_call.arguments.front()).value; + _assembly.appendImmutableAssignment(identifier.str()); + } + )); + builtins.emplace(createFunction( + "loadimmutable", + 1, + 1, + SideEffects{}, + {true}, + []( + FunctionCall const& _call, + AbstractAssembly& _assembly, + BuiltinContext&, + std::function + ) { + solAssert(_call.arguments.size() == 1, ""); + _assembly.appendImmutable(std::get(_call.arguments.front()).value.str()); + } + )); } return builtins; } } + EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess): m_objectAccess(_objectAccess), m_evmVersion(_evmVersion), @@ -268,23 +319,23 @@ EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectA m_functions["popbool"_yulstring].name = "popbool"_yulstring; m_functions["popbool"_yulstring].parameters = {"bool"_yulstring}; m_functions.insert(createFunction("bool_to_u256", 1, 1, {}, {}, []( - FunctionCall const&, - AbstractAssembly&, + FunctionCall const& _call, + AbstractAssembly& _assembly, BuiltinContext&, - std::function _visitArguments + std::function _visitExpression ) { - _visitArguments(); + visitArguments(_assembly, _call, _visitExpression); })); m_functions["bool_to_u256"_yulstring].parameters = {"bool"_yulstring}; m_functions["bool_to_u256"_yulstring].returns = {"u256"_yulstring}; m_functions.insert(createFunction("u256_to_bool", 1, 1, {}, {}, []( - FunctionCall const&, + FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&, - std::function _visitArguments + std::function _visitExpression ) { // A value larger than 1 causes an invalid instruction. - _visitArguments(); + visitArguments(_assembly, _call, _visitExpression); _assembly.appendConstant(2); _assembly.appendInstruction(evmasm::Instruction::DUP2); _assembly.appendInstruction(evmasm::Instruction::LT); diff --git a/libyul/backends/evm/EVMDialect.h b/libyul/backends/evm/EVMDialect.h index 00852fe34b15..68f0b7046fd7 100644 --- a/libyul/backends/evm/EVMDialect.h +++ b/libyul/backends/evm/EVMDialect.h @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -49,9 +50,9 @@ struct BuiltinFunctionForEVM: public BuiltinFunction { std::optional instruction; /// Function to generate code for the given function call and append it to the abstract - /// assembly. The fourth parameter is called to visit (and generate code for) the arguments - /// from right to left. - std::function)> generateCode; + /// assembly. The fourth parameter is called to visit (and generate code for) the given + /// argument. + std::function)> generateCode; }; diff --git a/libyul/backends/evm/NoOutputAssembly.cpp b/libyul/backends/evm/NoOutputAssembly.cpp index 8d01c2c5e649..8e26014a9988 100644 --- a/libyul/backends/evm/NoOutputAssembly.cpp +++ b/libyul/backends/evm/NoOutputAssembly.cpp @@ -19,10 +19,14 @@ */ #include + #include #include +#include + + using namespace std; using namespace solidity; using namespace solidity::yul; @@ -142,6 +146,17 @@ AbstractAssembly::SubID NoOutputAssembly::appendData(bytes const&) return 1; } + +void NoOutputAssembly::appendImmutable(std::string const&) +{ + yulAssert(false, "loadimmutable not implemented."); +} + +void NoOutputAssembly::appendImmutableAssignment(std::string const&) +{ + yulAssert(false, "setimmutable not implemented."); +} + NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom): EVMDialect(_copyFrom.evmVersion(), _copyFrom.providesObjectAccess()) { @@ -149,9 +164,11 @@ NoOutputEVMDialect::NoOutputEVMDialect(EVMDialect const& _copyFrom): { size_t parameters = fun.second.parameters.size(); size_t returns = fun.second.returns.size(); - fun.second.generateCode = [=](FunctionCall const&, AbstractAssembly& _assembly, BuiltinContext&, std::function _visitArguments) + fun.second.generateCode = [=](FunctionCall const& _call, AbstractAssembly& _assembly, BuiltinContext&, std::function _visitExpression) { - _visitArguments(); + for (auto const& arg: _call.arguments | boost::adaptors::reversed) + _visitExpression(arg); + for (size_t i = 0; i < parameters; i++) _assembly.appendInstruction(evmasm::Instruction::POP); diff --git a/libyul/backends/evm/NoOutputAssembly.h b/libyul/backends/evm/NoOutputAssembly.h index d72f9ea66000..7101a30f5a74 100644 --- a/libyul/backends/evm/NoOutputAssembly.h +++ b/libyul/backends/evm/NoOutputAssembly.h @@ -71,6 +71,9 @@ class NoOutputAssembly: public AbstractAssembly void appendDataSize(SubID _sub) override; SubID appendData(bytes const& _data) override; + void appendImmutable(std::string const& _identifier) override; + void appendImmutableAssignment(std::string const& _identifier) override; + private: bool m_evm15 = false; ///< if true, switch to evm1.5 mode int m_stackHeight = 0; diff --git a/test/libsolidity/syntaxTests/inlineAssembly/immutables.sol b/test/libsolidity/syntaxTests/inlineAssembly/immutables.sol new file mode 100644 index 000000000000..17b30b8d8871 --- /dev/null +++ b/test/libsolidity/syntaxTests/inlineAssembly/immutables.sol @@ -0,0 +1,11 @@ +contract C { + function f() public pure { + assembly { + setimmutable("abc", 0) + loadimmutable("abc") + } + } +} +// ---- +// DeclarationError: (63-75): Function not found. +// DeclarationError: (92-105): Function not found. From 51ccb1519fdb454ef9b4dc88d14da0b9a77d0f0e Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 2 Apr 2020 20:06:52 +0200 Subject: [PATCH 2/4] Yul codegen for immutables. --- .../codegen/ir/IRGenerationContext.cpp | 31 ++++++++ libsolidity/codegen/ir/IRGenerationContext.h | 17 ++++ libsolidity/codegen/ir/IRGenerator.cpp | 78 +++++++++++++++---- .../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, 167 insertions(+), 32 deletions(-) diff --git a/libsolidity/codegen/ir/IRGenerationContext.cpp b/libsolidity/codegen/ir/IRGenerationContext.cpp index 43e9f95e2932..ed3b989d0ef9 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 0a07f2f29ffb..a7eab7cebdf2 100644 --- a/libsolidity/codegen/ir/IRGenerationContext.h +++ b/libsolidity/codegen/ir/IRGenerationContext.h @@ -81,6 +81,17 @@ class IRGenerationContext 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 @@ class IRGenerationContext 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 aed373c1018f..fb466f3bafbd 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); - - return Whiskers(R"( - function () -> rval { - rval := () - } - )") - ("functionName", functionName) - ("readStorage", m_utils.readFromStorage(*type, slot_offset.second, false)) - ("slot", slot_offset.first.str()) - .render(); + 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(); + } }); } } @@ -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 755110c27b0a..84cf53cd2763 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 02d46b6e4146..c5eadc9b2753 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 3f71a31e0fe0..030a3544cc42 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 bb4b191cf9f6..c0997744ed00 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 f61009f60040..bca0ee8895cd 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 0673aafb5672..0119a90a2e22 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 387541066e56..a800ab90407a 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 d83da476d423..442ef56360c4 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 From 3738cff6e6b7a011df48830358e247fbd6a7e346 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 27 Apr 2020 19:35:35 +0200 Subject: [PATCH 3/4] Test updates. --- test/cmdlineTests/standard_ir_requested/output.json | 1 + test/cmdlineTests/yul_string_format_ascii/output.json | 1 + test/cmdlineTests/yul_string_format_ascii_bytes32/output.json | 1 + .../yul_string_format_ascii_bytes32_from_number/output.json | 1 + test/cmdlineTests/yul_string_format_ascii_long/output.json | 1 + test/cmdlineTests/yul_string_format_hex/output.json | 1 + 6 files changed, 6 insertions(+) diff --git a/test/cmdlineTests/standard_ir_requested/output.json b/test/cmdlineTests/standard_ir_requested/output.json index bf9b20ed82a3..69b95a683695 100644 --- a/test/cmdlineTests/standard_ir_requested/output.json +++ b/test/cmdlineTests/standard_ir_requested/output.json @@ -14,6 +14,7 @@ object \"C_6\" { constructor_C_6() codecopy(0, dataoffset(\"C_6_deployed\"), datasize(\"C_6_deployed\")) + return(0, datasize(\"C_6_deployed\")) function constructor_C_6() { diff --git a/test/cmdlineTests/yul_string_format_ascii/output.json b/test/cmdlineTests/yul_string_format_ascii/output.json index 81fa10ca70d7..215146d1d9ea 100644 --- a/test/cmdlineTests/yul_string_format_ascii/output.json +++ b/test/cmdlineTests/yul_string_format_ascii/output.json @@ -14,6 +14,7 @@ object \"C_10\" { constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) function constructor_C_10() { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json index a171f87b59ce..7b2291843f3c 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32/output.json @@ -14,6 +14,7 @@ object \"C_10\" { constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) function constructor_C_10() { diff --git a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json index 3dccf690dc22..ef647f39e24f 100644 --- a/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_bytes32_from_number/output.json @@ -14,6 +14,7 @@ object \"C_10\" { constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) function constructor_C_10() { diff --git a/test/cmdlineTests/yul_string_format_ascii_long/output.json b/test/cmdlineTests/yul_string_format_ascii_long/output.json index 77360e5a4631..8243cd93ef95 100644 --- a/test/cmdlineTests/yul_string_format_ascii_long/output.json +++ b/test/cmdlineTests/yul_string_format_ascii_long/output.json @@ -14,6 +14,7 @@ object \"C_10\" { constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) function constructor_C_10() { diff --git a/test/cmdlineTests/yul_string_format_hex/output.json b/test/cmdlineTests/yul_string_format_hex/output.json index 81853dadbe2b..70f2fc3dee9d 100644 --- a/test/cmdlineTests/yul_string_format_hex/output.json +++ b/test/cmdlineTests/yul_string_format_hex/output.json @@ -14,6 +14,7 @@ object \"C_10\" { constructor_C_10() codecopy(0, dataoffset(\"C_10_deployed\"), datasize(\"C_10_deployed\")) + return(0, datasize(\"C_10_deployed\")) function constructor_C_10() { From ea7e751750c7387b300613a15df25748058bbaf8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 4 May 2020 15:05:14 +0200 Subject: [PATCH 4/4] Documentation. --- docs/yul.rst | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/docs/yul.rst b/docs/yul.rst index 2fcae1175612..6e8654ff5ccf 100644 --- a/docs/yul.rst +++ b/docs/yul.rst @@ -885,13 +885,6 @@ the ``dup`` and ``swap`` instructions as well as ``jump`` instructions, labels a | gaslimit() | | F | block gas limit of the current block | +-------------------------+-----+---+-----------------------------------------------------------------+ -There are three additional functions, ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``, -which are used to access other parts of a Yul object. - -``datasize`` and ``dataoffset`` can only take string literals (the names of other objects) -as arguments and return the size and offset in the data area, respectively. -For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. - .. _yul-call-return-area: .. note:: @@ -903,6 +896,32 @@ For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. The remaining bytes will retain their values as of before the call. If the call fails (it returns ``0``), nothing is written to that area, but you can still retrieve the failure data using ``returndatacopy``. + +In some internal dialects, there are additional functions: + +datasize, dataoffset, datacopy +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The functions ``datasize(x)``, ``dataoffset(x)`` and ``datacopy(t, f, l)``, +are used to access other parts of a Yul object. + +``datasize`` and ``dataoffset`` can only take string literals (the names of other objects) +as arguments and return the size and offset in the data area, respectively. +For the EVM, the ``datacopy`` function is equivalent to ``codecopy``. + + +setimmutable, loadimmutable +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The functions ``setimmutable("name", value)`` and ``loadimmutable("name")`` are +used for the immutable mechanism in Solidity and do not nicely map to pur Yul. +The function ``setimmutable`` assumes that the runtime code of a contract +is currently copied to memory at offsot zero. The call to ``setimmutable("name", value)`` +will store ``value`` at all points in memory that contain a call to +``loadimmutable("name")``. + + + .. _yul-object: Specification of Yul Object