diff options
84 files changed, 1519 insertions, 183 deletions
diff --git a/Changelog.md b/Changelog.md index 79ec90a0..bdfd9746 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.5.3 (unreleased) Language Features: + * Provide access to creation and runtime code of contracts via ``type(C).creationCode`` / ``type(C).runtimeCode``. Compiler Features: @@ -8,6 +9,7 @@ Compiler Features: Bugfixes: + * TypeChecker: Return type error if fixed point encoding is attempted instead of throwing ``UnimplementedFeatureError``. * Yul: Check that arguments to ``dataoffset`` and ``datasize`` are literals at parse time and properly take this into account in the optimizer. * Yul: Parse number literals for detecting duplicate switch cases. * Yul: Require switch cases to have the same type. diff --git a/docs/common-patterns.rst b/docs/common-patterns.rst index 3704b730..65bf7f44 100644 --- a/docs/common-patterns.rst +++ b/docs/common-patterns.rst @@ -117,7 +117,7 @@ to read the data, so will everyone else. You can restrict read access to your contract's state by **other contracts**. That is actually the default -unless you declare make your state variables ``public``. +unless you declare your state variables ``public``. Furthermore, you can restrict who can make modifications to your contract's state or call your contract's diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 5a6f3875..69124c77 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -385,6 +385,8 @@ Global Variables - ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei - ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure - ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure +- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`. +- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`. .. note:: Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness, @@ -445,7 +447,7 @@ These keywords are reserved in Solidity. They might become part of the syntax in ``abstract``, ``after``, ``alias``, ``apply``, ``auto``, ``case``, ``catch``, ``copyof``, ``default``, ``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``, ``mutable``, ``null``, ``of``, ``override``, ``partial``, ``promise``, ``reference``, ``relocatable``, -``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``type``, ``typedef``, ``typeof``, +``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``typedef``, ``typeof``, ``unchecked``. Language Grammar diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 077587dd..09db1423 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -329,6 +329,9 @@ Contracts do not support any operators. The members of contract types are the external functions of the contract including public state variables. +For a contract ``C`` you can use ``type(C)`` to access +:ref:`type information<meta-type>` about the contract. + .. index:: byte array, bytes32 Fixed-size byte arrays diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index c47f257b..ce7706c1 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -244,3 +244,33 @@ Furthermore, all functions of the current contract are callable directly includi .. note:: Prior to version 0.5.0, there was a function called ``suicide`` with the same semantics as ``selfdestruct``. + +.. index:: type, creationCode, runtimeCode + +.. _meta-type: + +Type Information +---------------- + +The expression ``type(X)`` can be used to retrieve information about the +type ``X``. Currently, there is limited support for this feature, but +it might be expanded in the future. The following properties are +available for a conract type ``C``: + +``type(C).creationCode``: + Memory byte array that contains the creation bytecode of the contract. + This can be used in inline assembly to build custom creation routines, + especially by using the ``create2`` opcode. + This property can **not** be accessed in the contract itself or any + derived contract. It causes the bytecode to be included in the bytecode + of the call site and thus circular references like that are not possible. + +``type(C).runtimeCode``: + Memory byte array that contains the runtime bytecode of the contract. + This is the code that is usually deployed by the constructor of ``C``. + If ``C`` has a constructor that uses inline assembly, this might be + different from the actually deployed bytecode. Also note that libraries + modify their runtime bytecode at time of deployment to guard against + regular calls. + The same restrictions as with ``.creationCode`` also apply for this + property. diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 1d668f26..98331936 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -263,6 +263,59 @@ void iterateReplacing(std::vector<T>& _vector, const F& _f) _vector = std::move(modifiedVector); } + +namespace detail +{ +template <typename T, typename F, std::size_t... I> +void iterateReplacingWindow(std::vector<T>& _vector, F const& _f, std::index_sequence<I...>) +{ + // Concept: _f must be Callable, must accept sizeof...(I) parameters of type T&, must return optional<vector<T>> + bool useModified = false; + std::vector<T> modifiedVector; + size_t i = 0; + for (; i + sizeof...(I) <= _vector.size(); ++i) + { + if (boost::optional<std::vector<T>> r = _f(_vector[i + I]...)) + { + if (!useModified) + { + std::move(_vector.begin(), _vector.begin() + i, back_inserter(modifiedVector)); + useModified = true; + } + modifiedVector += std::move(*r); + i += sizeof...(I) - 1; + } + else if (useModified) + modifiedVector.emplace_back(std::move(_vector[i])); + } + if (useModified) + { + for (; i < _vector.size(); ++i) + modifiedVector.emplace_back(std::move(_vector[i])); + _vector = std::move(modifiedVector); + } +} + +} + +/// Function that iterates over the vector @param _vector, +/// calling the function @param _f on sequences of @tparam N of its +/// elements. If @param _f returns a vector, these elements are replaced by +/// the returned vector and the iteration continues with the next @tparam N elements. +/// If the function does not return a vector, the iteration continues with an overlapping +/// sequence of @tparam N elements that starts with the second element of the previous +/// iteration. +/// During the iteration, the original vector is only valid +/// on the current element and after that. The actual replacement takes +/// place at the end, but already visited elements might be invalidated. +/// If nothing is replaced, no copy is performed. +template <std::size_t N, typename T, typename F> +void iterateReplacingWindow(std::vector<T>& _vector, F const& _f) +{ + // Concept: _f must be Callable, must accept N parameters of type T&, must return optional<vector<T>> + detail::iterateReplacingWindow(_vector, _f, std::make_index_sequence<N>{}); +} + /// @returns true iff @a _str passess the hex address checksum test. /// @param _strict if false, hex strings with only uppercase or only lowercase letters /// are considered valid. diff --git a/liblangutil/Token.h b/liblangutil/Token.h index f832fdf7..b3a1acb1 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -180,6 +180,7 @@ namespace langutil K(CallData, "calldata", 0) \ K(Struct, "struct", 0) \ K(Throw, "throw", 0) \ + K(Type, "type", 0) \ K(Using, "using", 0) \ K(Var, "var", 0) \ K(View, "view", 0) \ @@ -256,7 +257,6 @@ namespace langutil K(Supports, "supports", 0) \ K(Switch, "switch", 0) \ K(Try, "try", 0) \ - K(Type, "type", 0) \ K(Typedef, "typedef", 0) \ K(TypeOf, "typeof", 0) \ K(Unchecked, "unchecked", 0) \ diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index cd5fe07d..2276d783 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -61,7 +61,14 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{ make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("sha3", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)), make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)), - make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)) + make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), + make_shared<MagicVariableDeclaration>("type", make_shared<FunctionType>( + strings{"address"} /* accepts any contract type, handled by the type checker */, + strings{} /* returns a MagicType, handled by the type checker */, + FunctionType::Kind::MetaType, + false, + StateMutability::Pure + )), }) { } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index aaaa4f9f..11ed6a4f 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -32,6 +32,56 @@ using namespace dev; using namespace langutil; using namespace dev::solidity; +/** + * Helper class that determines whether a contract's constructor uses inline assembly. + */ +class dev::solidity::ConstructorUsesAssembly +{ +public: + /// @returns true if and only if the contract's or any of its bases' constructors + /// use inline assembly. + bool check(ContractDefinition const& _contract) + { + for (auto const* base: _contract.annotation().linearizedBaseContracts) + if (checkInternal(*base)) + return true; + return false; + } + + +private: + class Checker: public ASTConstVisitor + { + public: + Checker(FunctionDefinition const& _f) { _f.accept(*this); } + bool visit(InlineAssembly const&) override { assemblySeen = true; return false; } + bool assemblySeen = false; + }; + + bool checkInternal(ContractDefinition const& _contract) + { + if (!m_usesAssembly.count(&_contract)) + { + bool usesAssembly = false; + if (_contract.constructor()) + usesAssembly = Checker{*_contract.constructor()}.assemblySeen; + m_usesAssembly[&_contract] = usesAssembly; + } + return m_usesAssembly[&_contract]; + } + + map<ContractDefinition const*, bool> m_usesAssembly; +}; + +StaticAnalyzer::StaticAnalyzer(ErrorReporter& _errorReporter): + m_errorReporter(_errorReporter) +{ +} + +StaticAnalyzer::~StaticAnalyzer() +{ +} + bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); @@ -152,6 +202,18 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess) _memberAccess.location(), "\"block.blockhash()\" has been deprecated in favor of \"blockhash()\"" ); + else if (type->kind() == MagicType::Kind::MetaType && _memberAccess.memberName() == "runtimeCode") + { + if (!m_constructorUsesAssembly) + m_constructorUsesAssembly = make_unique<ConstructorUsesAssembly>(); + ContractType const& contract = dynamic_cast<ContractType const&>(*type->typeArgument()); + if (m_constructorUsesAssembly->check(contract.contractDefinition())) + m_errorReporter.warning( + _memberAccess.location(), + "The constructor of the contract (or its base) uses inline assembly. " + "Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode." + ); + } } if (_memberAccess.memberName() == "callcode") diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index ff33fa3a..3daf83b3 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -38,6 +38,8 @@ namespace dev namespace solidity { +class ConstructorUsesAssembly; + /** * The module that performs static analysis on the AST. @@ -49,7 +51,8 @@ class StaticAnalyzer: private ASTConstVisitor { public: /// @param _errorReporter provides the error logging functionality. - explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {} + explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter); + ~StaticAnalyzer(); /// Performs static analysis on the given source unit and all of its sub-nodes. /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings @@ -85,6 +88,10 @@ private: /// when traversing. std::map<std::pair<size_t, VariableDeclaration const*>, int> m_localVarUseCount; + /// Cache that holds information about whether a contract's constructor + /// uses inline assembly. + std::unique_ptr<ConstructorUsesAssembly> m_constructorUsesAssembly; + FunctionDefinition const* m_currentFunction = nullptr; /// Flag that indicates a constructor. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 5f2443a2..f1a5f7ce 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -199,6 +199,38 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c return components; } +TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall) +{ + vector<ASTPointer<Expression const>> arguments = _functionCall.arguments(); + if (arguments.size() != 1) + { + m_errorReporter.typeError( + _functionCall.location(), + "This function takes one argument, but " + + toString(arguments.size()) + + " were provided." + ); + return {}; + } + TypePointer firstArgType = type(*arguments.front()); + if ( + firstArgType->category() != Type::Category::TypeType || + dynamic_cast<TypeType const&>(*firstArgType).actualType()->category() != TypeType::Category::Contract + ) + { + m_errorReporter.typeError( + arguments.front()->location(), + "Invalid type for argument in function call. " + "Contract type required, but " + + type(*arguments.front())->toString(true) + + " provided." + ); + return {}; + } + + return {MagicType::metaType(dynamic_cast<TypeType const&>(*firstArgType).actualType())}; +} + void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) { auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); @@ -1489,7 +1521,16 @@ void TypeChecker::typeCheckABIEncodeFunctions( if (argType->category() == Type::Category::RationalNumber) { - if (!argType->mobileType()) + auto const& rationalType = dynamic_cast<RationalNumberType const&>(*argType); + if (rationalType.isFractional()) + { + m_errorReporter.typeError( + arguments[i]->location(), + "Fractional numbers cannot yet be encoded." + ); + continue; + } + else if (!argType->mobileType()) { m_errorReporter.typeError( arguments[i]->location(), @@ -1829,6 +1870,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall) returnTypes = functionType->returnParameterTypes(); break; } + case FunctionType::Kind::MetaType: + returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall); + break; default: { typeCheckFunctionCall(_functionCall, functionType); @@ -2069,8 +2113,24 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess) if (tt->actualType()->category() == Type::Category::Enum) annotation.isPure = true; if (auto magicType = dynamic_cast<MagicType const*>(exprType.get())) + { if (magicType->kind() == MagicType::Kind::ABI) annotation.isPure = true; + else if (magicType->kind() == MagicType::Kind::MetaType && ( + memberName == "creationCode" || memberName == "runtimeCode" + )) + { + annotation.isPure = true; + m_scope->annotation().contractDependencies.insert( + &dynamic_cast<ContractType const&>(*magicType->typeArgument()).contractDefinition() + ); + if (contractDependenciesAreCyclic(*m_scope)) + m_errorReporter.typeError( + _memberAccess.location(), + "Circular reference for contract code access." + ); + } + } return false; } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index b60c571a..d5f3645c 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -81,6 +81,8 @@ private: bool _abiEncoderV2 ); + TypePointers typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall); + /// Performs type checks and determines result types for type conversion FunctionCall nodes. TypePointer typeCheckTypeConversionAndRetrieveReturnType( FunctionCall const& _functionCall diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index eb019481..7df7ac17 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -338,7 +338,9 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) {MagicType::Kind::ABI, "encodeWithSignature"}, {MagicType::Kind::Block, "blockhash"}, {MagicType::Kind::Message, "data"}, - {MagicType::Kind::Message, "sig"} + {MagicType::Kind::Message, "sig"}, + {MagicType::Kind::MetaType, "creationCode"}, + {MagicType::Kind::MetaType, "runtimeCode"} }; set<MagicMember> static const payableMembers{ {MagicType::Kind::Message, "value"} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index f379d758..4cf8b1e8 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -138,6 +138,11 @@ bool ContractDefinition::constructorIsPublic() const return !f || f->isPublic(); } +bool ContractDefinition::canBeDeployed() const +{ + return constructorIsPublic() && annotation().unimplementedFunctions.empty(); +} + FunctionDefinition const* ContractDefinition::fallbackFunction() const { for (ContractDefinition const* contract: annotation().linearizedBaseContracts) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 3a64a34a..f6fdc441 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -409,6 +409,10 @@ public: FunctionDefinition const* constructor() const; /// @returns true iff the constructor of this contract is public (or non-existing). bool constructorIsPublic() const; + /// @returns true iff the contract can be deployed, i.e. is not abstract and has a + /// public constructor. + /// Should only be called after the type checker has run. + bool canBeDeployed() const; /// Returns the fallback function or nullptr if no fallback function was specified. FunctionDefinition const* fallbackFunction() const; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c8280d0f..3a8c9878 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2626,6 +2626,7 @@ string FunctionType::richIdentifier() const case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break; case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break; case Kind::ABIDecode: id += "abidecode"; break; + case Kind::MetaType: id += "metatype"; break; } id += "_" + stateMutabilityToString(m_stateMutability); id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes); @@ -3037,7 +3038,8 @@ bool FunctionType::isPure() const m_kind == Kind::ABIEncodePacked || m_kind == Kind::ABIEncodeWithSelector || m_kind == Kind::ABIEncodeWithSignature || - m_kind == Kind::ABIDecode; + m_kind == Kind::ABIDecode || + m_kind == Kind::MetaType; } TypePointers FunctionType::parseElementaryTypeVector(strings const& _types) @@ -3305,6 +3307,14 @@ string ModuleType::toString(bool) const return string("module \"") + m_sourceUnit.annotation().path + string("\""); } +shared_ptr<MagicType> MagicType::metaType(TypePointer _type) +{ + solAssert(_type && _type->category() == Type::Category::Contract, "Only contracts supported for now."); + auto t = make_shared<MagicType>(Kind::MetaType); + t->m_typeArgument = std::move(_type); + return t; +} + string MagicType::richIdentifier() const { switch (m_kind) @@ -3317,6 +3327,9 @@ string MagicType::richIdentifier() const return "t_magic_transaction"; case Kind::ABI: return "t_magic_abi"; + case Kind::MetaType: + solAssert(m_typeArgument, ""); + return "t_magic_meta_type_" + m_typeArgument->richIdentifier(); } return ""; } @@ -3403,12 +3416,27 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const StateMutability::Pure )} }); - default: - solAssert(false, "Unknown kind of magic."); + case Kind::MetaType: + { + solAssert( + m_typeArgument && m_typeArgument->category() == Type::Category::Contract, + "Only contracts supported for now" + ); + ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_typeArgument).contractDefinition(); + if (contract.canBeDeployed()) + return MemberList::MemberMap({ + {"creationCode", std::make_shared<ArrayType>(DataLocation::Memory)}, + {"runtimeCode", std::make_shared<ArrayType>(DataLocation::Memory)} + }); + else + return {}; + } } + solAssert(false, "Unknown kind of magic."); + return {}; } -string MagicType::toString(bool) const +string MagicType::toString(bool _short) const { switch (m_kind) { @@ -3420,7 +3448,17 @@ string MagicType::toString(bool) const return "tx"; case Kind::ABI: return "abi"; - default: - solAssert(false, "Unknown kind of magic."); + case Kind::MetaType: + solAssert(m_typeArgument, ""); + return "type(" + m_typeArgument->toString(_short) + ")"; } + solAssert(false, "Unknown kind of magic."); + return {}; +} + +TypePointer MagicType::typeArgument() const +{ + solAssert(m_kind == Kind::MetaType, ""); + solAssert(m_typeArgument, ""); + return m_typeArgument; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index bee00661..53109de1 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -989,6 +989,7 @@ public: ABIEncodeWithSignature, ABIDecode, GasLeft, ///< gasleft() + MetaType ///< type(...) }; Category category() const override { return Category::Function; } @@ -1299,16 +1300,23 @@ private: }; /** - * Special type for magic variables (block, msg, tx), similar to a struct but without any reference - * (it always references a global singleton by name). + * Special type for magic variables (block, msg, tx, type(...)), similar to a struct but without any reference. */ class MagicType: public Type { public: - enum class Kind { Block, Message, Transaction, ABI }; + enum class Kind { + Block, ///< "block" + Message, ///< "msg" + Transaction, ///< "tx" + ABI, ///< "abi" + MetaType ///< "type(...)" + }; Category category() const override { return Category::Magic; } explicit MagicType(Kind _kind): m_kind(_kind) {} + /// Factory function for meta type + static std::shared_ptr<MagicType> metaType(TypePointer _type); TypeResult binaryOperatorResult(Token, TypePointer const&) const override { @@ -1327,8 +1335,13 @@ public: Kind kind() const { return m_kind; } + TypePointer typeArgument() const; + private: Kind m_kind; + /// Contract type used for contract metadata magic. + TypePointer m_typeArgument; + }; /** diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index a22e6e9d..72efed33 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -31,22 +31,28 @@ using namespace dev::solidity; void Compiler::compileContract( ContractDefinition const& _contract, - std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts, + std::map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers, bytes const& _metadata ) { ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns); - runtimeCompiler.compileContract(_contract, _contracts); + runtimeCompiler.compileContract(_contract, _otherCompilers); m_runtimeContext.appendAuxiliaryData(_metadata); // This might modify m_runtimeContext because it can access runtime functions at // creation time. ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1); - m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); + m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers); m_context.optimise(m_optimize, m_optimizeRuns); } +std::shared_ptr<eth::Assembly> Compiler::runtimeAssemblyPtr() const +{ + solAssert(m_context.runtimeContext(), ""); + return m_context.runtimeContext()->assemblyPtr(); +} + eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const { return m_runtimeContext.functionEntryLabelIfExists(_function); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 784d7f8c..c21de96d 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -45,11 +45,15 @@ public: /// @arg _metadata contains the to be injected metadata CBOR void compileContract( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts, + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers, bytes const& _metadata ); /// @returns Entire assembly. eth::Assembly const& assembly() const { return m_context.assembly(); } + /// @returns Entire assembly as a shared pointer to non-const. + std::shared_ptr<eth::Assembly> assemblyPtr() const { return m_context.assemblyPtr(); } + /// @returns Runtime assembly. + std::shared_ptr<eth::Assembly> runtimeAssemblyPtr() const; /// @returns The entire assembled object (with constructor). eth::LinkerObject assembledObject() const { return m_context.assembledObject(); } /// @returns Only the runtime object (without constructor). diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index be681b2e..861b1c98 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -167,11 +167,18 @@ unsigned CompilerContext::numberOfLocalVariables() const return m_localVariables.size(); } -eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const +shared_ptr<eth::Assembly> CompilerContext::compiledContract(ContractDefinition const& _contract) const { - auto ret = m_compiledContracts.find(&_contract); - solAssert(ret != m_compiledContracts.end(), "Compiled contract not found."); - return *ret->second; + auto ret = m_otherCompilers.find(&_contract); + solAssert(ret != m_otherCompilers.end(), "Compiled contract not found."); + return ret->second->assemblyPtr(); +} + +shared_ptr<eth::Assembly> CompilerContext::compiledContractRuntime(ContractDefinition const& _contract) const +{ + auto ret = m_otherCompilers.find(&_contract); + solAssert(ret != m_otherCompilers.end(), "Compiled contract not found."); + return ret->second->runtimeAssemblyPtr(); } bool CompilerContext::isLocalVariable(Declaration const* _declaration) const diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index dedcd95f..e5ddfbc5 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -41,6 +41,7 @@ namespace dev { namespace solidity { +class Compiler; /** * Context to be shared by all units that compile the same contract. @@ -74,8 +75,9 @@ public: /// Returns the number of currently allocated local variables. unsigned numberOfLocalVariables() const; - void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; } - eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; + void setOtherCompilers(std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers) { m_otherCompilers = _otherCompilers; } + std::shared_ptr<eth::Assembly> compiledContract(ContractDefinition const& _contract) const; + std::shared_ptr<eth::Assembly> compiledContractRuntime(ContractDefinition const& _contract) const; void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } @@ -222,15 +224,15 @@ public: void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, m_evmVersion, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. - CompilerContext* runtimeContext() { return m_runtimeContext; } + CompilerContext* runtimeContext() const { return m_runtimeContext; } /// @returns the identifier of the runtime subroutine. size_t runtimeSub() const { return m_runtimeSub; } /// @returns a const reference to the underlying assembly. eth::Assembly const& assembly() const { return *m_asm; } - /// @returns non-const reference to the underlying assembly. Should be avoided in favour of - /// wrappers in this class. - eth::Assembly& nonConstAssembly() { return *m_asm; } + /// @returns a shared pointer to the assembly. + /// Should be avoided except when adding sub-assemblies. + std::shared_ptr<eth::Assembly> assemblyPtr() const { return m_asm; } /// @arg _sourceCodes is the map of input files to source code strings std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const @@ -307,7 +309,7 @@ private: /// Activated experimental features. std::set<ExperimentalFeature> m_experimentalFeatures; /// Other already compiled contracts to be used in contract creation calls. - std::map<ContractDefinition const*, eth::Assembly const*> m_compiledContracts; + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers; /// Storage offsets of state variables std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index bbc703c7..6cfb0777 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -1199,6 +1199,29 @@ void CompilerUtils::computeHashStatic() m_context << u256(32) << u256(0) << Instruction::KECCAK256; } +void CompilerUtils::copyContractCodeToMemory(ContractDefinition const& contract, bool _creation) +{ + string which = _creation ? "Creation" : "Runtime"; + m_context.callLowLevelFunction( + "$copyContract" + which + "CodeToMemory_" + contract.type()->identifier(), + 1, + 1, + [&contract, _creation](CompilerContext& _context) + { + // copy the contract's code into memory + shared_ptr<eth::Assembly> assembly = + _creation ? + _context.compiledContract(contract) : + _context.compiledContractRuntime(contract); + // pushes size + auto subroutine = _context.addSubroutine(assembly); + _context << Instruction::DUP1 << subroutine; + _context << Instruction::DUP4 << Instruction::CODECOPY; + _context << Instruction::ADD; + } + ); +} + void CompilerUtils::storeStringData(bytesConstRef _data) { //@todo provide both alternatives to the optimiser diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 7e4f47ba..6bde2e8b 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -263,6 +263,13 @@ public: /// Appends code that computes the Keccak-256 hash of the topmost stack element of 32 byte type. void computeHashStatic(); + /// Apppends code that copies the code of the given contract to memory. + /// Stack pre: Memory position + /// Stack post: Updated memory position + /// @param creation if true, copies creation code, if false copies runtime code. + /// @note the contract has to be compiled already, so beware of cyclic dependencies! + void copyContractCodeToMemory(ContractDefinition const& contract, bool _creationCode); + /// Bytes we need to the start of call data. /// - The size in bytes of the function (hash) identifier. static const unsigned dataStartOffset; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index b051d260..f843e07a 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -60,7 +60,7 @@ private: void ContractCompiler::compileContract( ContractDefinition const& _contract, - std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts + map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); @@ -70,7 +70,7 @@ void ContractCompiler::compileContract( // This has to be the first code in the contract. appendDelegatecallCheck(); - initializeContext(_contract, _contracts); + initializeContext(_contract, _otherCompilers); // This generates the dispatch function for externally visible functions // and adds the function to the compilation queue. Additionally internal functions, // which are referenced directly or indirectly will be added. @@ -81,7 +81,7 @@ void ContractCompiler::compileContract( size_t ContractCompiler::compileConstructor( ContractDefinition const& _contract, - std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts + std::map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); @@ -89,18 +89,18 @@ size_t ContractCompiler::compileConstructor( return deployLibrary(_contract); else { - initializeContext(_contract, _contracts); + initializeContext(_contract, _otherCompilers); return packIntoContractCreator(_contract); } } void ContractCompiler::initializeContext( ContractDefinition const& _contract, - map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts + map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers ) { m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures); - m_context.setCompiledContracts(_compiledContracts); + m_context.setOtherCompilers(_otherCompilers); m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts); CompilerUtils(m_context).initialiseFreeMemoryPointer(); registerStateVariables(_contract); @@ -716,7 +716,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) CodeGenerator::assemble( _inlineAssembly.operations(), *_inlineAssembly.annotation().analysisInfo, - m_context.nonConstAssembly(), + *m_context.assemblyPtr(), identifierAccess ); m_context.setStackOffset(startStackHeight); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 40871f0d..9ab006f6 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -49,13 +49,13 @@ public: void compileContract( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers ); /// Compiles the constructor part of the contract. /// @returns the identifier of the runtime sub-assembly. size_t compileConstructor( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers ); private: @@ -63,7 +63,7 @@ private: /// information about the contract like the AST annotations. void initializeContext( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers ); /// Adds the code that is run at creation time. Should be run after exchanging the run-time context /// with a new and initialized context. Adds the constructor code. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index be2709ae..e6bb163d 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -594,22 +594,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } ContractDefinition const* contract = &dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition(); - m_context.callLowLevelFunction( - "$copyContractCreationCodeToMemory_" + contract->type()->identifier(), - 0, - 1, - [contract](CompilerContext& _context) - { - // copy the contract's code into memory - eth::Assembly const& assembly = _context.compiledContract(*contract); - CompilerUtils(_context).fetchFreeMemoryPointer(); - // pushes size - auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly)); - _context << Instruction::DUP1 << subroutine; - _context << Instruction::DUP4 << Instruction::CODECOPY; - _context << Instruction::ADD; - } - ); + utils().fetchFreeMemoryPointer(); + utils().copyContractCodeToMemory(*contract, true); utils().abiEncode(argumentTypes, function.parameterTypes()); // now on stack: memory_end_ptr // need: size, offset, endowment @@ -1107,6 +1093,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::GasLeft: m_context << Instruction::GAS; break; + case FunctionType::Kind::MetaType: + // No code to generate. + break; } } return false; @@ -1348,6 +1337,23 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) solAssert(false, "Gas has been removed."); else if (member == "blockhash") solAssert(false, "Blockhash has been removed."); + else if (member == "creationCode" || member == "runtimeCode") + { + TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument(); + ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition(); + utils().fetchFreeMemoryPointer(); + m_context << Instruction::DUP1 << u256(32) << Instruction::ADD; + utils().copyContractCodeToMemory(contract, member == "creationCode"); + // Stack: start end + m_context.appendInlineAssembly( + Whiskers(R"({ + mstore(start, sub(end, add(start, 0x20))) + mstore(<free>, end) + })")("free", to_string(CompilerUtils::freeMemoryPointer)).render(), + {"start", "end"} + ); + m_context << Instruction::POP; + } else solAssert(false, "Unknown magic member."); break; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index f9d889e7..9e4da62d 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -343,12 +343,12 @@ bool CompilerStack::compile() return false; // Only compile contracts individually which have been requested. - map<ContractDefinition const*, eth::Assembly const*> compiledContracts; + map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers; for (Source const* source: m_sourceOrder) for (ASTPointer<ASTNode> const& node: source->ast->nodes()) if (auto contract = dynamic_cast<ContractDefinition const*>(node.get())) if (isRequestedContract(*contract)) - compileContract(*contract, compiledContracts); + compileContract(*contract, otherCompilers); m_stackState = CompilationSuccessful; this->link(); return true; @@ -795,19 +795,15 @@ bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& featu void CompilerStack::compileContract( ContractDefinition const& _contract, - map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts + map<ContractDefinition const*, shared_ptr<Compiler const>>& _otherCompilers ) { solAssert(m_stackState >= AnalysisSuccessful, ""); - if ( - _compiledContracts.count(&_contract) || - !_contract.annotation().unimplementedFunctions.empty() || - !_contract.constructorIsPublic() - ) + if (_otherCompilers.count(&_contract) || !_contract.canBeDeployed()) return; for (auto const* dependency: _contract.annotation().contractDependencies) - compileContract(*dependency, _compiledContracts); + compileContract(*dependency, _otherCompilers); Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName()); @@ -825,7 +821,7 @@ void CompilerStack::compileContract( try { // Run optimiser and compile the contract. - compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata); + compiler->compileContract(_contract, _otherCompilers, cborEncodedMetadata); } catch(eth::OptimizerException const&) { @@ -852,7 +848,7 @@ void CompilerStack::compileContract( solAssert(false, "Assembly exception for deployed bytecode"); } - _compiledContracts[compiledContract.contract] = &compiler->assembly(); + _otherCompilers[compiledContract.contract] = compiler; } CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 81d5009f..79927850 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -293,10 +293,12 @@ private: /// @returns true if the contract is requested to be compiled. bool isRequestedContract(ContractDefinition const& _contract) const; - /// Compile a single contract and put the result in @a _compiledContracts. + /// Compile a single contract. + /// @param _otherCompilers provides access to compilers of other contracts, to get + /// their bytecode if needed. Only filled after they have been compiled. void compileContract( ContractDefinition const& _contract, - std::map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts + std::map<ContractDefinition const*, std::shared_ptr<Compiler const>>& _otherCompilers ); /// Links all the known library addresses in the available objects. Any unknown diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 8a6bc343..35476a76 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -1551,6 +1551,12 @@ ASTPointer<Expression> Parser::parsePrimaryExpression() nodeFactory.markEndPosition(); expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance()); break; + case Token::Type: + // Inside expressions "type" is the name of a special, globally-available function. + nodeFactory.markEndPosition(); + m_scanner->next(); + expression = nodeFactory.createNode<Identifier>(make_shared<ASTString>("type")); + break; case Token::LParen: case Token::LBrack: { diff --git a/libyul/CMakeLists.txt b/libyul/CMakeLists.txt index 44af2fc3..259f43f8 100644 --- a/libyul/CMakeLists.txt +++ b/libyul/CMakeLists.txt @@ -80,6 +80,8 @@ add_library(yul optimiser/RedundantAssignEliminator.h optimiser/Rematerialiser.cpp optimiser/Rematerialiser.h + optimiser/SSAReverser.cpp + optimiser/SSAReverser.h optimiser/SSATransform.cpp optimiser/SSATransform.h optimiser/SSAValueTracker.cpp diff --git a/libyul/optimiser/Metrics.cpp b/libyul/optimiser/Metrics.cpp index df919682..e13940aa 100644 --- a/libyul/optimiser/Metrics.cpp +++ b/libyul/optimiser/Metrics.cpp @@ -134,3 +134,15 @@ void CodeCost::visit(Expression const& _expression) ++m_cost; ASTWalker::visit(_expression); } + +void AssignmentCounter::operator()(Assignment const& _assignment) +{ + for (auto const& variable: _assignment.variableNames) + ++m_assignmentCounters[variable.name]; +} + +size_t AssignmentCounter::assignmentCount(YulString _name) const +{ + auto it = m_assignmentCounters.find(_name); + return (it == m_assignmentCounters.end()) ? 0 : it->second; +} diff --git a/libyul/optimiser/Metrics.h b/libyul/optimiser/Metrics.h index 03e1b62a..5364646e 100644 --- a/libyul/optimiser/Metrics.h +++ b/libyul/optimiser/Metrics.h @@ -77,4 +77,18 @@ private: size_t m_cost = 0; }; +/** + * Counts the number of assignments to every variable. + * Only works after running the Disambiguator. + */ +class AssignmentCounter: public ASTWalker +{ +public: + using ASTWalker::operator(); + void operator()(Assignment const& _assignment) override; + std::size_t assignmentCount(YulString _name) const; +private: + std::map<YulString, size_t> m_assignmentCounters; +}; + } diff --git a/libyul/optimiser/SSAReverser.cpp b/libyul/optimiser/SSAReverser.cpp new file mode 100644 index 00000000..6548ebb5 --- /dev/null +++ b/libyul/optimiser/SSAReverser.cpp @@ -0,0 +1,114 @@ +/* + 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/>. +*/ +#include <libyul/optimiser/SSAReverser.h> +#include <libyul/optimiser/Metrics.h> +#include <libyul/AsmData.h> +#include <libdevcore/CommonData.h> + +using namespace std; +using namespace dev; +using namespace yul; + +void SSAReverser::run(Block& _block) +{ + AssignmentCounter assignmentCounter; + assignmentCounter(_block); + SSAReverser{assignmentCounter}(_block); +} + +void SSAReverser::operator()(Block& _block) +{ + walkVector(_block.statements); + iterateReplacingWindow<2>( + _block.statements, + [&](Statement& _stmt1, Statement& _stmt2) -> boost::optional<vector<Statement>> + { + auto* varDecl = boost::get<VariableDeclaration>(&_stmt1); + + if (!varDecl || varDecl->variables.size() != 1 || !varDecl->value) + return {}; + + // Replaces + // let a_1 := E + // a := a_1 + // with + // a := E + // let a_1 := a + if (auto* assignment = boost::get<Assignment>(&_stmt2)) + { + auto* identifier = boost::get<Identifier>(assignment->value.get()); + if ( + assignment->variableNames.size() == 1 && + identifier && + identifier->name == varDecl->variables.front().name + ) + { + vector<Statement> result; + result.emplace_back(Assignment{ + std::move(assignment->location), + assignment->variableNames, + std::move(varDecl->value) + }); + result.emplace_back(VariableDeclaration{ + std::move(varDecl->location), + std::move(varDecl->variables), + std::make_unique<Expression>(std::move(assignment->variableNames.front())) + }); + return { std::move(result) }; + } + } + // Replaces + // let a_1 := E + // let a := a_1 + // with + // let a := E + // let a_1 := a + else if (auto* varDecl2 = boost::get<VariableDeclaration>(&_stmt2)) + { + auto* identifier = boost::get<Identifier>(varDecl2->value.get()); + if ( + varDecl2->variables.size() == 1 && + identifier && + identifier->name == varDecl->variables.front().name && ( + m_assignmentCounter.assignmentCount(varDecl2->variables.front().name) > + m_assignmentCounter.assignmentCount(varDecl->variables.front().name) + ) + ) + { + vector<Statement> result; + auto varIdentifier2 = std::make_unique<Expression>(Identifier{ + varDecl2->variables.front().location, + varDecl2->variables.front().name + }); + result.emplace_back(VariableDeclaration{ + std::move(varDecl2->location), + std::move(varDecl2->variables), + std::move(varDecl->value) + }); + result.emplace_back(VariableDeclaration{ + std::move(varDecl->location), + std::move(varDecl->variables), + std::move(varIdentifier2) + }); + return { std::move(result) }; + } + } + + return {}; + } + ); +} diff --git a/libyul/optimiser/SSAReverser.h b/libyul/optimiser/SSAReverser.h new file mode 100644 index 00000000..67abeb56 --- /dev/null +++ b/libyul/optimiser/SSAReverser.h @@ -0,0 +1,74 @@ +/* + 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/>. +*/ +#pragma once + +#include <libyul/optimiser/ASTWalker.h> + +namespace yul +{ + +class AssignmentCounter; + +/** + * Reverses the SSA transformation. + * + * In particular, the SSA transform will rewrite + * + * a := E + * + * to + * + * let a_1 := E + * a := a_1 + * + * To undo this kind of transformation, the SSAReverser changes this back to + * + * a := E + * let a_1 := a + * + * Secondly, the SSA transform will rewrite + * + * let a := E + * to + * + * let a_1 := E + * let a := a_1 + * + * To undo this kind of transformation, the SSAReverser changes this back to + * + * let a := E + * let a_1 := a + * + * After that the CSE can replace references of a_1 by references to a, + * after which the unused pruner can remove the declaration of a_1. + * + * Prerequisites: Disambiguator + * + */ +class SSAReverser: public ASTModifier +{ +public: + using ASTModifier::operator(); + void operator()(Block& _block) override; + + static void run(Block& _block); +private: + SSAReverser(AssignmentCounter const& _assignmentCounter): m_assignmentCounter(_assignmentCounter) {} + AssignmentCounter const& m_assignmentCounter; +}; + +} diff --git a/libyul/optimiser/Suite.cpp b/libyul/optimiser/Suite.cpp index 38c0bf49..8cf6e104 100644 --- a/libyul/optimiser/Suite.cpp +++ b/libyul/optimiser/Suite.cpp @@ -35,6 +35,7 @@ #include <libyul/optimiser/UnusedPruner.h> #include <libyul/optimiser/ExpressionSimplifier.h> #include <libyul/optimiser/CommonSubexpressionEliminator.h> +#include <libyul/optimiser/SSAReverser.h> #include <libyul/optimiser/SSATransform.h> #include <libyul/optimiser/StructuralSimplifier.h> #include <libyul/optimiser/RedundantAssignEliminator.h> @@ -88,9 +89,10 @@ void OptimiserSuite::run( UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); CommonSubexpressionEliminator{_dialect}(ast); UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); - SSATransform::run(ast, dispenser); - RedundantAssignEliminator::run(_dialect, ast); - RedundantAssignEliminator::run(_dialect, ast); + + SSAReverser::run(ast); + CommonSubexpressionEliminator{_dialect}(ast); + UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); ExpressionJoiner::run(ast); ExpressionJoiner::run(ast); @@ -127,6 +129,11 @@ void OptimiserSuite::run( UnusedPruner::runUntilStabilised(_dialect, ast); ExpressionJoiner::run(ast); UnusedPruner::runUntilStabilised(_dialect, ast); + + SSAReverser::run(ast); + CommonSubexpressionEliminator{_dialect}(ast); + UnusedPruner::runUntilStabilised(_dialect, ast, reservedIdentifiers); + ExpressionJoiner::run(ast); Rematerialiser::run(_dialect, ast); UnusedPruner::runUntilStabilised(_dialect, ast); diff --git a/test/libsolidity/ASTJSON/address_payable.json b/test/libsolidity/ASTJSON/address_payable.json index 0f30e8e8..c8b71628 100644 --- a/test/libsolidity/ASTJSON/address_payable.json +++ b/test/libsolidity/ASTJSON/address_payable.json @@ -277,7 +277,7 @@ "name" : "this", "nodeType" : "Identifier", "overloadedDeclarations" : [], - "referencedDeclaration" : 65, + "referencedDeclaration" : 66, "src" : "217:4:1", "typeDescriptions" : { diff --git a/test/libsolidity/ASTJSON/address_payable_legacy.json b/test/libsolidity/ASTJSON/address_payable_legacy.json index dd8a5582..8abebce0 100644 --- a/test/libsolidity/ASTJSON/address_payable_legacy.json +++ b/test/libsolidity/ASTJSON/address_payable_legacy.json @@ -424,7 +424,7 @@ [ null ], - "referencedDeclaration" : 65, + "referencedDeclaration" : 66, "type" : "contract C", "value" : "this" }, diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index baa9bff1..b5a1797b 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -46,6 +46,7 @@ namespace dev { namespace solidity { +class Contract; namespace test { @@ -84,7 +85,7 @@ eth::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) { Compiler compiler(dev::test::Options::get().evmVersion()); - compiler.compileContract(*contract, map<ContractDefinition const*, Assembly const*>{}, bytes()); + compiler.compileContract(*contract, map<ContractDefinition const*, shared_ptr<Compiler const>>{}, bytes()); return compiler.runtimeAssemblyItems(); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8d219d16..38be5ae7 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -14234,6 +14234,113 @@ BOOST_AUTO_TEST_CASE(base_access_to_function_type_variables) ABI_CHECK(callContractFunction("h()"), encodeArgs(2)); } +BOOST_AUTO_TEST_CASE(code_access) +{ + char const* sourceCode = R"( + contract C { + function lengths() public pure returns (bool) { + uint crLen = type(D).creationCode.length; + uint runLen = type(D).runtimeCode.length; + require(runLen < crLen); + require(crLen >= 0x20); + require(runLen >= 0x20); + return true; + } + function creation() public pure returns (bytes memory) { + return type(D).creationCode; + } + function runtime() public pure returns (bytes memory) { + return type(D).runtimeCode; + } + function runtimeAllocCheck() public pure returns (bytes memory) { + uint[] memory a = new uint[](2); + bytes memory c = type(D).runtimeCode; + uint[] memory b = new uint[](2); + a[0] = 0x1111; + a[1] = 0x2222; + b[0] = 0x3333; + b[1] = 0x4444; + return c; + } + } + contract D { + function f() public pure returns (uint) { return 7; } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("lengths()"), encodeArgs(true)); + bytes codeCreation = callContractFunction("creation()"); + bytes codeRuntime1 = callContractFunction("runtime()"); + bytes codeRuntime2 = callContractFunction("runtimeAllocCheck()"); + ABI_CHECK(codeRuntime1, codeRuntime2); +} + +BOOST_AUTO_TEST_CASE(code_access_create) +{ + char const* sourceCode = R"( + contract C { + function test() public returns (uint) { + bytes memory c = type(D).creationCode; + D d; + assembly { + d := create(0, add(c, 0x20), mload(c)) + } + return d.f(); + } + } + contract D { + uint x; + constructor() public { x = 7; } + function f() public view returns (uint) { return x; } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("test()"), encodeArgs(7)); +} + +BOOST_AUTO_TEST_CASE(code_access_content) +{ + char const* sourceCode = R"( + contract C { + function testRuntime() public returns (bool) { + D d = new D(); + bytes32 runtimeHash = keccak256(type(D).runtimeCode); + bytes32 otherHash; + uint size; + assembly { + size := extcodesize(d) + extcodecopy(d, mload(0x40), 0, size) + otherHash := keccak256(mload(0x40), size) + } + require(size == type(D).runtimeCode.length); + require(runtimeHash == otherHash); + return true; + } + function testCreation() public returns (bool) { + D d = new D(); + bytes32 creationHash = keccak256(type(D).creationCode); + require(creationHash == d.x()); + return true; + } + } + contract D { + bytes32 public x; + constructor() public { + bytes32 codeHash; + assembly { + let size := codesize() + codecopy(mload(0x40), 0, size) + codeHash := keccak256(mload(0x40), size) + } + x = codeHash; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + ABI_CHECK(callContractFunction("testRuntime()"), encodeArgs(true)); + ABI_CHECK(callContractFunction("testCreation()"), encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index a33c6134..413a8c9c 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -539,7 +539,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved) "supports", "switch", "try", - "type", "typedef", "typeof", "unchecked" diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol new file mode 100644 index 00000000..e90443e1 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol @@ -0,0 +1,12 @@ +contract Test { + function creationOther() public pure returns (bytes memory) { + return type(Other).creationCode; + } + function runtimeOther() public pure returns (bytes memory) { + return type(Other).runtimeCode; + } +} +contract Other { + function f(uint) public pure returns (uint) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol new file mode 100644 index 00000000..bae137c5 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol @@ -0,0 +1,10 @@ +contract Test { + function creationOther() public pure returns (bytes memory) { + return type(Other).creationCode; + } +} +contract Other { + function f(uint) public returns (uint); +} +// ---- +// TypeError: (97-121): Member "creationCode" not found or not visible after argument-dependent lookup in type(contract Other). diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol new file mode 100644 index 00000000..186d2714 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol @@ -0,0 +1,10 @@ +contract Test { + function runtime() public pure returns (bytes memory) { + return type(Other).runtimeCode; + } +} +contract Other { + function f(uint) public returns (uint); +} +// ---- +// TypeError: (91-114): Member "runtimeCode" not found or not visible after argument-dependent lookup in type(contract Other). diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol new file mode 100644 index 00000000..33dbfd7c --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol @@ -0,0 +1,22 @@ +contract Base { + function f() public pure returns (uint) {} +} +contract Test is Base { + function creation() public pure returns (bytes memory) { + return type(Test).creationCode; + } + function runtime() public pure returns (bytes memory) { + return type(Test).runtimeCode; + } + function creationBase() public pure returns (bytes memory) { + return type(Base).creationCode; + } + function runtimeBase() public pure returns (bytes memory) { + return type(Base).runtimeCode; + } +} +// ---- +// TypeError: (165-188): Circular reference for contract code access. +// TypeError: (271-293): Circular reference for contract code access. +// TypeError: (381-404): Circular reference for contract code access. +// TypeError: (491-513): Circular reference for contract code access. diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol new file mode 100644 index 00000000..d5723df6 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol @@ -0,0 +1,12 @@ +contract A { + function f() public pure { + type(B).runtimeCode; + } +} +contract B { + function f() public pure { + type(A).runtimeCode; + } +} +// ---- +// TypeError: (133-152): Circular reference for contract code access. diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol new file mode 100644 index 00000000..cda5d5c3 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol @@ -0,0 +1,7 @@ +contract Test { + bytes constant c = type(B).creationCode; + bytes constant r = type(B).runtimeCode; + +} +contract B { function f() public pure {} } +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol new file mode 100644 index 00000000..f746dc35 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol @@ -0,0 +1,12 @@ +contract Test { + function creationOther() public pure returns (bytes memory) { + return type(Library).creationCode; + } + function runtime() public pure returns (bytes memory) { + return type(Library).runtimeCode; + } +} +contract Library { + function f(uint) public pure returns (uint) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol b/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol new file mode 100644 index 00000000..022e4d6f --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol @@ -0,0 +1,10 @@ +contract Test { + function f() public pure { + type(C).creationCode = new bytes(6); + type(C).runtimeCode = new bytes(6); + } +} +contract C {} +// ---- +// TypeError: (55-75): Expression has to be an lvalue. +// TypeError: (100-119): Expression has to be an lvalue. diff --git a/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol b/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol new file mode 100644 index 00000000..542aaf53 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol @@ -0,0 +1,7 @@ +contract Test { + function creation() public pure returns (bytes memory) { + type(); + } +} +// ---- +// TypeError: (85-91): This function takes one argument, but 0 were provided. diff --git a/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol b/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol new file mode 100644 index 00000000..ec8d9784 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol @@ -0,0 +1,17 @@ +contract Test { + function f() public pure returns (uint) { + return type(C).runtimeCode.length + + type(D).runtimeCode.length + + type(C).creationCode.length + + type(D).creationCode.length; + } +} +contract C { + constructor() public { assembly {} } +} +contract D is C { + constructor() public {} +} +// ---- +// Warning: (77-96): The constructor of the contract (or its base) uses inline assembly. Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode. +// Warning: (118-137): The constructor of the contract (or its base) uses inline assembly. Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode. diff --git a/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol b/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol new file mode 100644 index 00000000..61c2b779 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol @@ -0,0 +1,7 @@ +contract Test { + function creation() public pure returns (bytes memory) { + type(1, 2); + } +} +// ---- +// TypeError: (85-95): This function takes one argument, but 2 were provided. diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol new file mode 100644 index 00000000..144ca1c3 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol @@ -0,0 +1,3 @@ +contract type { } +// ---- +// ParserError: (9-13): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol new file mode 100644 index 00000000..b7881f15 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol @@ -0,0 +1,6 @@ +contract Test { + function type() public pure { + } +} +// ---- +// ParserError: (29-33): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol new file mode 100644 index 00000000..001ba840 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol @@ -0,0 +1,6 @@ +contract Test { + function f(uint type) public pure { + } +} +// ---- +// ParserError: (36-40): Expected ',' but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol new file mode 100644 index 00000000..fa827a33 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol @@ -0,0 +1,5 @@ +contract Test { + uint type; +} +// ---- +// ParserError: (25-29): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol new file mode 100644 index 00000000..fa57698d --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol @@ -0,0 +1,7 @@ +contract Test { + function f() public pure { + uint type; + } +} +// ---- +// ParserError: (60-64): Expected ';' but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol b/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol new file mode 100644 index 00000000..036c36e8 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol @@ -0,0 +1,6 @@ +contract Test { + function f() public pure returns (bytes memory) { + type(Test); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol b/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol new file mode 100644 index 00000000..0ce06786 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol @@ -0,0 +1,8 @@ +contract Test { + function f() public pure { + type(type(type(Test))); + } +} +// ---- +// TypeError: (65-75): Invalid type for argument in function call. Contract type required, but type(contract Test) provided. +// TypeError: (60-76): Invalid type for argument in function call. Contract type required, but tuple() provided. diff --git a/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol b/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol new file mode 100644 index 00000000..5c27d42f --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol @@ -0,0 +1,9 @@ +contract Test { + struct S { uint x; } + function f() public pure { + // Unsupported for now, but might be supported in the future + type(S); + } +} +// ---- +// TypeError: (154-155): Invalid type for argument in function call. Contract type required, but type(struct Test.S) provided. diff --git a/test/libsolidity/syntaxTests/types/encoding_fractional.sol b/test/libsolidity/syntaxTests/types/encoding_fractional.sol new file mode 100644 index 00000000..16c76d9a --- /dev/null +++ b/test/libsolidity/syntaxTests/types/encoding_fractional.sol @@ -0,0 +1,7 @@ +contract C { + function f1() public pure returns (bytes memory) { + return abi.encode(0.1, 1); + } +} +// ---- +// TypeError: (92-95): Fractional numbers cannot yet be encoded. diff --git a/test/libsolidity/syntaxTests/types/encoding_fractional_abiencoderv2.sol b/test/libsolidity/syntaxTests/types/encoding_fractional_abiencoderv2.sol new file mode 100644 index 00000000..80efa9c5 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/encoding_fractional_abiencoderv2.sol @@ -0,0 +1,9 @@ +pragma experimental ABIEncoderV2; +contract C { + function f1() public pure returns (bytes memory) { + return abi.encode(0.1, 1); + } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (126-129): Fractional numbers cannot yet be encoded. diff --git a/test/libsolidity/syntaxTests/types/encoding_packed_fractional.sol b/test/libsolidity/syntaxTests/types/encoding_packed_fractional.sol new file mode 100644 index 00000000..71080cb0 --- /dev/null +++ b/test/libsolidity/syntaxTests/types/encoding_packed_fractional.sol @@ -0,0 +1,8 @@ +contract C { + function f1() public pure returns (bytes memory) { + return abi.encodePacked(0.1, 1); + } +} +// ---- +// TypeError: (98-101): Fractional numbers cannot yet be encoded. +// TypeError: (103-104): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. diff --git a/test/libsolidity/syntaxTests/types/encoding_packed_fractional_abiencoderv2.sol b/test/libsolidity/syntaxTests/types/encoding_packed_fractional_abiencoderv2.sol new file mode 100644 index 00000000..e82c3c9a --- /dev/null +++ b/test/libsolidity/syntaxTests/types/encoding_packed_fractional_abiencoderv2.sol @@ -0,0 +1,10 @@ +pragma experimental ABIEncoderV2; +contract C { + function f1() public pure returns (bytes memory) { + return abi.encodePacked(0.1, 1); + } +} +// ---- +// Warning: (0-33): Experimental features are turned on. Do not use experimental features on live deployments. +// TypeError: (132-135): Fractional numbers cannot yet be encoded. +// TypeError: (137-138): Cannot perform packed encoding for a literal. Please convert it to an explicit type first. diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 59cde4fe..306721a0 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -38,6 +38,7 @@ #include <libyul/optimiser/ExpressionSimplifier.h> #include <libyul/optimiser/UnusedPruner.h> #include <libyul/optimiser/ExpressionJoiner.h> +#include <libyul/optimiser/SSAReverser.h> #include <libyul/optimiser/SSATransform.h> #include <libyul/optimiser/RedundantAssignEliminator.h> #include <libyul/optimiser/StructuralSimplifier.h> @@ -223,6 +224,23 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con disambiguate(); EquivalentFunctionCombiner::run(*m_ast); } + else if (m_optimizerStep == "ssaReverser") + { + disambiguate(); + SSAReverser::run(*m_ast); + } + else if (m_optimizerStep == "ssaAndBack") + { + disambiguate(); + // apply SSA + NameDispenser nameDispenser{*m_dialect, *m_ast}; + SSATransform::run(*m_ast, nameDispenser); + RedundantAssignEliminator::run(*m_dialect, *m_ast); + // reverse SSA + SSAReverser::run(*m_ast); + CommonSubexpressionEliminator{*m_dialect}(*m_ast); + UnusedPruner::runUntilStabilised(*m_dialect, *m_ast); + } else if (m_optimizerStep == "fullSuite") OptimiserSuite::run(*m_dialect, *m_ast, *m_analysisInfo); else diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi2.yul b/test/libyul/yulOptimizerTests/fullSuite/abi2.yul index 887399b6..41a52c67 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi2.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi2.yul @@ -1073,12 +1073,12 @@ // fullSuite // { // let _2 := mload(1) -// let _172 := mload(0) -// if slt(sub(_2, _172), 64) +// let _153 := mload(0) +// if slt(sub(_2, _153), 64) // { // revert(0, 0) // } -// sstore(0, and(calldataload(_172), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// sstore(0, and(calldataload(_153), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) // let x0, x1, x2, x3, x4 := abi_decode_tuple_t_addresst_uint256t_bytes_calldata_ptrt_enum$_Operation_$1949(mload(7), mload(8)) // sstore(x1, x0) // sstore(x3, x2) @@ -1093,40 +1093,40 @@ // value0_57 := and(calldataload(add(headStart_55, value4)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) // value1_58 := calldataload(add(headStart_55, 32)) // let offset_62 := calldataload(add(headStart_55, 64)) -// let _220 := 0xffffffffffffffff -// if gt(offset_62, _220) +// let _201 := 0xffffffffffffffff +// if gt(offset_62, _201) // { // revert(value4, value4) // } -// let _222 := add(headStart_55, offset_62) -// if iszero(slt(add(_222, 0x1f), dataEnd_56)) +// let _203 := add(headStart_55, offset_62) +// if iszero(slt(add(_203, 0x1f), dataEnd_56)) // { // revert(value4, value4) // } -// let abi_decode_length_15_116 := calldataload(_222) -// if gt(abi_decode_length_15_116, _220) +// let abi_decode_length_15_246 := calldataload(_203) +// if gt(abi_decode_length_15_246, _201) // { // revert(value4, value4) // } -// if gt(add(add(_222, abi_decode_length_15_116), 32), dataEnd_56) +// if gt(add(add(_203, abi_decode_length_15_246), 32), dataEnd_56) // { // revert(value4, value4) // } -// value2_59 := add(_222, 32) -// value3 := abi_decode_length_15_116 -// let _225 := calldataload(add(headStart_55, 96)) -// if iszero(lt(_225, 3)) +// value2_59 := add(_203, 32) +// value3 := abi_decode_length_15_246 +// let _206 := calldataload(add(headStart_55, 96)) +// if iszero(lt(_206, 3)) // { // revert(value4, value4) // } -// value4 := _225 +// value4 := _206 // } // function abi_encode_tuple_t_bytes32_t_address_t_uint256_t_bytes32_t_enum$_Operation_$1949_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256__to_t_bytes32_t_address_t_uint256_t_bytes32_t_uint8_t_uint256_t_uint256_t_uint256_t_address_t_address_t_uint256_(headStart_252, value10_253, value9_254, value8_255, value7_256, value6_257, value5_258, value4_259, value3_260, value2_261, value1_262, value0_263) -> tail_264 // { // tail_264 := add(headStart_252, 352) // mstore(headStart_252, value0_263) -// let _439 := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -// mstore(add(headStart_252, 32), and(value1_262, _439)) +// let _413 := 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +// mstore(add(headStart_252, 32), and(value1_262, _413)) // mstore(add(headStart_252, 64), value2_261) // mstore(add(headStart_252, 96), value3_260) // if iszero(lt(value4_259, 3)) @@ -1137,8 +1137,8 @@ // mstore(add(headStart_252, 160), value5_258) // mstore(add(headStart_252, 192), value6_257) // mstore(add(headStart_252, 224), value7_256) -// mstore(add(headStart_252, 256), and(value8_255, _439)) -// mstore(add(headStart_252, 288), and(value9_254, _439)) +// mstore(add(headStart_252, 256), and(value8_255, _413)) +// mstore(add(headStart_252, 288), and(value9_254, _413)) // mstore(add(headStart_252, 320), value10_253) // } // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul index a8cac6c6..d38025ae 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/abi_example1.yul @@ -460,12 +460,12 @@ // { // let _1 := 0x20 // let _2 := 0 -// let _268 := mload(_2) +// let _218 := mload(_2) // let abi_encode_pos := _1 -// let abi_encode_length_68 := mload(_268) +// let abi_encode_length_68 := mload(_218) // mstore(_1, abi_encode_length_68) // abi_encode_pos := 64 -// let abi_encode_srcPtr := add(_268, _1) +// let abi_encode_srcPtr := add(_218, _1) // let abi_encode_i_69 := _2 // for { // } @@ -474,66 +474,64 @@ // abi_encode_i_69 := add(abi_encode_i_69, 1) // } // { -// let _668 := mload(abi_encode_srcPtr) -// let abi_encode_pos_71_760 := abi_encode_pos -// let abi_encode_srcPtr_73_762 := _668 -// let abi_encode_i_74_763 := _2 +// let _580 := mload(abi_encode_srcPtr) +// let abi_encode_pos_71_672 := abi_encode_pos +// let abi_encode_srcPtr_73_674 := _580 +// let abi_encode_i_74_675 := _2 // for { // } -// lt(abi_encode_i_74_763, 0x3) +// lt(abi_encode_i_74_675, 0x3) // { -// abi_encode_i_74_763 := add(abi_encode_i_74_763, 1) +// abi_encode_i_74_675 := add(abi_encode_i_74_675, 1) // } // { -// mstore(abi_encode_pos_71_760, and(mload(abi_encode_srcPtr_73_762), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) -// abi_encode_srcPtr_73_762 := add(abi_encode_srcPtr_73_762, _1) -// abi_encode_pos_71_760 := add(abi_encode_pos_71_760, _1) +// mstore(abi_encode_pos_71_672, and(mload(abi_encode_srcPtr_73_674), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) +// abi_encode_srcPtr_73_674 := add(abi_encode_srcPtr_73_674, _1) +// abi_encode_pos_71_672 := add(abi_encode_pos_71_672, _1) // } // abi_encode_srcPtr := add(abi_encode_srcPtr, _1) // abi_encode_pos := add(abi_encode_pos, 0x60) // } -// let _270 := mload(64) -// let _271 := mload(_1) -// if slt(sub(_270, _271), 128) +// let _220 := mload(64) +// let _221 := mload(_1) +// if slt(sub(_220, _221), 128) // { // revert(_2, _2) // } -// let abi_decode_offset_64 := calldataload(add(_271, 64)) +// let abi_decode_offset_64 := calldataload(add(_221, 64)) // let abi_decode__74 := 0xffffffffffffffff // if gt(abi_decode_offset_64, abi_decode__74) // { // revert(_2, _2) // } -// let abi_decode_value2_367 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_271, abi_decode_offset_64), _270) -// let abi_decode_offset_65 := calldataload(add(_271, 96)) +// let abi_decode_value2_317 := abi_decode_t_array$_t_uint256_$dyn_memory_ptr(add(_221, abi_decode_offset_64), _220) +// let abi_decode_offset_65 := calldataload(add(_221, 96)) // if gt(abi_decode_offset_65, abi_decode__74) // { // revert(_2, _2) // } -// let abi_decode_value3_368 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_271, abi_decode_offset_65), _270) -// sstore(calldataload(_271), calldataload(add(_271, _1))) -// sstore(abi_decode_value2_367, abi_decode_value3_368) +// let abi_decode_value3_318 := abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(add(_221, abi_decode_offset_65), _220) +// sstore(calldataload(_221), calldataload(add(_221, _1))) +// sstore(abi_decode_value2_317, abi_decode_value3_318) // sstore(_2, abi_encode_pos) // function abi_decode_t_array$_t_array$_t_uint256_$2_memory_$dyn_memory_ptr(offset_3, end_4) -> array_5 // { // if iszero(slt(add(offset_3, 0x1f), end_4)) // { -// revert(0, 0) +// revert(array_5, array_5) // } // let length_6 := calldataload(offset_3) -// let array_5_115 := allocateMemory(array_allocation_size_t_array$_t_address_$dyn_memory(length_6)) -// array_5 := array_5_115 -// let dst_7 := array_5_115 -// mstore(array_5_115, length_6) +// array_5 := allocateMemory(array_allocation_size_t_array$_t_address_$dyn_memory(length_6)) +// let dst_7 := array_5 +// mstore(array_5, length_6) // let _16 := 0x20 -// dst_7 := add(array_5_115, _16) +// dst_7 := add(array_5, _16) // let src_8 := add(offset_3, _16) // if gt(add(add(offset_3, mul(length_6, 0x40)), _16), end_4) // { // revert(0, 0) // } -// let i_9_346 := 0 -// let i_9 := i_9_346 +// let i_9 := 0 // for { // } // lt(i_9, length_6) @@ -543,17 +541,17 @@ // { // if iszero(slt(add(src_8, 0x1f), end_4)) // { -// revert(i_9_346, i_9_346) +// revert(0, 0) // } -// let abi_decode_array_13_124 := allocateMemory(array_allocation_size_t_array$_t_uint256_$2_memory(0x2)) -// let abi_decode_dst_15 := abi_decode_array_13_124 +// let abi_decode_dst_15 := allocateMemory(array_allocation_size_t_array$_t_uint256_$2_memory(0x2)) +// let abi_decode_dst_15_1155 := abi_decode_dst_15 // let abi_decode_src_16 := src_8 -// let abi_decode__289 := add(src_8, 0x40) -// if gt(abi_decode__289, end_4) +// let abi_decode__239 := add(src_8, 0x40) +// if gt(abi_decode__239, end_4) // { -// revert(i_9_346, i_9_346) +// revert(0, 0) // } -// let abi_decode_i_17 := i_9_346 +// let abi_decode_i_17 := 0 // for { // } // lt(abi_decode_i_17, 0x2) @@ -565,24 +563,23 @@ // abi_decode_dst_15 := add(abi_decode_dst_15, _16) // abi_decode_src_16 := add(abi_decode_src_16, _16) // } -// mstore(dst_7, abi_decode_array_13_124) +// mstore(dst_7, abi_decode_dst_15_1155) // dst_7 := add(dst_7, _16) -// src_8 := abi_decode__289 +// src_8 := abi_decode__239 // } // } // function abi_decode_t_array$_t_uint256_$dyn_memory_ptr(offset_27, end_28) -> array_29 // { // if iszero(slt(add(offset_27, 0x1f), end_28)) // { -// revert(0, 0) +// revert(array_29, array_29) // } // let length_30 := calldataload(offset_27) -// let array_29_131 := allocateMemory(array_allocation_size_t_array$_t_address_$dyn_memory(length_30)) -// array_29 := array_29_131 -// let dst_31 := array_29_131 -// mstore(array_29_131, length_30) +// array_29 := allocateMemory(array_allocation_size_t_array$_t_address_$dyn_memory(length_30)) +// let dst_31 := array_29 +// mstore(array_29, length_30) // let _52 := 0x20 -// dst_31 := add(array_29_131, _52) +// dst_31 := add(array_29, _52) // let src_32 := add(offset_27, _52) // if gt(add(add(offset_27, mul(length_30, _52)), _52), end_28) // { @@ -603,10 +600,9 @@ // } // function allocateMemory(size) -> memPtr // { -// let memPtr_157 := mload(64) -// memPtr := memPtr_157 -// let newFreePtr := add(memPtr_157, size) -// if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr_157)) +// memPtr := mload(64) +// let newFreePtr := add(memPtr, size) +// if or(gt(newFreePtr, 0xffffffffffffffff), lt(newFreePtr, memPtr)) // { // revert(0, 0) // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul index fc09b5d9..868c6b01 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/aztec.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/aztec.yul @@ -245,17 +245,16 @@ // mstore(0x00, 404) // revert(0x00, 0x20) // } -// let validateJo_kn_287 := calldataload(add(calldatasize(), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40)) -// let validateJo_kn := validateJo_kn_287 +// let validateJo_kn := calldataload(add(calldatasize(), 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff40)) // let validateJo__24 := 0x2a0 // mstore(validateJo__24, caller()) -// mstore(0x2c0, validateJo_kn_287) +// mstore(0x2c0, validateJo_kn) // mstore(0x2e0, validateJo_m) -// validateJo_kn := mulmod(sub(validateJo_gen_order, validateJo_kn_287), validateJo_challenge, validateJo_gen_order) +// validateJo_kn := mulmod(sub(validateJo_gen_order, validateJo_kn), validateJo_challenge, validateJo_gen_order) // hashCommitments(validateJo_notes, validateJo_n) // let validateJo_b := add(0x300, mul(validateJo_n, validateJo__6)) -// let validateJo_i_290 := 0 -// let validateJo_i := validateJo_i_290 +// let validateJo_i := 0 +// let validateJo_i_1306 := validateJo_i // for { // } // lt(validateJo_i, validateJo_n) @@ -264,11 +263,10 @@ // } // { // let validateJo__34 := 0x20 -// let validateJo__373 := add(validateJo__10, mul(validateJo_i, 0xc0)) -// let validateJo_noteIndex := add(validateJo__373, 0x24) -// let validateJo_k := validateJo_i_290 -// let validateJo_a_292 := calldataload(add(validateJo__373, 0x44)) -// let validateJo_a := validateJo_a_292 +// let validateJo__351 := add(validateJo__10, mul(validateJo_i, 0xc0)) +// let validateJo_noteIndex := add(validateJo__351, 0x24) +// let validateJo_k := validateJo_i_1306 +// let validateJo_a := calldataload(add(validateJo__351, 0x44)) // let validateJo_c := validateJo_challenge // let validateJo__39 := add(validateJo_i, 0x01) // switch eq(validateJo__39, validateJo_n) @@ -282,36 +280,34 @@ // case 0 { // validateJo_k := calldataload(validateJo_noteIndex) // } -// validateCommitment(validateJo_noteIndex, validateJo_k, validateJo_a_292) +// validateCommitment(validateJo_noteIndex, validateJo_k, validateJo_a) // switch gt(validateJo__39, validateJo_m) // case 1 { // validateJo_kn := addmod(validateJo_kn, sub(validateJo_gen_order, validateJo_k), validateJo_gen_order) -// let validateJo_x := mod(mload(validateJo_i_290), validateJo_gen_order) +// let validateJo_x := mod(mload(validateJo_i_1306), validateJo_gen_order) // validateJo_k := mulmod(validateJo_k, validateJo_x, validateJo_gen_order) -// validateJo_a := mulmod(validateJo_a_292, validateJo_x, validateJo_gen_order) +// validateJo_a := mulmod(validateJo_a, validateJo_x, validateJo_gen_order) // validateJo_c := mulmod(validateJo_challenge, validateJo_x, validateJo_gen_order) -// mstore(validateJo_i_290, keccak256(validateJo_i_290, validateJo__34)) +// mstore(validateJo_i_1306, keccak256(validateJo_i_1306, validateJo__34)) // } // case 0 { // validateJo_kn := addmod(validateJo_kn, validateJo_k, validateJo_gen_order) // } // let validateJo__52 := 0x40 -// calldatacopy(0xe0, add(validateJo__373, 164), validateJo__52) -// calldatacopy(validateJo__34, add(validateJo__373, 100), validateJo__52) +// calldatacopy(0xe0, add(validateJo__351, 164), validateJo__52) +// calldatacopy(validateJo__34, add(validateJo__351, 100), validateJo__52) // let validateJo__61 := 0x120 // mstore(validateJo__61, sub(validateJo_gen_order, validateJo_c)) // let validateJo__62 := 0x60 // mstore(validateJo__62, validateJo_k) // mstore(0xc0, validateJo_a) // let validateJo__65 := 0x1a0 -// let validateJo_result_302 := call(gas(), 7, validateJo_i_290, 0xe0, validateJo__62, validateJo__65, validateJo__52) -// let validateJo_result := validateJo_result_302 -// let validateJo_result_303 := and(validateJo_result_302, call(gas(), 7, validateJo_i_290, validateJo__34, validateJo__62, validateJo__61, validateJo__52)) +// let validateJo_result := call(gas(), 7, validateJo_i_1306, 0xe0, validateJo__62, validateJo__65, validateJo__52) +// let validateJo_result_303 := and(validateJo_result, call(gas(), 7, validateJo_i_1306, validateJo__34, validateJo__62, validateJo__61, validateJo__52)) // let validateJo__80 := 0x160 -// let validateJo_result_304 := and(validateJo_result_303, call(gas(), 7, validateJo_i_290, validateJo__6, validateJo__62, validateJo__80, validateJo__52)) -// let validateJo_result_305 := and(validateJo_result_304, call(gas(), 6, validateJo_i_290, validateJo__61, validateJo__6, validateJo__80, validateJo__52)) -// let validateJo_result_306 := and(validateJo_result_305, call(gas(), 6, validateJo_i_290, validateJo__80, validateJo__6, validateJo_b, validateJo__52)) -// validateJo_result := validateJo_result_306 +// let validateJo_result_304 := and(validateJo_result_303, call(gas(), 7, validateJo_i_1306, validateJo__6, validateJo__62, validateJo__80, validateJo__52)) +// let validateJo_result_305 := and(validateJo_result_304, call(gas(), 6, validateJo_i_1306, validateJo__61, validateJo__6, validateJo__80, validateJo__52)) +// validateJo_result := and(validateJo_result_305, call(gas(), 6, validateJo_i_1306, validateJo__80, validateJo__6, validateJo_b, validateJo__52)) // if eq(validateJo_i, validateJo_m) // { // mstore(0x260, mload(validateJo__34)) @@ -323,14 +319,14 @@ // { // mstore(validateJo__62, validateJo_c) // let validateJo__120 := 0x220 -// let validateJo_result_307 := and(validateJo_result_306, call(gas(), 7, validateJo_i_290, validateJo__34, validateJo__62, validateJo__120, validateJo__52)) -// let validateJo_result_308 := and(validateJo_result_307, call(gas(), 6, validateJo_i_290, validateJo__120, validateJo__6, 0x260, validateJo__52)) -// validateJo_result := and(validateJo_result_308, call(gas(), 6, validateJo_i_290, validateJo__65, validateJo__6, 0x1e0, validateJo__52)) +// let validateJo_result_307 := and(validateJo_result, call(gas(), 7, validateJo_i_1306, validateJo__34, validateJo__62, validateJo__120, validateJo__52)) +// let validateJo_result_308 := and(validateJo_result_307, call(gas(), 6, validateJo_i_1306, validateJo__120, validateJo__6, 0x260, validateJo__52)) +// validateJo_result := and(validateJo_result_308, call(gas(), 6, validateJo_i_1306, validateJo__65, validateJo__6, 0x1e0, validateJo__52)) // } // if iszero(validateJo_result) // { -// mstore(validateJo_i_290, 400) -// revert(validateJo_i_290, validateJo__34) +// mstore(validateJo_i_1306, 400) +// revert(validateJo_i_1306, validateJo__34) // } // validateJo_b := add(validateJo_b, validateJo__52) // } @@ -340,13 +336,13 @@ // } // if iszero(eq(mod(keccak256(validateJo__24, add(validateJo_b, 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd60)), validateJo_gen_order), validateJo_challenge)) // { -// mstore(validateJo_i_290, 404) -// revert(validateJo_i_290, 0x20) +// mstore(validateJo_i_1306, 404) +// revert(validateJo_i_1306, 0x20) // } -// mstore(validateJo_i_290, 0x01) -// return(validateJo_i_290, 0x20) -// mstore(validateJo_i_290, 404) -// revert(validateJo_i_290, 0x20) +// mstore(validateJo_i_1306, 0x01) +// return(validateJo_i_1306, 0x20) +// mstore(validateJo_i_1306, 404) +// revert(validateJo_i_1306, 0x20) // function validatePairing(t2) // { // let t2_x_1 := calldataload(t2) diff --git a/test/libyul/yulOptimizerTests/fullSuite/medium.yul b/test/libyul/yulOptimizerTests/fullSuite/medium.yul index b10c6c69..1d07cd03 100644 --- a/test/libyul/yulOptimizerTests/fullSuite/medium.yul +++ b/test/libyul/yulOptimizerTests/fullSuite/medium.yul @@ -21,8 +21,8 @@ // { // let allocate__19 := 0x40 // mstore(allocate__19, add(mload(allocate__19), 0x20)) -// let allocate_p_24_41 := mload(allocate__19) -// mstore(allocate__19, add(allocate_p_24_41, allocate__19)) -// mstore(add(allocate_p_24_41, 96), 2) +// let allocate_p_35_39 := mload(allocate__19) +// mstore(allocate__19, add(allocate_p_35_39, allocate__19)) +// mstore(add(allocate_p_35_39, 96), 2) // mstore(allocate__19, 0x20) // } diff --git a/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul b/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul new file mode 100644 index 00000000..204b444e --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/ssaReverse.yul @@ -0,0 +1,62 @@ +{ + // This is an abi decode function with the SSA transform applied once. + // This test is supposed to verify that the SSA transform is correctly reversed by the full suite. + function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 + { + if iszero(slt(add(offset_12, 0x1f), end_13)) + { + revert(0, 0) + } + let length_15_1 := calldataload(offset_12) + length_15 := length_15_1 + if gt(length_15_1, 0xffffffffffffffff) + { + revert(0, 0) + } + let arrayPos_14_2 := add(offset_12, 0x20) + arrayPos_14 := arrayPos_14_2 + if gt(add(arrayPos_14_2, mul(length_15_1, 0x1)), end_13) + { + revert(0, 0) + } + } + + // prevent inlining + let a,b := abi_decode_t_bytes_calldata_ptr(mload(0),mload(1)) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + a,b := abi_decode_t_bytes_calldata_ptr(a,b) + mstore(a,b) +} +// ---- +// fullSuite +// { +// let a, b := abi_decode_t_bytes_calldata_ptr(mload(0), mload(1)) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// a, b := abi_decode_t_bytes_calldata_ptr(a, b) +// mstore(a, b) +// function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 +// { +// if iszero(slt(add(offset_12, 0x1f), end_13)) +// { +// revert(0, 0) +// } +// length_15 := calldataload(offset_12) +// if gt(length_15, 0xffffffffffffffff) +// { +// revert(0, 0) +// } +// arrayPos_14 := add(offset_12, 0x20) +// if gt(add(add(offset_12, length_15), 0x20), end_13) +// { +// revert(0, 0) +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul b/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul new file mode 100644 index 00000000..2e178f31 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullSuite/ssaReverseComplex.yul @@ -0,0 +1,23 @@ +{ + let a := mload(0) + let b := mload(1) + if mload(2) { + a := mload(b) + b := mload(a) + a := mload(b) + b := mload(a) + } + mstore(a, b) +} +// ---- +// fullSuite +// { +// let a := mload(0) +// let b := mload(1) +// if mload(2) +// { +// a := mload(mload(mload(b))) +// b := mload(a) +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul new file mode 100644 index 00000000..18498e61 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/for_loop.yul @@ -0,0 +1,36 @@ +{ + for { + let a := mload(0) + let b := mload(1) + } + lt(mload(a),mload(b)) + { + a := mload(b) + } + { + b := mload(a) + a := mload(b) + a := mload(b) + a := mload(b) + b := mload(a) + } +} +// ---- +// ssaAndBack +// { +// for { +// let a := mload(0) +// let b := mload(1) +// } +// lt(mload(a), mload(b)) +// { +// a := mload(b) +// } +// { +// let b_3 := mload(a) +// pop(mload(b_3)) +// pop(mload(b_3)) +// let a_6 := mload(b_3) +// b := mload(a_6) +// } +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul new file mode 100644 index 00000000..23c433d1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign.yul @@ -0,0 +1,18 @@ +{ + let a := mload(0) + a := mload(1) + a := mload(2) + a := mload(3) + a := mload(4) + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// pop(mload(0)) +// pop(mload(1)) +// pop(mload(2)) +// pop(mload(3)) +// let a_5 := mload(4) +// mstore(a_5, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul new file mode 100644 index 00000000..fd5981ef --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_if.yul @@ -0,0 +1,22 @@ +{ + let a := mload(0) + if mload(1) + { + a := mload(1) + a := mload(2) + a := mload(3) + } + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// if mload(1) +// { +// pop(mload(1)) +// pop(mload(2)) +// a := mload(3) +// } +// mstore(a, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul new file mode 100644 index 00000000..b0b3efb5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_if.yul @@ -0,0 +1,25 @@ +{ + let a := mload(0) + let b := mload(1) + if mload(2) { + a := mload(b) + b := mload(a) + a := mload(b) + b := mload(a) + } + mstore(a, b) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// let b := mload(1) +// if mload(2) +// { +// let a_3 := mload(b) +// let b_4 := mload(a_3) +// a := mload(b_4) +// b := mload(a) +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul new file mode 100644 index 00000000..50f56b87 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_multi_var_switch.yul @@ -0,0 +1,38 @@ +{ + let a := mload(0) + let b := mload(1) + switch mload(2) + case 0 { + a := mload(b) + b := mload(a) + a := mload(b) + b := mload(a) + } + default { + b := mload(a) + a := mload(b) + b := mload(a) + a := mload(b) + } + mstore(a, b) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// let b := mload(1) +// switch mload(2) +// case 0 { +// let a_3 := mload(b) +// let b_4 := mload(a_3) +// a := mload(b_4) +// b := mload(a) +// } +// default { +// let b_7 := mload(a) +// let a_8 := mload(b_7) +// b := mload(a_8) +// a := mload(b) +// } +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul new file mode 100644 index 00000000..1efbbde7 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/multi_assign_switch.yul @@ -0,0 +1,32 @@ +{ + let a := mload(0) + switch mload(1) + case 0 { + a := mload(1) + a := mload(2) + a := mload(3) + } + default { + a := mload(4) + a := mload(5) + a := mload(6) + } + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// switch mload(1) +// case 0 { +// pop(mload(1)) +// pop(mload(2)) +// a := mload(3) +// } +// default { +// pop(mload(4)) +// pop(mload(5)) +// a := mload(6) +// } +// mstore(a, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul b/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul new file mode 100644 index 00000000..912940c5 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/simple.yul @@ -0,0 +1,12 @@ +{ + let a := mload(0) + a := mload(1) + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// pop(mload(0)) +// let a_2 := mload(1) +// mstore(a_2, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul new file mode 100644 index 00000000..5185245c --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_if.yul @@ -0,0 +1,18 @@ +{ + let a := mload(0) + if mload(1) + { + a := mload(1) + } + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// if mload(1) +// { +// a := mload(1) +// } +// mstore(a, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul new file mode 100644 index 00000000..e0e53b3f --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/single_assign_switch.yul @@ -0,0 +1,24 @@ +{ + let a := mload(0) + switch mload(1) + case 0 { + a := mload(1) + } + default { + a := mload(2) + } + mstore(a, 0) +} +// ---- +// ssaAndBack +// { +// let a := mload(0) +// switch mload(1) +// case 0 { +// a := mload(1) +// } +// default { +// a := mload(2) +// } +// mstore(a, 0) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul b/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul new file mode 100644 index 00000000..d2ba6471 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/ssaReverse.yul @@ -0,0 +1,45 @@ +{ + function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 + { + if iszero(slt(add(offset_12, 0x1f), end_13)) + { + revert(0, 0) + } + length_15 := calldataload(offset_12) + if gt(length_15, 0xffffffffffffffff) + { + revert(0, 0) + } + arrayPos_14 := add(offset_12, 0x20) + if gt(add(add(offset_12, length_15), 0x20), end_13) + { + revert(0, 0) + } + } + // prevent removal of the function + let a,b := abi_decode_t_bytes_calldata_ptr(mload(0),mload(1)) + mstore(a,b) +} +// ---- +// ssaAndBack +// { +// function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 +// { +// if iszero(slt(add(offset_12, 0x1f), end_13)) +// { +// revert(arrayPos_14, arrayPos_14) +// } +// length_15 := calldataload(offset_12) +// if gt(length_15, 0xffffffffffffffff) +// { +// revert(arrayPos_14, arrayPos_14) +// } +// arrayPos_14 := add(offset_12, 0x20) +// if gt(add(add(offset_12, length_15), 0x20), end_13) +// { +// revert(0, 0) +// } +// } +// let a, b := abi_decode_t_bytes_calldata_ptr(mload(0), mload(1)) +// mstore(a, b) +// } diff --git a/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul b/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul new file mode 100644 index 00000000..9f2a046e --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaAndBack/two_vars.yul @@ -0,0 +1,20 @@ +{ + let a := mload(0) + let b := mload(a) + a := mload(b) + b := mload(a) + a := mload(b) + b := mload(a) + mstore(a, b) +} +// ---- +// ssaAndBack +// { +// let a_1 := mload(0) +// let b_2 := mload(a_1) +// let a_3 := mload(b_2) +// let b_4 := mload(a_3) +// let a_5 := mload(b_4) +// let b_6 := mload(a_5) +// mstore(a_5, b_6) +// } diff --git a/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul b/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul new file mode 100644 index 00000000..923a42ba --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaReverser/abi_example.yul @@ -0,0 +1,44 @@ +{ + function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 + { + if iszero(slt(add(offset_12, 0x1f), end_13)) + { + revert(0, 0) + } + let length_15_1 := calldataload(offset_12) + length_15 := length_15_1 + if gt(length_15_1, 0xffffffffffffffff) + { + revert(0, 0) + } + let arrayPos_14_2 := add(offset_12, 0x20) + arrayPos_14 := arrayPos_14_2 + if gt(add(arrayPos_14_2, mul(length_15_1, 0x1)), end_13) + { + revert(0, 0) + } + } +} +// ---- +// ssaReverser +// { +// function abi_decode_t_bytes_calldata_ptr(offset_12, end_13) -> arrayPos_14, length_15 +// { +// if iszero(slt(add(offset_12, 0x1f), end_13)) +// { +// revert(0, 0) +// } +// length_15 := calldataload(offset_12) +// let length_15_1 := length_15 +// if gt(length_15_1, 0xffffffffffffffff) +// { +// revert(0, 0) +// } +// arrayPos_14 := add(offset_12, 0x20) +// let arrayPos_14_2 := arrayPos_14 +// if gt(add(arrayPos_14_2, mul(length_15_1, 0x1)), end_13) +// { +// revert(0, 0) +// } +// } +// } diff --git a/test/libyul/yulOptimizerTests/ssaReverser/simple.yul b/test/libyul/yulOptimizerTests/ssaReverser/simple.yul new file mode 100644 index 00000000..eba1f5f1 --- /dev/null +++ b/test/libyul/yulOptimizerTests/ssaReverser/simple.yul @@ -0,0 +1,14 @@ +{ + let a := mload(1) + let a_1 := mload(0) + a := a_1 + mstore(a_1, 0) +} +// ---- +// ssaReverser +// { +// let a := mload(1) +// a := mload(0) +// let a_1 := a +// mstore(a_1, 0) +// } diff --git a/test/tools/yulopti.cpp b/test/tools/yulopti.cpp index efd1ba05..7203ef54 100644 --- a/test/tools/yulopti.cpp +++ b/test/tools/yulopti.cpp @@ -46,6 +46,7 @@ #include <libyul/optimiser/UnusedPruner.h> #include <libyul/optimiser/ExpressionJoiner.h> #include <libyul/optimiser/RedundantAssignEliminator.h> +#include <libyul/optimiser/SSAReverser.h> #include <libyul/optimiser/SSATransform.h> #include <libyul/optimiser/StructuralSimplifier.h> #include <libyul/optimiser/VarDeclInitializer.h> @@ -129,7 +130,7 @@ public: cout << "(q)quit/(f)flatten/(c)se/initialize var(d)ecls/(x)plit/(j)oin/(g)rouper/(h)oister/" << endl; cout << " (e)xpr inline/(i)nline/(s)implify/(u)nusedprune/ss(a) transform/" << endl; cout << " (r)edundant assign elim./re(m)aterializer/f(o)r-loop-pre-rewriter/" << endl; - cout << " s(t)ructural simplifier/equi(v)alent function combiner? " << endl; + cout << " s(t)ructural simplifier/equi(v)alent function combiner/ssa re(V)erser? " << endl; cout.flush(); int option = readStandardInputChar(); cout << ' ' << char(option) << endl; @@ -188,6 +189,9 @@ public: case 'v': EquivalentFunctionCombiner::run(*m_ast); break; + case 'V': + SSAReverser::run(*m_ast); + break; default: cout << "Unknown option." << endl; } |