/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup fn */ #include "FN_multi_function.hh" namespace blender::fn::multi_function { class Variable; class Instruction; class CallInstruction; class BranchInstruction; class DestructInstruction; class DummyInstruction; class ReturnInstruction; class Procedure; /** Every instruction has exactly one of these types. */ enum class InstructionType { Call, Branch, Destruct, Dummy, Return, }; /** * An #InstructionCursor points to a position in a multi-function procedure, where an instruction * can be inserted. */ class InstructionCursor { public: enum Type { None, Entry, Call, Destruct, Branch, Dummy, }; private: Type type_ = None; Instruction *instruction_ = nullptr; /* Only used when it is a branch instruction. */ bool branch_output_ = false; public: InstructionCursor() = default; InstructionCursor(CallInstruction &instruction); InstructionCursor(DestructInstruction &instruction); InstructionCursor(BranchInstruction &instruction, bool branch_output); InstructionCursor(DummyInstruction &instruction); static InstructionCursor ForEntry(); Instruction *next(Procedure &procedure) const; void set_next(Procedure &procedure, Instruction *new_instruction) const; Instruction *instruction() const; Type type() const; friend bool operator==(const InstructionCursor &a, const InstructionCursor &b) { return a.type_ == b.type_ && a.instruction_ == b.instruction_ && a.branch_output_ == b.branch_output_; } friend bool operator!=(const InstructionCursor &a, const InstructionCursor &b) { return !(a == b); } }; /** * A variable is similar to a virtual register in other libraries. During evaluation, every is * either uninitialized or contains a value for every index (remember, a multi-function procedure * is always evaluated for many indices at the same time). */ class Variable : NonCopyable, NonMovable { private: DataType data_type_; Vector users_; std::string name_; int index_in_graph_; friend Procedure; friend CallInstruction; friend BranchInstruction; friend DestructInstruction; public: DataType data_type() const; Span users(); StringRefNull name() const; void set_name(std::string name); int index_in_procedure() const; }; /** Base class for all instruction types. */ class Instruction : NonCopyable, NonMovable { protected: InstructionType type_; Vector prev_; friend Procedure; friend CallInstruction; friend BranchInstruction; friend DestructInstruction; friend DummyInstruction; friend ReturnInstruction; public: InstructionType type() const; /** * Other instructions that come before this instruction. There can be multiple previous * instructions when branching is used in the procedure. */ Span prev() const; }; /** * References a multi-function that is evaluated when the instruction is executed. It also * references the variables whose data will be passed into the multi-function. */ class CallInstruction : public Instruction { private: const MultiFunction *fn_ = nullptr; Instruction *next_ = nullptr; MutableSpan params_; friend Procedure; public: const MultiFunction &fn() const; Instruction *next(); const Instruction *next() const; void set_next(Instruction *instruction); void set_param_variable(int param_index, Variable *variable); void set_params(Span variables); Span params(); Span params() const; }; /** * What makes a branch instruction special is that it has two successor instructions. One that will * be used when a condition variable was true, and one otherwise. */ class BranchInstruction : public Instruction { private: Variable *condition_ = nullptr; Instruction *branch_true_ = nullptr; Instruction *branch_false_ = nullptr; friend Procedure; public: Variable *condition(); const Variable *condition() const; void set_condition(Variable *variable); Instruction *branch_true(); const Instruction *branch_true() const; void set_branch_true(Instruction *instruction); Instruction *branch_false(); const Instruction *branch_false() const; void set_branch_false(Instruction *instruction); }; /** * A destruct instruction destructs a single variable. So the variable value will be uninitialized * after this instruction. All variables that are not output variables of the procedure, have to be * destructed before the procedure ends. Destructing early is generally a good thing, because it * might help with memory buffer reuse, which decreases memory-usage and increases performance. */ class DestructInstruction : public Instruction { private: Variable *variable_ = nullptr; Instruction *next_ = nullptr; friend Procedure; public: Variable *variable(); const Variable *variable() const; void set_variable(Variable *variable); Instruction *next(); const Instruction *next() const; void set_next(Instruction *instruction); }; /** * This instruction does nothing, it just exists to building a procedure simpler in some cases. */ class DummyInstruction : public Instruction { private: Instruction *next_ = nullptr; friend Procedure; public: Instruction *next(); const Instruction *next() const; void set_next(Instruction *instruction); }; /** * This instruction ends the procedure. */ class ReturnInstruction : public Instruction { }; /** * Inputs and outputs of the entire procedure network. */ struct MFParameter { ParamType::InterfaceType type; Variable *variable; }; struct ConstMFParameter { ParamType::InterfaceType type; const Variable *variable; }; /** * A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of * variables and instructions that operate on those variables. Branching and looping within the * procedure is supported as well. * * Typically, a #Procedure should be constructed using a #ProcedureBuilder, which has many more * utility methods for common use cases. */ class Procedure : NonCopyable, NonMovable { private: LinearAllocator<> allocator_; Vector call_instructions_; Vector branch_instructions_; Vector destruct_instructions_; Vector dummy_instructions_; Vector return_instructions_; Vector variables_; Vector params_; Vector> owned_functions_; Instruction *entry_ = nullptr; friend class ProcedureDotExport; public: Procedure() = default; ~Procedure(); Variable &new_variable(DataType data_type, std::string name = ""); CallInstruction &new_call_instruction(const MultiFunction &fn); BranchInstruction &new_branch_instruction(); DestructInstruction &new_destruct_instruction(); DummyInstruction &new_dummy_instruction(); ReturnInstruction &new_return_instruction(); void add_parameter(ParamType::InterfaceType interface_type, Variable &variable); Span params() const; template const MultiFunction &construct_function(Args &&...args); Instruction *entry(); const Instruction *entry() const; void set_entry(Instruction &entry); Span variables(); Span variables() const; std::string to_dot() const; bool validate() const; private: bool validate_all_instruction_pointers_set() const; bool validate_all_params_provided() const; bool validate_same_variables_in_one_call() const; bool validate_parameters() const; bool validate_initialization() const; struct InitState { bool can_be_initialized = false; bool can_be_uninitialized = false; }; InitState find_initialization_state_before_instruction(const Instruction &target_instruction, const Variable &variable) const; }; /* -------------------------------------------------------------------- */ /** \name #InstructionCursor Inline Methods * \{ */ inline InstructionCursor::InstructionCursor(CallInstruction &instruction) : type_(Call), instruction_(&instruction) { } inline InstructionCursor::InstructionCursor(DestructInstruction &instruction) : type_(Destruct), instruction_(&instruction) { } inline InstructionCursor::InstructionCursor(BranchInstruction &instruction, bool branch_output) : type_(Branch), instruction_(&instruction), branch_output_(branch_output) { } inline InstructionCursor::InstructionCursor(DummyInstruction &instruction) : type_(Dummy), instruction_(&instruction) { } inline InstructionCursor InstructionCursor::ForEntry() { InstructionCursor cursor; cursor.type_ = Type::Entry; return cursor; } inline Instruction *InstructionCursor::instruction() const { /* This isn't really const correct unfortunately, because to make it correct we'll need a const * version of #InstructionCursor. */ return instruction_; } inline InstructionCursor::Type InstructionCursor::type() const { return type_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name #Variable Inline Methods * \{ */ inline DataType Variable::data_type() const { return data_type_; } inline Span Variable::users() { return users_; } inline StringRefNull Variable::name() const { return name_; } inline int Variable::index_in_procedure() const { return index_in_graph_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name #Instruction Inline Methods * \{ */ inline InstructionType Instruction::type() const { return type_; } inline Span Instruction::prev() const { return prev_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name #CallInstruction Inline Methods * \{ */ inline const MultiFunction &CallInstruction::fn() const { return *fn_; } inline Instruction *CallInstruction::next() { return next_; } inline const Instruction *CallInstruction::next() const { return next_; } inline Span CallInstruction::params() { return params_; } inline Span CallInstruction::params() const { return params_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name #BranchInstruction Inline Methods * \{ */ inline Variable *BranchInstruction::condition() { return condition_; } inline const Variable *BranchInstruction::condition() const { return condition_; } inline Instruction *BranchInstruction::branch_true() { return branch_true_; } inline const Instruction *BranchInstruction::branch_true() const { return branch_true_; } inline Instruction *BranchInstruction::branch_false() { return branch_false_; } inline const Instruction *BranchInstruction::branch_false() const { return branch_false_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name #DestructInstruction Inline Methods * \{ */ inline Variable *DestructInstruction::variable() { return variable_; } inline const Variable *DestructInstruction::variable() const { return variable_; } inline Instruction *DestructInstruction::next() { return next_; } inline const Instruction *DestructInstruction::next() const { return next_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name #DummyInstruction Inline Methods * \{ */ inline Instruction *DummyInstruction::next() { return next_; } inline const Instruction *DummyInstruction::next() const { return next_; } /** \} */ /* -------------------------------------------------------------------- */ /** \name #Procedure Inline Methods * \{ */ inline Span Procedure::params() const { static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter)); return params_.as_span().cast(); } inline Instruction *Procedure::entry() { return entry_; } inline const Instruction *Procedure::entry() const { return entry_; } inline Span Procedure::variables() { return variables_; } inline Span Procedure::variables() const { return variables_; } template inline const MultiFunction &Procedure::construct_function(Args &&...args) { destruct_ptr fn = allocator_.construct(std::forward(args)...); const MultiFunction &fn_ref = *fn; owned_functions_.append(std::move(fn)); return fn_ref; } /** \} */ } // namespace blender::fn::multi_function