/* 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 . */ // SPDX-License-Identifier: GPL-3.0 /** * Control flow graph and stack layout structures used during code generation. */ #pragma once #include #include #include #include #include #include #include #include #include namespace solidity::yul { /// The following structs describe different kinds of stack slots. /// Each stack slot is equality- and less-than-comparable and /// specifies an attribute ``canBeFreelyGenerated`` that is true, /// if a slot of this kind always has a known value at compile time and /// therefore can safely be removed from the stack at any time and then /// regenerated later. /// The label pushed as return label before a function call, i.e. the label the call is supposed to return to. struct FunctionCallReturnLabelSlot { std::reference_wrapper call; bool operator==(FunctionCallReturnLabelSlot const& _rhs) const { return &call.get() == &_rhs.call.get(); } bool operator<(FunctionCallReturnLabelSlot const& _rhs) const { return &call.get() < &_rhs.call.get(); } static constexpr bool canBeFreelyGenerated = true; }; /// The return jump target of a function while generating the code of the function body. /// I.e. the caller of a function pushes a ``FunctionCallReturnLabelSlot`` (see above) before jumping to the function and /// this very slot is viewed as ``FunctionReturnLabelSlot`` inside the function body and jumped to when returning from /// the function. struct FunctionReturnLabelSlot { std::reference_wrapper function; bool operator==(FunctionReturnLabelSlot const& _rhs) const { // There can never be return label slots of different functions on stack simultaneously. yulAssert(&function.get() == &_rhs.function.get(), ""); return true; } bool operator<(FunctionReturnLabelSlot const& _rhs) const { // There can never be return label slots of different functions on stack simultaneously. yulAssert(&function.get() == &_rhs.function.get(), ""); return false; } static constexpr bool canBeFreelyGenerated = false; }; /// A slot containing the current value of a particular variable. struct VariableSlot { std::reference_wrapper variable; std::shared_ptr debugData{}; bool operator==(VariableSlot const& _rhs) const { return &variable.get() == &_rhs.variable.get(); } bool operator<(VariableSlot const& _rhs) const { return &variable.get() < &_rhs.variable.get(); } static constexpr bool canBeFreelyGenerated = false; }; /// A slot containing a literal value. struct LiteralSlot { u256 value; std::shared_ptr debugData{}; bool operator==(LiteralSlot const& _rhs) const { return value == _rhs.value; } bool operator<(LiteralSlot const& _rhs) const { return value < _rhs.value; } static constexpr bool canBeFreelyGenerated = true; }; /// A slot containing the index-th return value of a previous call. struct TemporarySlot { /// The call that returned this slot. std::reference_wrapper call; /// Specifies to which of the values returned by the call this slot refers. /// index == 0 refers to the slot deepest in the stack after the call. size_t index = 0; bool operator==(TemporarySlot const& _rhs) const { return &call.get() == &_rhs.call.get() && index == _rhs.index; } bool operator<(TemporarySlot const& _rhs) const { return std::make_pair(&call.get(), index) < std::make_pair(&_rhs.call.get(), _rhs.index); } static constexpr bool canBeFreelyGenerated = false; }; /// A slot containing an arbitrary value that is always eventually popped and never used. /// Used to maintain stack balance on control flow joins. struct JunkSlot { bool operator==(JunkSlot const&) const { return true; } bool operator<(JunkSlot const&) const { return false; } static constexpr bool canBeFreelyGenerated = true; }; using StackSlot = std::variant; /// The stack top is usually the last element of the vector. using Stack = std::vector; /// @returns true if @a _slot can be generated on the stack at any time. inline bool canBeFreelyGenerated(StackSlot const& _slot) { return std::visit([](auto const& _typedSlot) { return std::decay_t::canBeFreelyGenerated; }, _slot); } /// Control flow graph consisting of ``CFG::BasicBlock``s connected by control flow. struct CFG { explicit CFG() {} CFG(CFG const&) = delete; CFG(CFG&&) = delete; CFG& operator=(CFG const&) = delete; CFG& operator=(CFG&&) = delete; ~CFG() = default; struct BuiltinCall { std::shared_ptr debugData; std::reference_wrapper builtin; std::reference_wrapper functionCall; /// Number of proper arguments with a position on the stack, excluding literal arguments. /// Literal arguments (like the literal string in ``datasize``) do not have a location on the stack, /// but are handled internally by the builtin's code generation function. size_t arguments = 0; }; struct FunctionCall { std::shared_ptr debugData; std::reference_wrapper function; std::reference_wrapper functionCall; /// True, if the call is recursive, i.e. entering the function involves a control flow path (potentially involving /// more intermediate function calls) that leads back to this very call. bool recursive = false; }; struct Assignment { std::shared_ptr debugData; /// The variables being assigned to also occur as ``output`` in the ``Operation`` containing /// the assignment, but are also stored here for convenience. std::vector variables; }; struct Operation { /// Stack slots this operation expects at the top of the stack and consumes. Stack input; /// Stack slots this operation leaves on the stack as output. Stack output; std::variant operation; }; struct FunctionInfo; /// A basic control flow block containing ``Operation``s acting on the stack. /// Maintains a list of entry blocks and a typed exit. struct BasicBlock { struct MainExit {}; struct ConditionalJump { std::shared_ptr debugData; StackSlot condition; BasicBlock* nonZero = nullptr; BasicBlock* zero = nullptr; }; struct Jump { std::shared_ptr debugData; BasicBlock* target = nullptr; /// The only backwards jumps are jumps from loop post to loop condition. bool backwards = false; }; struct FunctionReturn { std::shared_ptr debugData; CFG::FunctionInfo* info = nullptr; }; struct Terminated {}; std::shared_ptr debugData; std::vector entries; std::vector operations; /// True, if the block is the beginning of a disconnected subgraph. That is, if no block that is reachable /// from this block is an ancestor of this block. In other words, this is true, if this block is the target /// of a cut-edge/bridge in the CFG or if the block itself terminates. bool isStartOfSubGraph = false; /// True, if there is a path from this block to a function return. bool needsCleanStack = false; /// If the block starts a sub-graph and does not lead to a function return, we are free to add junk to it. bool allowsJunk() const { return isStartOfSubGraph && !needsCleanStack; } std::variant exit = MainExit{}; }; struct FunctionInfo { std::shared_ptr debugData; Scope::Function const& function; BasicBlock* entry = nullptr; std::vector parameters; std::vector returnVariables; std::vector exits; }; /// The main entry point, i.e. the start of the outermost Yul block. BasicBlock* entry = nullptr; /// Subgraphs for functions. std::map functionInfo; /// List of functions in order of declaration. std::list functions; /// Container for blocks for explicit ownership. std::list blocks; /// Container for generated variables for explicit ownership. /// Ghost variables are generated to store switch conditions when transforming the control flow /// of a switch to a sequence of conditional jumps. std::list ghostVariables; /// Container for generated calls for explicit ownership. /// Ghost calls are used for the equality comparisons of the switch condition ghost variable with /// the switch case literals when transforming the control flow of a switch to a sequence of conditional jumps. std::list ghostCalls; BasicBlock& makeBlock(std::shared_ptr _debugData) { return blocks.emplace_back(BasicBlock{std::move(_debugData), {}, {}}); } }; }