Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--source/blender/functions/CMakeLists.txt7
-rw-r--r--source/blender/functions/FN_multi_function_procedure.hh360
-rw-r--r--source/blender/functions/FN_multi_function_procedure_builder.hh249
-rw-r--r--source/blender/functions/FN_multi_function_procedure_executor.hh38
-rw-r--r--source/blender/functions/intern/multi_function_procedure.cc326
-rw-r--r--source/blender/functions/intern/multi_function_procedure_builder.cc161
-rw-r--r--source/blender/functions/intern/multi_function_procedure_executor.cc1140
-rw-r--r--source/blender/functions/tests/FN_multi_function_procedure_test.cc278
8 files changed, 2559 insertions, 0 deletions
diff --git a/source/blender/functions/CMakeLists.txt b/source/blender/functions/CMakeLists.txt
index f8d2acc74a8..a44086cfec1 100644
--- a/source/blender/functions/CMakeLists.txt
+++ b/source/blender/functions/CMakeLists.txt
@@ -33,6 +33,9 @@ set(SRC
intern/generic_virtual_vector_array.cc
intern/multi_function.cc
intern/multi_function_builder.cc
+ intern/multi_function_procedure.cc
+ intern/multi_function_procedure_builder.cc
+ intern/multi_function_procedure_executor.cc
FN_cpp_type.hh
FN_cpp_type_make.hh
@@ -48,6 +51,9 @@ set(SRC
FN_multi_function_data_type.hh
FN_multi_function_param_type.hh
FN_multi_function_params.hh
+ FN_multi_function_procedure.hh
+ FN_multi_function_procedure_builder.hh
+ FN_multi_function_procedure_executor.hh
FN_multi_function_signature.hh
)
@@ -62,6 +68,7 @@ if(WITH_GTESTS)
tests/FN_cpp_type_test.cc
tests/FN_generic_span_test.cc
tests/FN_generic_vector_array_test.cc
+ tests/FN_multi_function_procedure_test.cc
tests/FN_multi_function_test.cc
)
set(TEST_LIB
diff --git a/source/blender/functions/FN_multi_function_procedure.hh b/source/blender/functions/FN_multi_function_procedure.hh
new file mode 100644
index 00000000000..b9540540992
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure.hh
@@ -0,0 +1,360 @@
+/*
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function.hh"
+
+namespace blender::fn {
+
+class MFVariable;
+class MFInstruction;
+class MFCallInstruction;
+class MFBranchInstruction;
+class MFDestructInstruction;
+class MFDummyInstruction;
+class MFProcedure;
+
+enum class MFInstructionType {
+ Call,
+ Branch,
+ Destruct,
+ Dummy,
+};
+
+class MFVariable : NonCopyable, NonMovable {
+ private:
+ MFDataType data_type_;
+ Vector<MFInstruction *> users_;
+ std::string name_;
+ int id_;
+
+ friend MFProcedure;
+ friend MFCallInstruction;
+ friend MFBranchInstruction;
+ friend MFDestructInstruction;
+
+ public:
+ MFDataType data_type() const;
+ Span<MFInstruction *> users();
+
+ StringRefNull name() const;
+ void set_name(std::string name);
+
+ int id() const;
+};
+
+class MFInstruction : NonCopyable, NonMovable {
+ protected:
+ MFInstructionType type_;
+ Vector<MFInstruction *> prev_;
+
+ friend MFProcedure;
+ friend MFCallInstruction;
+ friend MFBranchInstruction;
+ friend MFDestructInstruction;
+ friend MFDummyInstruction;
+
+ public:
+ MFInstructionType type() const;
+ Span<MFInstruction *> prev();
+};
+
+class MFCallInstruction : public MFInstruction {
+ private:
+ const MultiFunction *fn_ = nullptr;
+ MFInstruction *next_ = nullptr;
+ MutableSpan<MFVariable *> params_;
+
+ friend MFProcedure;
+
+ public:
+ const MultiFunction &fn() const;
+
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+
+ void set_param_variable(int param_index, MFVariable *variable);
+ void set_params(Span<MFVariable *> variables);
+ Span<MFVariable *> params();
+ Span<const MFVariable *> params() const;
+};
+
+class MFBranchInstruction : public MFInstruction {
+ private:
+ MFVariable *condition_ = nullptr;
+ MFInstruction *branch_true_ = nullptr;
+ MFInstruction *branch_false_ = nullptr;
+
+ public:
+ MFVariable *condition();
+ const MFVariable *condition() const;
+ void set_condition(MFVariable *variable);
+
+ MFInstruction *branch_true();
+ const MFInstruction *branch_true() const;
+ void set_branch_true(MFInstruction *instruction);
+
+ MFInstruction *branch_false();
+ const MFInstruction *branch_false() const;
+ void set_branch_false(MFInstruction *instruction);
+};
+
+class MFDestructInstruction : public MFInstruction {
+ private:
+ MFVariable *variable_ = nullptr;
+ MFInstruction *next_ = nullptr;
+
+ public:
+ MFVariable *variable();
+ const MFVariable *variable() const;
+ void set_variable(MFVariable *variable);
+
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+};
+
+class MFDummyInstruction : public MFInstruction {
+ private:
+ MFInstruction *next_ = nullptr;
+
+ public:
+ MFInstruction *next();
+ const MFInstruction *next() const;
+ void set_next(MFInstruction *instruction);
+};
+
+class MFProcedure : NonCopyable, NonMovable {
+ private:
+ LinearAllocator<> allocator_;
+ Vector<MFCallInstruction *> call_instructions_;
+ Vector<MFBranchInstruction *> branch_instructions_;
+ Vector<MFDestructInstruction *> destruct_instructions_;
+ Vector<MFDummyInstruction *> dummy_instructions_;
+ Vector<MFVariable *> variables_;
+ Vector<std::pair<MFParamType::InterfaceType, MFVariable *>> params_;
+ MFInstruction *entry_ = nullptr;
+
+ public:
+ MFProcedure() = default;
+ ~MFProcedure();
+
+ MFVariable &new_variable(MFDataType data_type, std::string name = "");
+ MFCallInstruction &new_call_instruction(const MultiFunction &fn);
+ MFBranchInstruction &new_branch_instruction();
+ MFDestructInstruction &new_destruct_instruction();
+ MFDummyInstruction &new_dummy_instruction();
+
+ void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
+
+ Span<std::pair<MFParamType::InterfaceType, const MFVariable *>> params() const;
+
+ MFInstruction *entry();
+ const MFInstruction *entry() const;
+ void set_entry(MFInstruction &entry);
+
+ Span<MFVariable *> variables();
+ Span<const MFVariable *> variables() const;
+
+ void assert_valid() const;
+
+ std::string to_dot() const;
+};
+
+namespace multi_function_procedure_types {
+using MFVariable = fn::MFVariable;
+using MFInstruction = fn::MFInstruction;
+using MFCallInstruction = fn::MFCallInstruction;
+using MFBranchInstruction = fn::MFBranchInstruction;
+using MFDestructInstruction = fn::MFDestructInstruction;
+using MFProcedure = fn::MFProcedure;
+} // namespace multi_function_procedure_types
+
+/* --------------------------------------------------------------------
+ * MFVariable inline methods.
+ */
+
+inline MFDataType MFVariable::data_type() const
+{
+ return data_type_;
+}
+
+inline Span<MFInstruction *> MFVariable::users()
+{
+ return users_;
+}
+
+inline StringRefNull MFVariable::name() const
+{
+ return name_;
+}
+
+inline int MFVariable::id() const
+{
+ return id_;
+}
+
+/* --------------------------------------------------------------------
+ * MFInstruction inline methods.
+ */
+
+inline MFInstructionType MFInstruction::type() const
+{
+ return type_;
+}
+
+inline Span<MFInstruction *> MFInstruction::prev()
+{
+ return prev_;
+}
+
+/* --------------------------------------------------------------------
+ * MFCallInstruction inline methods.
+ */
+
+inline const MultiFunction &MFCallInstruction::fn() const
+{
+ return *fn_;
+}
+
+inline MFInstruction *MFCallInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFCallInstruction::next() const
+{
+ return next_;
+}
+
+inline Span<MFVariable *> MFCallInstruction::params()
+{
+ return params_;
+}
+
+inline Span<const MFVariable *> MFCallInstruction::params() const
+{
+ return params_;
+}
+
+/* --------------------------------------------------------------------
+ * MFBranchInstruction inline methods.
+ */
+
+inline MFVariable *MFBranchInstruction::condition()
+{
+ return condition_;
+}
+
+inline const MFVariable *MFBranchInstruction::condition() const
+{
+ return condition_;
+}
+
+inline MFInstruction *MFBranchInstruction::branch_true()
+{
+ return branch_true_;
+}
+
+inline const MFInstruction *MFBranchInstruction::branch_true() const
+{
+ return branch_true_;
+}
+
+inline MFInstruction *MFBranchInstruction::branch_false()
+{
+ return branch_false_;
+}
+
+inline const MFInstruction *MFBranchInstruction::branch_false() const
+{
+ return branch_false_;
+}
+
+/* --------------------------------------------------------------------
+ * MFDestructInstruction inline methods.
+ */
+
+inline MFVariable *MFDestructInstruction::variable()
+{
+ return variable_;
+}
+
+inline const MFVariable *MFDestructInstruction::variable() const
+{
+ return variable_;
+}
+
+inline MFInstruction *MFDestructInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFDestructInstruction::next() const
+{
+ return next_;
+}
+
+/* --------------------------------------------------------------------
+ * MFDummyInstruction inline methods.
+ */
+
+inline MFInstruction *MFDummyInstruction::next()
+{
+ return next_;
+}
+
+inline const MFInstruction *MFDummyInstruction::next() const
+{
+ return next_;
+}
+
+/* --------------------------------------------------------------------
+ * MFProcedure inline methods.
+ */
+
+inline Span<std::pair<MFParamType::InterfaceType, const MFVariable *>> MFProcedure::params() const
+{
+ return params_.as_span().cast<std::pair<MFParamType::InterfaceType, const MFVariable *>>();
+}
+
+inline MFInstruction *MFProcedure::entry()
+{
+ return entry_;
+}
+
+inline const MFInstruction *MFProcedure::entry() const
+{
+ return entry_;
+}
+
+inline Span<MFVariable *> MFProcedure::variables()
+{
+ return variables_;
+}
+
+inline Span<const MFVariable *> MFProcedure::variables() const
+{
+ return variables_;
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_procedure_builder.hh b/source/blender/functions/FN_multi_function_procedure_builder.hh
new file mode 100644
index 00000000000..1088c18a8b9
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure_builder.hh
@@ -0,0 +1,249 @@
+/*
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+namespace blender::fn {
+
+class MFInstructionCursor {
+ private:
+ MFInstruction *instruction_ = nullptr;
+ /* Only used when it is a branch instruction. */
+ bool branch_output_ = false;
+ /* Only used when instruction is null. */
+ bool is_entry_ = false;
+
+ public:
+ MFInstructionCursor() = default;
+
+ MFInstructionCursor(MFCallInstruction &instruction);
+ MFInstructionCursor(MFDestructInstruction &instruction);
+ MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
+ MFInstructionCursor(MFDummyInstruction &instruction);
+
+ static MFInstructionCursor Entry();
+
+ void insert(MFProcedure &procedure, MFInstruction *new_instruction);
+};
+
+class MFProcedureBuilder {
+ private:
+ MFProcedure *procedure_ = nullptr;
+ Vector<MFInstructionCursor> cursors_;
+
+ public:
+ struct Branch;
+ struct Loop;
+
+ MFProcedureBuilder(MFProcedure &procedure,
+ MFInstructionCursor initial_cursor = MFInstructionCursor::Entry());
+
+ MFProcedureBuilder(Span<MFProcedureBuilder *> builders);
+
+ MFProcedureBuilder(Branch &branch);
+
+ void set_cursor(const MFInstructionCursor &cursor);
+ void set_cursor(Span<MFInstructionCursor> cursors);
+ void set_cursor(Span<MFProcedureBuilder *> builders);
+ void set_cursor_after_branch(Branch &branch);
+ void set_cursor_after_loop(Loop &loop);
+
+ void add_destruct(MFVariable &variable);
+ void add_destruct(Span<MFVariable *> variables);
+
+ Branch add_branch(MFVariable &condition);
+
+ Loop add_loop();
+ void add_loop_continue(Loop &loop);
+ void add_loop_break(Loop &loop);
+
+ MFCallInstruction &add_call_with_no_variables(const MultiFunction &fn);
+ MFCallInstruction &add_call_with_all_variables(const MultiFunction &fn,
+ Span<MFVariable *> param_variables);
+
+ Vector<MFVariable *> add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables = {});
+
+ template<int OutputN>
+ std::array<MFVariable *, OutputN> add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables = {});
+
+ void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);
+ MFVariable &add_parameter(MFParamType param_type, std::string name = "");
+
+ MFVariable &add_input_parameter(MFDataType data_type, std::string name = "");
+ template<typename T> MFVariable &add_single_input_parameter(std::string name = "");
+ template<typename T> MFVariable &add_single_mutable_parameter(std::string name = "");
+
+ void add_output_parameter(MFVariable &variable);
+
+ private:
+ void link_to_cursors(MFInstruction *instruction);
+};
+
+struct MFProcedureBuilder::Branch {
+ MFProcedureBuilder branch_true;
+ MFProcedureBuilder branch_false;
+};
+
+struct MFProcedureBuilder::Loop {
+ MFInstruction *begin = nullptr;
+ MFDummyInstruction *end = nullptr;
+};
+
+/* --------------------------------------------------------------------
+ * MFInstructionCursor inline methods.
+ */
+
+inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
+ : instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
+ : instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
+ bool branch_output)
+ : instruction_(&instruction), branch_output_(branch_output)
+{
+}
+
+inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
+ : instruction_(&instruction)
+{
+}
+
+inline MFInstructionCursor MFInstructionCursor::Entry()
+{
+ MFInstructionCursor cursor;
+ cursor.is_entry_ = true;
+ return cursor;
+}
+
+/* --------------------------------------------------------------------
+ * MFProcedureBuilder inline methods.
+ */
+
+inline MFProcedureBuilder::MFProcedureBuilder(Branch &branch)
+ : MFProcedureBuilder(*branch.branch_true.procedure_)
+{
+ this->set_cursor_after_branch(branch);
+}
+
+inline MFProcedureBuilder::MFProcedureBuilder(MFProcedure &procedure,
+ MFInstructionCursor initial_cursor)
+ : procedure_(&procedure), cursors_({initial_cursor})
+{
+}
+
+inline MFProcedureBuilder::MFProcedureBuilder(Span<MFProcedureBuilder *> builders)
+ : MFProcedureBuilder(*builders[0]->procedure_)
+{
+ this->set_cursor(builders);
+}
+
+inline void MFProcedureBuilder::set_cursor(const MFInstructionCursor &cursor)
+{
+ cursors_ = {cursor};
+}
+
+inline void MFProcedureBuilder::set_cursor(Span<MFInstructionCursor> cursors)
+{
+ cursors_ = cursors;
+}
+
+inline void MFProcedureBuilder::set_cursor_after_branch(Branch &branch)
+{
+ this->set_cursor({&branch.branch_false, &branch.branch_true});
+}
+
+inline void MFProcedureBuilder::set_cursor_after_loop(Loop &loop)
+{
+ this->set_cursor(MFInstructionCursor{*loop.end});
+}
+
+inline void MFProcedureBuilder::set_cursor(Span<MFProcedureBuilder *> builders)
+{
+ cursors_.clear();
+ for (MFProcedureBuilder *builder : builders) {
+ cursors_.extend(builder->cursors_);
+ }
+}
+
+template<int OutputN>
+inline std::array<MFVariable *, OutputN> MFProcedureBuilder::add_call(
+ const MultiFunction &fn, Span<MFVariable *> input_and_mutable_variables)
+{
+ Vector<MFVariable *> output_variables = this->add_call(fn, input_and_mutable_variables);
+ BLI_assert(output_variables.size() == OutputN);
+
+ std::array<MFVariable *, OutputN> output_array;
+ initialized_copy_n(output_variables.data(), OutputN, output_array.data());
+ return output_array;
+}
+
+inline void MFProcedureBuilder::add_parameter(MFParamType::InterfaceType interface_type,
+ MFVariable &variable)
+{
+ procedure_->add_parameter(interface_type, variable);
+}
+
+inline MFVariable &MFProcedureBuilder::add_parameter(MFParamType param_type, std::string name)
+{
+ MFVariable &variable = procedure_->new_variable(param_type.data_type(), std::move(name));
+ this->add_parameter(param_type.interface_type(), variable);
+ return variable;
+}
+
+inline MFVariable &MFProcedureBuilder::add_input_parameter(MFDataType data_type, std::string name)
+{
+ return this->add_parameter(MFParamType(MFParamType::Input, data_type), std::move(name));
+}
+
+template<typename T>
+inline MFVariable &MFProcedureBuilder::add_single_input_parameter(std::string name)
+{
+ return this->add_parameter(MFParamType::ForSingleInput(CPPType::get<T>()), std::move(name));
+}
+
+template<typename T>
+inline MFVariable &MFProcedureBuilder::add_single_mutable_parameter(std::string name)
+{
+ return this->add_parameter(MFParamType::ForMutableSingle(CPPType::get<T>()), std::move(name));
+}
+
+inline void MFProcedureBuilder::add_output_parameter(MFVariable &variable)
+{
+ this->add_parameter(MFParamType::Output, variable);
+}
+
+inline void MFProcedureBuilder::link_to_cursors(MFInstruction *instruction)
+{
+ for (MFInstructionCursor &cursor : cursors_) {
+ cursor.insert(*procedure_, instruction);
+ }
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/FN_multi_function_procedure_executor.hh b/source/blender/functions/FN_multi_function_procedure_executor.hh
new file mode 100644
index 00000000000..e352be41d9e
--- /dev/null
+++ b/source/blender/functions/FN_multi_function_procedure_executor.hh
@@ -0,0 +1,38 @@
+/*
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup fn
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+namespace blender::fn {
+
+class MFProcedureExecutor : public MultiFunction {
+ private:
+ MFSignature signature_;
+ const MFProcedure &procedure_;
+
+ public:
+ MFProcedureExecutor(std::string name, const MFProcedure &procedure);
+
+ void call(IndexMask mask, MFParams params, MFContext context) const override;
+};
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure.cc b/source/blender/functions/intern/multi_function_procedure.cc
new file mode 100644
index 00000000000..d9bf611fa34
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure.cc
@@ -0,0 +1,326 @@
+/*
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure.hh"
+
+#include "BLI_dot_export.hh"
+
+namespace blender::fn {
+
+void MFVariable::set_name(std::string name)
+{
+ name_ = std::move(name);
+}
+
+void MFCallInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+void MFCallInstruction::set_param_variable(int param_index, MFVariable *variable)
+{
+ if (params_[param_index] != nullptr) {
+ params_[param_index]->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ BLI_assert(fn_->param_type(param_index).data_type() == variable->data_type());
+ variable->users_.append(this);
+ }
+ params_[param_index] = variable;
+}
+
+void MFCallInstruction::set_params(Span<MFVariable *> variables)
+{
+ BLI_assert(variables.size() == params_.size());
+ for (const int i : variables.index_range()) {
+ this->set_param_variable(i, variables[i]);
+ }
+}
+
+void MFBranchInstruction::set_condition(MFVariable *variable)
+{
+ if (condition_ != nullptr) {
+ condition_->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ variable->users_.append(this);
+ }
+ condition_ = variable;
+}
+
+void MFBranchInstruction::set_branch_true(MFInstruction *instruction)
+{
+ if (branch_true_ != nullptr) {
+ branch_true_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ branch_true_ = instruction;
+}
+
+void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
+{
+ if (branch_false_ != nullptr) {
+ branch_false_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ branch_false_ = instruction;
+}
+
+void MFDestructInstruction::set_variable(MFVariable *variable)
+{
+ if (variable_ != nullptr) {
+ variable_->users_.remove_first_occurrence_and_reorder(this);
+ }
+ if (variable != nullptr) {
+ variable->users_.append(this);
+ }
+ variable_ = variable;
+}
+
+void MFDestructInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+void MFDummyInstruction::set_next(MFInstruction *instruction)
+{
+ if (next_ != nullptr) {
+ next_->prev_.remove_first_occurrence_and_reorder(this);
+ }
+ if (instruction != nullptr) {
+ instruction->prev_.append(this);
+ }
+ next_ = instruction;
+}
+
+MFVariable &MFProcedure::new_variable(MFDataType data_type, std::string name)
+{
+ MFVariable &variable = *allocator_.construct<MFVariable>().release();
+ variable.name_ = std::move(name);
+ variable.data_type_ = data_type;
+ variable.id_ = variables_.size();
+ variables_.append(&variable);
+ return variable;
+}
+
+MFCallInstruction &MFProcedure::new_call_instruction(const MultiFunction &fn)
+{
+ MFCallInstruction &instruction = *allocator_.construct<MFCallInstruction>().release();
+ instruction.type_ = MFInstructionType::Call;
+ instruction.fn_ = &fn;
+ instruction.params_ = allocator_.allocate_array<MFVariable *>(fn.param_amount());
+ instruction.params_.fill(nullptr);
+ call_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFBranchInstruction &MFProcedure::new_branch_instruction()
+{
+ MFBranchInstruction &instruction = *allocator_.construct<MFBranchInstruction>().release();
+ instruction.type_ = MFInstructionType::Branch;
+ branch_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFDestructInstruction &MFProcedure::new_destruct_instruction()
+{
+ MFDestructInstruction &instruction = *allocator_.construct<MFDestructInstruction>().release();
+ instruction.type_ = MFInstructionType::Destruct;
+ destruct_instructions_.append(&instruction);
+ return instruction;
+}
+
+MFDummyInstruction &MFProcedure::new_dummy_instruction()
+{
+ MFDummyInstruction &instruction = *allocator_.construct<MFDummyInstruction>().release();
+ instruction.type_ = MFInstructionType::Dummy;
+ dummy_instructions_.append(&instruction);
+ return instruction;
+}
+
+void MFProcedure::add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable)
+{
+ params_.append({interface_type, &variable});
+}
+
+void MFProcedure::set_entry(MFInstruction &entry)
+{
+ entry_ = &entry;
+}
+
+void MFProcedure::assert_valid() const
+{
+ /**
+ * - Non parameter variables are destructed.
+ * - At every instruction, every variable is either initialized or uninitialized.
+ * - Input and mutable parameters of call instructions are initialized.
+ * - Condition of branch instruction is initialized.
+ * - Output parameters of call instructions are not initialized.
+ * - Input parameters are never destructed.
+ * - Mutable and output parameteres are initialized on every exit.
+ * - No aliasing issues in call instructions (can happen when variable is used more than once).
+ */
+}
+
+MFProcedure::~MFProcedure()
+{
+ for (MFCallInstruction *instruction : call_instructions_) {
+ instruction->~MFCallInstruction();
+ }
+ for (MFBranchInstruction *instruction : branch_instructions_) {
+ instruction->~MFBranchInstruction();
+ }
+ for (MFDestructInstruction *instruction : destruct_instructions_) {
+ instruction->~MFDestructInstruction();
+ }
+ for (MFDummyInstruction *instruction : dummy_instructions_) {
+ instruction->~MFDummyInstruction();
+ }
+ for (MFVariable *variable : variables_) {
+ variable->~MFVariable();
+ }
+}
+
+static std::string optional_variable_to_string(const MFVariable *variable)
+{
+ if (variable == nullptr) {
+ return "<null>";
+ }
+ std::stringstream ss;
+ ss << variable->name() << "$" << variable->id();
+ return ss.str();
+}
+
+std::string MFProcedure::to_dot() const
+{
+ dot::DirectedGraph digraph;
+ Map<MFInstruction *, dot::Node *> dot_nodes;
+
+ for (MFCallInstruction *instruction : call_instructions_) {
+ std::stringstream ss;
+ const MultiFunction &fn = instruction->fn();
+ ss << fn.name();
+ ss << "(";
+ for (const int param_index : fn.param_indices()) {
+ MFParamType param_type = fn.param_type(param_index);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ ss << "in: ";
+ break;
+ }
+ case MFParamType::Output: {
+ ss << "out: ";
+ break;
+ }
+ case MFParamType::Mutable: {
+ ss << "mut: ";
+ break;
+ }
+ }
+ MFVariable *variable = instruction->params()[param_index];
+ ss << optional_variable_to_string(variable);
+ if (param_index < fn.param_amount() - 1) {
+ ss << ", ";
+ }
+ }
+ ss << ")";
+ dot::Node &dot_node = digraph.new_node(ss.str());
+ dot_node.set_shape(dot::Attr_shape::Rectangle);
+ dot_nodes.add_new(instruction, &dot_node);
+ }
+ for (MFBranchInstruction *instruction : branch_instructions_) {
+ MFVariable *variable = instruction->condition();
+ std::stringstream ss;
+ ss << "Branch: " << optional_variable_to_string(variable);
+ dot::Node &dot_node = digraph.new_node(ss.str());
+ dot_node.set_shape(dot::Attr_shape::Rectangle);
+ dot_nodes.add_new(instruction, &dot_node);
+ }
+ for (MFDestructInstruction *instruction : destruct_instructions_) {
+ MFVariable *variable = instruction->variable();
+ std::stringstream ss;
+ ss << "Destruct: " << optional_variable_to_string(variable);
+ dot::Node &dot_node = digraph.new_node(ss.str());
+ dot_node.set_shape(dot::Attr_shape::Rectangle);
+ dot_nodes.add_new(instruction, &dot_node);
+ }
+ for (MFDummyInstruction *instruction : dummy_instructions_) {
+ dot::Node &dot_node = digraph.new_node("Dummy");
+ dot_node.set_shape(dot::Attr_shape::Rectangle);
+ dot_nodes.add_new(instruction, &dot_node);
+ }
+
+ auto create_end_node = [&]() -> dot::Node & {
+ dot::Node &node = digraph.new_node("");
+ node.set_shape(dot::Attr_shape::Circle);
+ return node;
+ };
+
+ auto add_edge_to_instruction_or_end = [&](dot::Node &dot_from, MFInstruction *to) {
+ if (to == nullptr) {
+ dot::Node &dot_end_node = create_end_node();
+ digraph.new_edge(dot_from, dot_end_node);
+ }
+ else {
+ dot::Node &dot_to = *dot_nodes.lookup(to);
+ digraph.new_edge(dot_from, dot_to);
+ }
+ };
+
+ for (MFCallInstruction *instruction : call_instructions_) {
+ dot::Node &dot_node = *dot_nodes.lookup(instruction);
+ add_edge_to_instruction_or_end(dot_node, instruction->next());
+ }
+
+ for (MFBranchInstruction *instruction : branch_instructions_) {
+ dot::Node &dot_node = *dot_nodes.lookup(instruction);
+ add_edge_to_instruction_or_end(dot_node, instruction->branch_true());
+ add_edge_to_instruction_or_end(dot_node, instruction->branch_false());
+ }
+
+ for (MFDestructInstruction *instruction : destruct_instructions_) {
+ dot::Node &dot_node = *dot_nodes.lookup(instruction);
+ add_edge_to_instruction_or_end(dot_node, instruction->next());
+ }
+
+ for (MFDummyInstruction *instruction : dummy_instructions_) {
+ dot::Node &dot_node = *dot_nodes.lookup(instruction);
+ add_edge_to_instruction_or_end(dot_node, instruction->next());
+ }
+
+ dot::Node &dot_entry = digraph.new_node("Entry");
+ add_edge_to_instruction_or_end(dot_entry, entry_);
+
+ return digraph.to_dot_string();
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure_builder.cc b/source/blender/functions/intern/multi_function_procedure_builder.cc
new file mode 100644
index 00000000000..1e686bbd94d
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure_builder.cc
@@ -0,0 +1,161 @@
+/*
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure_builder.hh"
+
+namespace blender::fn {
+
+void MFInstructionCursor::insert(MFProcedure &procedure, MFInstruction *new_instruction)
+{
+ if (instruction_ == nullptr) {
+ if (is_entry_) {
+ procedure.set_entry(*new_instruction);
+ }
+ else {
+ /* The cursors points at nothing, nothing to do. */
+ }
+ }
+ else {
+ switch (instruction_->type()) {
+ case MFInstructionType::Call: {
+ static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ case MFInstructionType::Branch: {
+ MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(
+ instruction_);
+ if (branch_output_) {
+ branch_instruction.set_branch_true(new_instruction);
+ }
+ else {
+ branch_instruction.set_branch_false(new_instruction);
+ }
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
+ break;
+ }
+ }
+ }
+}
+
+void MFProcedureBuilder::add_destruct(MFVariable &variable)
+{
+ MFDestructInstruction &instruction = procedure_->new_destruct_instruction();
+ instruction.set_variable(&variable);
+ this->link_to_cursors(&instruction);
+ cursors_ = {MFInstructionCursor{instruction}};
+}
+
+void MFProcedureBuilder::add_destruct(Span<MFVariable *> variables)
+{
+ for (MFVariable *variable : variables) {
+ this->add_destruct(*variable);
+ }
+}
+
+MFCallInstruction &MFProcedureBuilder::add_call_with_no_variables(const MultiFunction &fn)
+{
+ MFCallInstruction &instruction = procedure_->new_call_instruction(fn);
+ this->link_to_cursors(&instruction);
+ cursors_ = {MFInstructionCursor{instruction}};
+ return instruction;
+}
+
+MFCallInstruction &MFProcedureBuilder::add_call_with_all_variables(
+ const MultiFunction &fn, Span<MFVariable *> param_variables)
+{
+ MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
+ instruction.set_params(param_variables);
+ return instruction;
+}
+
+Vector<MFVariable *> MFProcedureBuilder::add_call(const MultiFunction &fn,
+ Span<MFVariable *> input_and_mutable_variables)
+{
+ Vector<MFVariable *> output_variables;
+ MFCallInstruction &instruction = this->add_call_with_no_variables(fn);
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input:
+ case MFParamType::Mutable: {
+ MFVariable *variable = input_and_mutable_variables.first();
+ instruction.set_param_variable(param_index, variable);
+ input_and_mutable_variables = input_and_mutable_variables.drop_front(1);
+ break;
+ }
+ case MFParamType::Output: {
+ MFVariable &variable = procedure_->new_variable(param_type.data_type());
+ instruction.set_param_variable(param_index, &variable);
+ output_variables.append(&variable);
+ break;
+ }
+ }
+ }
+ /* All passed in variables should have been dropped in the loop above. */
+ BLI_assert(input_and_mutable_variables.is_empty());
+ return output_variables;
+}
+
+MFProcedureBuilder::Branch MFProcedureBuilder::add_branch(MFVariable &condition)
+{
+ MFBranchInstruction &instruction = procedure_->new_branch_instruction();
+ instruction.set_condition(&condition);
+ this->link_to_cursors(&instruction);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+
+ Branch branch{*procedure_, *procedure_};
+ branch.branch_true.set_cursor(MFInstructionCursor{instruction, true});
+ branch.branch_false.set_cursor(MFInstructionCursor{instruction, false});
+ return branch;
+}
+
+MFProcedureBuilder::Loop MFProcedureBuilder::add_loop()
+{
+ MFDummyInstruction &loop_begin = procedure_->new_dummy_instruction();
+ MFDummyInstruction &loop_end = procedure_->new_dummy_instruction();
+ this->link_to_cursors(&loop_begin);
+ cursors_ = {MFInstructionCursor{loop_begin}};
+
+ Loop loop;
+ loop.begin = &loop_begin;
+ loop.end = &loop_end;
+
+ return loop;
+}
+
+void MFProcedureBuilder::add_loop_continue(Loop &loop)
+{
+ this->link_to_cursors(loop.begin);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+}
+
+void MFProcedureBuilder::add_loop_break(Loop &loop)
+{
+ this->link_to_cursors(loop.end);
+ /* Clear cursors because this builder ends here. */
+ cursors_.clear();
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/intern/multi_function_procedure_executor.cc b/source/blender/functions/intern/multi_function_procedure_executor.cc
new file mode 100644
index 00000000000..45d7e03a138
--- /dev/null
+++ b/source/blender/functions/intern/multi_function_procedure_executor.cc
@@ -0,0 +1,1140 @@
+/*
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "FN_multi_function_procedure_executor.hh"
+
+#include "BLI_stack.hh"
+
+namespace blender::fn {
+
+MFProcedureExecutor::MFProcedureExecutor(std::string name, const MFProcedure &procedure)
+ : procedure_(procedure)
+{
+ MFSignatureBuilder signature(std::move(name));
+
+ for (const std::pair<MFParamType::InterfaceType, const MFVariable *> &param :
+ procedure.params()) {
+ signature.add(param.second->name(), MFParamType(param.first, param.second->data_type()));
+ }
+
+ signature_ = signature.build();
+ this->set_signature(&signature_);
+}
+
+using IndicesSplitVectors = std::array<Vector<int64_t>, 2>;
+
+namespace {
+enum class ValueType {
+ GVArray = 0,
+ Span = 1,
+ GVVectorArray = 2,
+ GVectorArray = 3,
+ OneSingle = 4,
+ OneVector = 5,
+};
+constexpr int tot_variable_value_types = 6;
+} // namespace
+
+struct VariableValue {
+ ValueType type;
+
+ VariableValue(ValueType type) : type(type)
+ {
+ }
+};
+
+/* This variable is the unmodified virtual array from the caller. */
+struct VariableValue_GVArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVArray;
+ const GVArray &data;
+
+ VariableValue_GVArray(const GVArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has a different value for every index. Some values may be uninitialized. The span
+ * may be owned by the caller. */
+struct VariableValue_Span : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::Span;
+ void *data;
+ bool owned;
+
+ VariableValue_Span(void *data, bool owned) : VariableValue(static_type), data(data), owned(owned)
+ {
+ }
+};
+
+/* This variable is the unmodified virtual vector array from the caller. */
+struct VariableValue_GVVectorArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVVectorArray;
+ const GVVectorArray &data;
+
+ VariableValue_GVVectorArray(const GVVectorArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has a different vector for every index. */
+struct VariableValue_GVectorArray : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::GVectorArray;
+ GVectorArray &data;
+ bool owned;
+
+ VariableValue_GVectorArray(GVectorArray &data, bool owned)
+ : VariableValue(static_type), data(data), owned(owned)
+ {
+ }
+};
+
+/* This variable has the same value for every index. */
+struct VariableValue_OneSingle : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::OneSingle;
+ void *data;
+ bool is_initialized = false;
+
+ VariableValue_OneSingle(void *data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+/* This variable has the same vector for every index. */
+struct VariableValue_OneVector : public VariableValue {
+ static inline constexpr ValueType static_type = ValueType::OneVector;
+ GVectorArray &data;
+
+ VariableValue_OneVector(GVectorArray &data) : VariableValue(static_type), data(data)
+ {
+ }
+};
+
+static_assert(std::is_trivially_destructible_v<VariableValue_GVArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_Span>);
+static_assert(std::is_trivially_destructible_v<VariableValue_GVVectorArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_GVectorArray>);
+static_assert(std::is_trivially_destructible_v<VariableValue_OneSingle>);
+static_assert(std::is_trivially_destructible_v<VariableValue_OneVector>);
+
+class VariableState;
+
+class ValueAllocator : NonCopyable, NonMovable {
+ private:
+ std::array<Stack<VariableValue *>, tot_variable_value_types> values_free_lists_;
+
+ public:
+ ValueAllocator() = default;
+
+ ~ValueAllocator()
+ {
+ for (Stack<VariableValue *> &stack : values_free_lists_) {
+ while (!stack.is_empty()) {
+ MEM_freeN(stack.pop());
+ }
+ }
+ }
+
+ template<typename... Args> VariableState *obtain_variable_state(Args &&...args);
+
+ void release_variable_state(VariableState *state);
+
+ VariableValue_GVArray *obtain_GVArray(const GVArray &varray)
+ {
+ return this->obtain<VariableValue_GVArray>(varray);
+ }
+
+ VariableValue_GVVectorArray *obtain_GVVectorArray(const GVVectorArray &varray)
+ {
+ return this->obtain<VariableValue_GVVectorArray>(varray);
+ }
+
+ VariableValue_Span *obtain_Span_not_owned(void *buffer)
+ {
+ return this->obtain<VariableValue_Span>(buffer, false);
+ }
+
+ VariableValue_Span *obtain_Span(const CPPType &type, int size)
+ {
+ void *buffer = MEM_mallocN_aligned(type.size() * size, type.alignment(), __func__);
+ return this->obtain<VariableValue_Span>(buffer, true);
+ }
+
+ VariableValue_GVectorArray *obtain_GVectorArray_not_owned(GVectorArray &data)
+ {
+ return this->obtain<VariableValue_GVectorArray>(data, false);
+ }
+
+ VariableValue_GVectorArray *obtain_GVectorArray(const CPPType &type, int size)
+ {
+ GVectorArray *vector_array = new GVectorArray(type, size);
+ return this->obtain<VariableValue_GVectorArray>(*vector_array, true);
+ }
+
+ VariableValue_OneSingle *obtain_OneSingle(const CPPType &type)
+ {
+ void *buffer = MEM_mallocN_aligned(type.size(), type.alignment(), __func__);
+ return this->obtain<VariableValue_OneSingle>(buffer);
+ }
+
+ VariableValue_OneVector *obtain_OneVector(const CPPType &type)
+ {
+ GVectorArray *vector_array = new GVectorArray(type, 1);
+ return this->obtain<VariableValue_OneVector>(*vector_array);
+ }
+
+ void release_value(VariableValue *value, const MFDataType &data_type)
+ {
+ switch (value->type) {
+ case ValueType::GVArray: {
+ break;
+ }
+ case ValueType::Span: {
+ auto *value_typed = static_cast<VariableValue_Span *>(value);
+ if (value_typed->owned) {
+ /* Assumes all values in the buffer are uninitialized already. */
+ MEM_freeN(value_typed->data);
+ }
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ break;
+ }
+ case ValueType::GVectorArray: {
+ auto *value_typed = static_cast<VariableValue_GVectorArray *>(value);
+ if (value_typed->owned) {
+ delete &value_typed->data;
+ }
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = static_cast<VariableValue_OneSingle *>(value);
+ if (value_typed->is_initialized) {
+ const CPPType &type = data_type.single_type();
+ type.destruct(value_typed->data);
+ }
+ MEM_freeN(value_typed->data);
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = static_cast<VariableValue_OneVector *>(value);
+ delete &value_typed->data;
+ break;
+ }
+ }
+
+ Stack<VariableValue *> &stack = values_free_lists_[(int)value->type];
+ stack.push(value);
+ }
+
+ private:
+ template<typename T, typename... Args> T *obtain(Args &&...args)
+ {
+ static_assert(std::is_base_of_v<VariableValue, T>);
+ Stack<VariableValue *> &stack = values_free_lists_[(int)T::static_type];
+ if (stack.is_empty()) {
+ void *buffer = MEM_mallocN(sizeof(T), __func__);
+ return new (buffer) T(std::forward<Args>(args)...);
+ }
+ return new (stack.pop()) T(std::forward<Args>(args)...);
+ }
+};
+
+class VariableState : NonCopyable, NonMovable {
+ private:
+ VariableValue *value_;
+ int tot_initialized_;
+ /* This a non-owning pointer to either span buffer or #GVectorArray or null. */
+ void *caller_provided_storage_ = nullptr;
+
+ public:
+ VariableState(VariableValue &value, int tot_initialized, void *caller_provided_storage = nullptr)
+ : value_(&value),
+ tot_initialized_(tot_initialized),
+ caller_provided_storage_(caller_provided_storage)
+ {
+ }
+
+ void destruct_self(ValueAllocator &value_allocator, const MFDataType &data_type)
+ {
+ value_allocator.release_value(value_, data_type);
+ value_allocator.release_variable_state(this);
+ }
+
+ /* True if this contains only one value for all indices, i.e. the value for all indices is
+ * the same. */
+ bool is_one() const
+ {
+ switch (value_->type) {
+ case ValueType::GVArray:
+ return this->value_as<VariableValue_GVArray>()->data.is_single();
+ case ValueType::Span:
+ return tot_initialized_ == 0;
+ case ValueType::GVVectorArray:
+ return this->value_as<VariableValue_GVVectorArray>()->data.is_single_vector();
+ case ValueType::GVectorArray:
+ return tot_initialized_ == 0;
+ case ValueType::OneSingle:
+ return true;
+ case ValueType::OneVector:
+ return true;
+ }
+ BLI_assert_unreachable();
+ return false;
+ }
+
+ bool is_fully_initialized(const IndexMask full_mask)
+ {
+ return tot_initialized_ == full_mask.size();
+ }
+
+ bool is_fully_uninitialized(const IndexMask full_mask)
+ {
+ UNUSED_VARS(full_mask);
+ return tot_initialized_ == 0;
+ }
+
+ void add_as_input(MFParamsBuilder &params, IndexMask mask, const MFDataType &data_type) const
+ {
+ /* Sanity check to make sure that enough values are initialized. */
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data);
+ break;
+ }
+ case ValueType::Span: {
+ const void *data = this->value_as<VariableValue_Span>()->data;
+ const GSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_readonly_single_input(span);
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::OneSingle: {
+ const auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ const GPointer gpointer{data_type.single_type(), value_typed->data};
+ params.add_readonly_single_input(gpointer);
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data[0]);
+ break;
+ }
+ }
+ }
+
+ /* TODO: What happens when the same parameter is passed to the same function more than ones.
+ * Maybe restrict this so that one variable can only be used either as multiple inputs, one
+ * mutable or one output. */
+ void ensure_is_mutable(IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ if (ELEM(value_->type, ValueType::Span, ValueType::GVectorArray)) {
+ return;
+ }
+
+ const int array_size = full_mask.min_array_size();
+
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ VariableValue_Span *new_value = nullptr;
+ if (caller_provided_storage_ == nullptr) {
+ new_value = value_allocator.obtain_Span(type, array_size);
+ }
+ else {
+ /* Reuse the storage provided caller when possible. */
+ new_value = value_allocator.obtain_Span_not_owned(caller_provided_storage_);
+ }
+ if (value_->type == ValueType::GVArray) {
+ /* Fill new buffer with data from virtual array. */
+ this->value_as<VariableValue_GVArray>()->data.materialize_to_uninitialized(
+ full_mask, new_value->data);
+ }
+ else if (value_->type == ValueType::OneSingle) {
+ auto *old_value_typed_ = this->value_as<VariableValue_OneSingle>();
+ if (old_value_typed_->is_initialized) {
+ /* Fill the buffer with a single value. */
+ type.fill_construct_indices(old_value_typed_->data, new_value->data, full_mask);
+ }
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ VariableValue_GVectorArray *new_value = nullptr;
+ if (caller_provided_storage_ == nullptr) {
+ new_value = value_allocator.obtain_GVectorArray(type, array_size);
+ }
+ else {
+ new_value = value_allocator.obtain_GVectorArray_not_owned(
+ *(GVectorArray *)caller_provided_storage_);
+ }
+ if (value_->type == ValueType::GVVectorArray) {
+ /* Fill new vector array with data from virtual vector array. */
+ new_value->data.extend(full_mask, this->value_as<VariableValue_GVVectorArray>()->data);
+ }
+ else if (value_->type == ValueType::OneVector) {
+ /* Fill all indices with the same value. */
+ const GSpan vector = this->value_as<VariableValue_OneVector>()->data[0];
+ new_value->data.extend(full_mask, GVVectorArray_For_SingleGSpan{vector, array_size});
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ }
+ }
+
+ void add_as_mutable(MFParamsBuilder &params,
+ IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ /* Sanity check to make sure that enough values are initialized. */
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::Span: {
+ void *data = this->value_as<VariableValue_Span>()->data;
+ const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_single_mutable(span);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_vector_mutable(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::GVVectorArray:
+ case ValueType::OneSingle:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void add_as_output(MFParamsBuilder &params,
+ IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ /* Sanity check to make sure that enough values are not initialized. */
+ BLI_assert(mask.size() <= full_mask.size() - tot_initialized_);
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::Span: {
+ void *data = this->value_as<VariableValue_Span>()->data;
+ const GMutableSpan span{data_type.single_type(), data, mask.min_array_size()};
+ params.add_uninitialized_single_output(span);
+ break;
+ }
+ case ValueType::GVectorArray: {
+ params.add_vector_output(this->value_as<VariableValue_GVectorArray>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::GVVectorArray:
+ case ValueType::OneSingle:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+
+ tot_initialized_ += mask.size();
+ }
+
+ void add_as_input__one(MFParamsBuilder &params, const MFDataType &data_type) const
+ {
+ BLI_assert(this->is_one());
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ params.add_readonly_single_input(this->value_as<VariableValue_GVArray>()->data);
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_GVVectorArray>()->data);
+ break;
+ }
+ case ValueType::OneSingle: {
+ const auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ GPointer ptr{data_type.single_type(), value_typed->data};
+ params.add_readonly_single_input(ptr);
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_readonly_vector_input(this->value_as<VariableValue_OneVector>()->data);
+ break;
+ }
+ case ValueType::Span:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void ensure_is_mutable__one(const MFDataType &data_type, ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ if (ELEM(value_->type, ValueType::OneSingle, ValueType::OneVector)) {
+ return;
+ }
+
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ VariableValue_OneSingle *new_value = value_allocator.obtain_OneSingle(type);
+ if (value_->type == ValueType::GVArray) {
+ this->value_as<VariableValue_GVArray>()->data.get_internal_single_to_uninitialized(
+ new_value->data);
+ new_value->is_initialized = true;
+ }
+ else if (value_->type == ValueType::Span) {
+ BLI_assert(tot_initialized_ == 0);
+ /* Nothing to do, the single value is uninitialized already. */
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ VariableValue_OneVector *new_value = value_allocator.obtain_OneVector(type);
+ if (value_->type == ValueType::GVVectorArray) {
+ const GVVectorArray &old_vector_array =
+ this->value_as<VariableValue_GVVectorArray>()->data;
+ new_value->data.extend(IndexRange(1), old_vector_array);
+ }
+ else if (value_->type == ValueType::GVectorArray) {
+ BLI_assert(tot_initialized_ == 0);
+ /* Nothing to do. */
+ }
+ else {
+ BLI_assert_unreachable();
+ }
+ value_allocator.release_value(value_, data_type);
+ value_ = new_value;
+ break;
+ }
+ }
+ }
+
+ void add_as_mutable__one(MFParamsBuilder &params,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ this->ensure_is_mutable__one(data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ params.add_single_mutable(GMutableSpan{data_type.single_type(), value_typed->data, 1});
+ break;
+ }
+ case ValueType::OneVector: {
+ params.add_vector_mutable(this->value_as<VariableValue_OneVector>()->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::Span:
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ void add_as_output__one(MFParamsBuilder &params,
+ IndexMask mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ BLI_assert(this->is_one());
+ this->ensure_is_mutable__one(data_type, value_allocator);
+
+ switch (value_->type) {
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(!value_typed->is_initialized);
+ params.add_uninitialized_single_output(
+ GMutableSpan{data_type.single_type(), value_typed->data, 1});
+ /* It becomes initialized when the multi-function is called. */
+ value_typed->is_initialized = true;
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = this->value_as<VariableValue_OneVector>();
+ BLI_assert(value_typed->data[0].is_empty());
+ params.add_vector_output(value_typed->data);
+ break;
+ }
+ case ValueType::GVArray:
+ case ValueType::Span:
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+
+ tot_initialized_ += mask.size();
+ }
+
+ void destruct(IndexMask mask,
+ IndexMask full_mask,
+ const MFDataType &data_type,
+ ValueAllocator &value_allocator)
+ {
+ /* Sanity check to make sure that enough indices can be destructed. */
+ BLI_assert(tot_initialized_ >= mask.size());
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ if (mask.size() == full_mask.size()) {
+ /* All elements are destructed. The elements are owned by the caller, so we don't
+ * actually destruct them. */
+ value_allocator.release_value(value_, data_type);
+ value_ = value_allocator.obtain_OneSingle(data_type.single_type());
+ }
+ else {
+ /* Not all elements are destructed. Since we can't work on the original array, we have to
+ * create a copy first. */
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+ BLI_assert(value_->type == ValueType::Span);
+ const CPPType &type = data_type.single_type();
+ type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask);
+ }
+ break;
+ }
+ case ValueType::Span: {
+ const CPPType &type = data_type.single_type();
+ type.destruct_indices(this->value_as<VariableValue_Span>()->data, mask);
+ break;
+ }
+ case ValueType::GVVectorArray: {
+ if (mask.size() == full_mask.size()) {
+ /* All elements are cleared. The elements are owned by the caller, so don't actually
+ * destruct them. */
+ value_allocator.release_value(value_, data_type);
+ value_ = value_allocator.obtain_OneVector(data_type.vector_base_type());
+ }
+ else {
+ /* Not all elements are cleared. Since we can't work on the original vector array, we
+ * have to create a copy first. A possible future optimization is to create the partial
+ * copy directly. */
+ this->ensure_is_mutable(full_mask, data_type, value_allocator);
+ BLI_assert(value_->type == ValueType::GVectorArray);
+ this->value_as<VariableValue_GVectorArray>()->data.clear(mask);
+ }
+ break;
+ }
+ case ValueType::GVectorArray: {
+ this->value_as<VariableValue_GVectorArray>()->data.clear(mask);
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ if (mask.size() == tot_initialized_) {
+ const CPPType &type = data_type.single_type();
+ type.destruct(value_typed->data);
+ value_typed->is_initialized = false;
+ }
+ break;
+ }
+ case ValueType::OneVector: {
+ auto *value_typed = this->value_as<VariableValue_OneVector>();
+ if (mask.size() == tot_initialized_) {
+ value_typed->data.clear({0});
+ }
+ break;
+ }
+ }
+
+ tot_initialized_ -= mask.size();
+ }
+
+ void indices_split(IndexMask mask, IndicesSplitVectors &r_indices)
+ {
+ BLI_assert(mask.size() <= tot_initialized_);
+
+ switch (value_->type) {
+ case ValueType::GVArray: {
+ const GVArray_Typed<bool> varray{this->value_as<VariableValue_GVArray>()->data};
+ for (const int i : mask) {
+ r_indices[varray[i]].append(i);
+ }
+ break;
+ }
+ case ValueType::Span: {
+ const Span<bool> span((bool *)this->value_as<VariableValue_Span>()->data,
+ mask.min_array_size());
+ for (const int i : mask) {
+ r_indices[span[i]].append(i);
+ }
+ break;
+ }
+ case ValueType::OneSingle: {
+ auto *value_typed = this->value_as<VariableValue_OneSingle>();
+ BLI_assert(value_typed->is_initialized);
+ const bool condition = *(bool *)value_typed->data;
+ r_indices[condition].extend(mask);
+ break;
+ }
+ case ValueType::GVVectorArray:
+ case ValueType::GVectorArray:
+ case ValueType::OneVector: {
+ BLI_assert_unreachable();
+ break;
+ }
+ }
+ }
+
+ template<typename T> T *value_as()
+ {
+ BLI_assert(value_->type == T::static_type);
+ return static_cast<T *>(value_);
+ }
+
+ template<typename T> const T *value_as() const
+ {
+ BLI_assert(value_->type == T::static_type);
+ return static_cast<T *>(value_);
+ }
+};
+
+template<typename... Args> VariableState *ValueAllocator::obtain_variable_state(Args &&...args)
+{
+ return new VariableState(std::forward<Args>(args)...);
+}
+
+void ValueAllocator::release_variable_state(VariableState *state)
+{
+ delete state;
+}
+
+class VariableStates {
+ private:
+ ValueAllocator value_allocator_;
+ Map<const MFVariable *, VariableState *> variable_states_;
+ IndexMask full_mask_;
+
+ public:
+ VariableStates(IndexMask full_mask) : full_mask_(full_mask)
+ {
+ }
+
+ ~VariableStates()
+ {
+ for (auto &&item : variable_states_.items()) {
+ const MFVariable *variable = item.key;
+ VariableState *state = item.value;
+ state->destruct_self(value_allocator_, variable->data_type());
+ }
+ }
+
+ ValueAllocator &value_allocator()
+ {
+ return value_allocator_;
+ }
+
+ const IndexMask &full_mask() const
+ {
+ return full_mask_;
+ }
+
+ void add_initial_variable_states(const MFProcedureExecutor &fn,
+ const MFProcedure &procedure,
+ MFParams &params)
+ {
+ for (const int param_index : fn.param_indices()) {
+ MFParamType param_type = fn.param_type(param_index);
+ const MFVariable *variable = procedure.params()[param_index].second;
+
+ auto add_state = [&](VariableValue *value,
+ bool input_is_initialized,
+ void *caller_provided_storage = nullptr) {
+ const int tot_initialized = input_is_initialized ? full_mask_.size() : 0;
+ variable_states_.add_new(variable,
+ value_allocator_.obtain_variable_state(
+ *value, tot_initialized, caller_provided_storage));
+ };
+
+ switch (param_type.category()) {
+ case MFParamType::SingleInput: {
+ const GVArray &data = params.readonly_single_input(param_index);
+ add_state(value_allocator_.obtain_GVArray(data), true);
+ break;
+ }
+ case MFParamType::VectorInput: {
+ const GVVectorArray &data = params.readonly_vector_input(param_index);
+ add_state(value_allocator_.obtain_GVVectorArray(data), true);
+ break;
+ }
+ case MFParamType::SingleOutput: {
+ GMutableSpan data = params.uninitialized_single_output(param_index);
+ add_state(value_allocator_.obtain_Span_not_owned(data.data()), false, data.data());
+ break;
+ }
+ case MFParamType::VectorOutput: {
+ GVectorArray &data = params.vector_output(param_index);
+ add_state(value_allocator_.obtain_GVectorArray_not_owned(data), false, &data);
+ break;
+ }
+ case MFParamType::SingleMutable: {
+ GMutableSpan data = params.single_mutable(param_index);
+ add_state(value_allocator_.obtain_Span_not_owned(data.data()), true, data.data());
+ break;
+ }
+ case MFParamType::VectorMutable: {
+ GVectorArray &data = params.vector_mutable(param_index);
+ add_state(value_allocator_.obtain_GVectorArray_not_owned(data), true, &data);
+ break;
+ }
+ }
+ }
+ }
+
+ void add_as_param(VariableState &variable_state,
+ MFParamsBuilder &params,
+ const MFParamType &param_type,
+ const IndexMask &mask)
+ {
+ const MFDataType data_type = param_type.data_type();
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ variable_state.add_as_input(params, mask, data_type);
+ break;
+ }
+ case MFParamType::Mutable: {
+ variable_state.add_as_mutable(params, mask, full_mask_, data_type, value_allocator_);
+ break;
+ }
+ case MFParamType::Output: {
+ variable_state.add_as_output(params, mask, full_mask_, data_type, value_allocator_);
+ break;
+ }
+ }
+ }
+
+ void add_as_param__one(VariableState &variable_state,
+ MFParamsBuilder &params,
+ const MFParamType &param_type,
+ const IndexMask &mask)
+ {
+ const MFDataType data_type = param_type.data_type();
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ variable_state.add_as_input__one(params, data_type);
+ break;
+ }
+ case MFParamType::Mutable: {
+ variable_state.add_as_mutable__one(params, data_type, value_allocator_);
+ break;
+ }
+ case MFParamType::Output: {
+ variable_state.add_as_output__one(params, mask, data_type, value_allocator_);
+ break;
+ }
+ }
+ }
+
+ void destruct(const MFVariable &variable, const IndexMask &mask)
+ {
+ VariableState &variable_state = this->get_variable_state(variable);
+ variable_state.destruct(mask, full_mask_, variable.data_type(), value_allocator_);
+ }
+
+ VariableState &get_variable_state(const MFVariable &variable)
+ {
+ return *variable_states_.lookup_or_add_cb(
+ &variable, [&]() { return this->create_new_state_for_variable(variable); });
+ }
+
+ VariableState *create_new_state_for_variable(const MFVariable &variable)
+ {
+ MFDataType data_type = variable.data_type();
+ switch (data_type.category()) {
+ case MFDataType::Single: {
+ const CPPType &type = data_type.single_type();
+ return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneSingle(type), 0);
+ }
+ case MFDataType::Vector: {
+ const CPPType &type = data_type.vector_base_type();
+ return value_allocator_.obtain_variable_state(*value_allocator_.obtain_OneVector(type), 0);
+ }
+ }
+ BLI_assert_unreachable();
+ return nullptr;
+ }
+};
+
+static bool evaluate_as_one(const MultiFunction &fn,
+ Span<VariableState *> param_variable_states,
+ const IndexMask &mask,
+ const IndexMask &full_mask)
+{
+ if (fn.depends_on_context()) {
+ return false;
+ }
+ if (mask.size() < full_mask.size()) {
+ return false;
+ }
+ for (VariableState *state : param_variable_states) {
+ if (!state->is_one()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void execute_call_instruction(const MFCallInstruction &instruction,
+ IndexMask mask,
+ VariableStates &variable_states,
+ const MFContext &context)
+{
+ const MultiFunction &fn = instruction.fn();
+
+ Vector<VariableState *> param_variable_states;
+ param_variable_states.resize(fn.param_amount());
+
+ for (const int param_index : fn.param_indices()) {
+ const MFVariable *variable = instruction.params()[param_index];
+ VariableState &variable_state = variable_states.get_variable_state(*variable);
+ param_variable_states[param_index] = &variable_state;
+ }
+
+ if (evaluate_as_one(fn, param_variable_states, mask, variable_states.full_mask())) {
+ MFParamsBuilder params(fn, 1);
+
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ VariableState &variable_state = *param_variable_states[param_index];
+ variable_states.add_as_param__one(variable_state, params, param_type, mask);
+ }
+
+ fn.call(IndexRange(1), params, context);
+ }
+ else {
+ MFParamsBuilder params(fn, mask.min_array_size());
+
+ for (const int param_index : fn.param_indices()) {
+ const MFParamType param_type = fn.param_type(param_index);
+ VariableState &variable_state = *param_variable_states[param_index];
+ variable_states.add_as_param(variable_state, params, param_type, mask);
+ }
+
+ fn.call(mask, params, context);
+ }
+}
+
+struct NextInstructionInfo {
+ const MFInstruction *instruction = nullptr;
+ Vector<int64_t> owned_indices;
+
+ IndexMask mask() const
+ {
+ return this->owned_indices.as_span();
+ }
+
+ operator bool() const
+ {
+ return this->instruction != nullptr;
+ }
+};
+
+class InstructionScheduler {
+ private:
+ Map<const MFInstruction *, Vector<Vector<int64_t>>> indices_by_instruction_;
+
+ public:
+ InstructionScheduler() = default;
+
+ void add_referenced_indices(const MFInstruction *instruction, IndexMask mask)
+ {
+ if (instruction == nullptr) {
+ return;
+ }
+ if (mask.is_empty()) {
+ return;
+ }
+ indices_by_instruction_.lookup_or_add_default(instruction).append(mask.indices());
+ }
+
+ void add_owned_indices(const MFInstruction *instruction, Vector<int64_t> indices)
+ {
+ if (instruction == nullptr) {
+ return;
+ }
+ if (indices.is_empty()) {
+ return;
+ }
+ BLI_assert(IndexMask::indices_are_valid_index_mask(indices));
+ indices_by_instruction_.lookup_or_add_default(instruction).append(std::move(indices));
+ }
+
+ void add_previous_instruction_indices(const MFInstruction *instruction,
+ NextInstructionInfo &instr_info)
+ {
+ this->add_owned_indices(instruction, std::move(instr_info.owned_indices));
+ }
+
+ NextInstructionInfo pop_next()
+ {
+ if (indices_by_instruction_.is_empty()) {
+ return {};
+ }
+ /* TODO: Implement better mechanism to determine next instruction. */
+ const MFInstruction *instruction = *indices_by_instruction_.keys().begin();
+
+ Vector<int64_t> indices = this->pop_indices_array(instruction);
+ if (indices.is_empty()) {
+ return {};
+ }
+
+ NextInstructionInfo next_instruction_info;
+ next_instruction_info.instruction = instruction;
+ next_instruction_info.owned_indices = std::move(indices);
+ return next_instruction_info;
+ }
+
+ private:
+ Vector<int64_t> pop_indices_array(const MFInstruction *instruction)
+ {
+ Vector<Vector<int64_t>> *indices = indices_by_instruction_.lookup_ptr(instruction);
+ if (indices == nullptr) {
+ return {};
+ }
+ Vector<int64_t> r_indices;
+ while (!indices->is_empty() && r_indices.is_empty()) {
+ r_indices = (*indices).pop_last();
+ }
+ if (indices->is_empty()) {
+ indices_by_instruction_.remove_contained(instruction);
+ }
+ return r_indices;
+ }
+};
+
+void MFProcedureExecutor::call(IndexMask full_mask, MFParams params, MFContext context) const
+{
+ if (procedure_.entry() == nullptr) {
+ return;
+ }
+
+ LinearAllocator<> allocator;
+
+ VariableStates variable_states{full_mask};
+ variable_states.add_initial_variable_states(*this, procedure_, params);
+
+ InstructionScheduler scheduler;
+ scheduler.add_referenced_indices(procedure_.entry(), full_mask);
+
+ while (NextInstructionInfo instr_info = scheduler.pop_next()) {
+ const MFInstruction &instruction = *instr_info.instruction;
+ switch (instruction.type()) {
+ case MFInstructionType::Call: {
+ const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
+ instruction);
+ execute_call_instruction(call_instruction, instr_info.mask(), variable_states, context);
+ scheduler.add_previous_instruction_indices(call_instruction.next(), instr_info);
+ break;
+ }
+ case MFInstructionType::Branch: {
+ const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
+ instruction);
+ const MFVariable *condition_var = branch_instruction.condition();
+ VariableState &variable_state = variable_states.get_variable_state(*condition_var);
+
+ IndicesSplitVectors new_indices;
+ variable_state.indices_split(instr_info.mask(), new_indices);
+ scheduler.add_owned_indices(branch_instruction.branch_false(), new_indices[false]);
+ scheduler.add_owned_indices(branch_instruction.branch_true(), new_indices[true]);
+ break;
+ }
+ case MFInstructionType::Destruct: {
+ const MFDestructInstruction &destruct_instruction =
+ static_cast<const MFDestructInstruction &>(instruction);
+ const MFVariable *variable = destruct_instruction.variable();
+ variable_states.destruct(*variable, instr_info.mask());
+ scheduler.add_previous_instruction_indices(destruct_instruction.next(), instr_info);
+ break;
+ }
+ case MFInstructionType::Dummy: {
+ const MFDummyInstruction &dummy_instruction = static_cast<const MFDummyInstruction &>(
+ instruction);
+ scheduler.add_previous_instruction_indices(dummy_instruction.next(), instr_info);
+ break;
+ }
+ }
+ }
+
+ for (const int param_index : this->param_indices()) {
+ const MFParamType param_type = this->param_type(param_index);
+ const MFVariable *variable = procedure_.params()[param_index].second;
+ VariableState &variable_state = variable_states.get_variable_state(*variable);
+ switch (param_type.interface_type()) {
+ case MFParamType::Input: {
+ /* Input variables must be destructed in the end. */
+ BLI_assert(variable_state.is_fully_uninitialized(full_mask));
+ break;
+ }
+ case MFParamType::Mutable:
+ case MFParamType::Output: {
+ /* Mutable and output variables must be initialized in the end. */
+ BLI_assert(variable_state.is_fully_initialized(full_mask));
+ /* Make sure that the data is in the memory provided by the caller. */
+ variable_state.ensure_is_mutable(
+ full_mask, param_type.data_type(), variable_states.value_allocator());
+ break;
+ }
+ }
+ }
+}
+
+} // namespace blender::fn
diff --git a/source/blender/functions/tests/FN_multi_function_procedure_test.cc b/source/blender/functions/tests/FN_multi_function_procedure_test.cc
new file mode 100644
index 00000000000..22e69cca4f2
--- /dev/null
+++ b/source/blender/functions/tests/FN_multi_function_procedure_test.cc
@@ -0,0 +1,278 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "FN_multi_function_builder.hh"
+#include "FN_multi_function_procedure_builder.hh"
+#include "FN_multi_function_procedure_executor.hh"
+#include "FN_multi_function_test_common.hh"
+
+namespace blender::fn::tests {
+
+TEST(multi_function_procedure, SimpleTest)
+{
+ /**
+ * procedure(int var1, int var2, int *var4) {
+ * int var3 = var1 + var2;
+ * var4 = var2 + var3;
+ * }
+ */
+
+ CustomMF_SI_SI_SO<int, int, int> add_fn{"add", [](int a, int b) { return a + b; }};
+ CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_input_parameter<int>();
+ MFVariable *var2 = &builder.add_single_input_parameter<int>();
+ auto [var3] = builder.add_call<1>(add_fn, {var1, var2});
+ auto [var4] = builder.add_call<1>(add_fn, {var2, var3});
+ builder.add_call(add_10_fn, {var4});
+ builder.add_destruct({var1, var2, var3});
+ builder.add_output_parameter(*var4);
+
+ MFProcedureExecutor executor{"My Procedure", procedure};
+
+ MFParamsBuilder params{executor, 3};
+ MFContextBuilder context;
+
+ Array<int> input_array = {1, 2, 3};
+ params.add_readonly_single_input(input_array.as_span());
+ params.add_readonly_single_input_value(3);
+
+ Array<int> output_array(3);
+ params.add_uninitialized_single_output(output_array.as_mutable_span());
+
+ executor.call(IndexRange(3), params, context);
+
+ EXPECT_EQ(output_array[0], 17);
+ EXPECT_EQ(output_array[1], 18);
+ EXPECT_EQ(output_array[2], 19);
+}
+
+TEST(multi_function_procedure, BranchTest)
+{
+ /**
+ * procedure(int &var1, bool var2) {
+ * if (var2) {
+ * var1 += 100;
+ * }
+ * else {
+ * var1 += 10;
+ * }
+ * var1 += 10;
+ * }
+ */
+
+ CustomMF_SM<int> add_10_fn{"add_10", [](int &a) { a += 10; }};
+ CustomMF_SM<int> add_100_fn{"add_100", [](int &a) { a += 100; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_mutable_parameter<int>();
+ MFVariable *var2 = &builder.add_single_input_parameter<bool>();
+
+ MFProcedureBuilder::Branch branch = builder.add_branch(*var2);
+ branch.branch_false.add_call(add_10_fn, {var1});
+ branch.branch_true.add_call(add_100_fn, {var1});
+ builder.set_cursor_after_branch(branch);
+ builder.add_call(add_10_fn, {var1});
+ builder.add_destruct({var2});
+
+ MFProcedureExecutor procedure_fn{"Condition Test", procedure};
+ MFParamsBuilder params(procedure_fn, 5);
+
+ Array<int> values_a = {1, 5, 3, 6, 2};
+ Array<bool> values_cond = {true, false, true, true, false};
+
+ params.add_single_mutable(values_a.as_mutable_span());
+ params.add_readonly_single_input(values_cond.as_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({1, 2, 3, 4}, params, context);
+
+ EXPECT_EQ(values_a[0], 1);
+ EXPECT_EQ(values_a[1], 25);
+ EXPECT_EQ(values_a[2], 113);
+ EXPECT_EQ(values_a[3], 116);
+ EXPECT_EQ(values_a[4], 22);
+}
+
+TEST(multi_function_procedure, EvaluateOne)
+{
+ /**
+ * procedure(int var1, int var2) {
+ * var2 = var1 + 10;
+ * }
+ */
+
+ int tot_evaluations = 0;
+ CustomMF_SI_SO<int, int> add_10_fn{"add_10", [&](int a) {
+ tot_evaluations++;
+ return a + 10;
+ }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var1 = &builder.add_single_input_parameter<int>();
+ auto [var2] = builder.add_call<1>(add_10_fn, {var1});
+ builder.add_destruct(*var1);
+ builder.add_output_parameter(*var2);
+
+ MFProcedureExecutor procedure_fn{"Evaluate One", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> values_out = {1, 2, 3, 4, 5};
+ params.add_readonly_single_input_value(1);
+ params.add_uninitialized_single_output(values_out.as_mutable_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(values_out[0], 11);
+ EXPECT_EQ(values_out[1], 11);
+ EXPECT_EQ(values_out[2], 3);
+ EXPECT_EQ(values_out[3], 11);
+ EXPECT_EQ(values_out[4], 11);
+ /* We expect only one evaluation, because the input is constant. */
+ EXPECT_EQ(tot_evaluations, 1);
+}
+
+TEST(multi_function_procedure, SimpleLoop)
+{
+ /**
+ * procedure(int count, int *out) {
+ * out = 1;
+ * int index = 0'
+ * loop {
+ * if (index >= count) {
+ * break;
+ * }
+ * out *= 2;
+ * index += 1;
+ * }
+ * out += 1000;
+ * }
+ */
+
+ CustomMF_Constant<int> const_1_fn{1};
+ CustomMF_Constant<int> const_0_fn{0};
+ CustomMF_SI_SI_SO<int, int, bool> greater_or_equal_fn{"greater or equal",
+ [](int a, int b) { return a >= b; }};
+ CustomMF_SM<int> double_fn{"double", [](int &a) { a *= 2; }};
+ CustomMF_SM<int> add_1000_fn{"add 1000", [](int &a) { a += 1000; }};
+ CustomMF_SM<int> add_1_fn{"add 1", [](int &a) { a += 1; }};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var_count = &builder.add_single_input_parameter<int>("count");
+ auto [var_out] = builder.add_call<1>(const_1_fn);
+ var_out->set_name("out");
+ auto [var_index] = builder.add_call<1>(const_0_fn);
+ var_index->set_name("index");
+
+ MFProcedureBuilder::Loop loop = builder.add_loop();
+ auto [var_condition] = builder.add_call<1>(greater_or_equal_fn, {var_index, var_count});
+ var_condition->set_name("condition");
+ MFProcedureBuilder::Branch branch = builder.add_branch(*var_condition);
+ branch.branch_true.add_destruct(*var_condition);
+ branch.branch_true.add_loop_break(loop);
+ branch.branch_false.add_destruct(*var_condition);
+ builder.set_cursor_after_branch(branch);
+ builder.add_call(double_fn, {var_out});
+ builder.add_call(add_1_fn, {var_index});
+ builder.add_loop_continue(loop);
+ builder.set_cursor_after_loop(loop);
+ builder.add_call(add_1000_fn, {var_out});
+ builder.add_destruct({var_count, var_index});
+ builder.add_output_parameter(*var_out);
+
+ MFProcedureExecutor procedure_fn{"Simple Loop", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> counts = {4, 3, 7, 6, 4};
+ Array<int> results(5, -1);
+
+ params.add_readonly_single_input(counts.as_span());
+ params.add_uninitialized_single_output(results.as_mutable_span());
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(results[0], 1016);
+ EXPECT_EQ(results[1], 1008);
+ EXPECT_EQ(results[2], -1);
+ EXPECT_EQ(results[3], 1064);
+ EXPECT_EQ(results[4], 1016);
+}
+
+TEST(multi_function_procedure, Vectors)
+{
+ /**
+ * procedure(vector<int> v1, vector<int> &v2, vector<int> *v3) {
+ * v1.extend(v2);
+ * int constant = 5;
+ * v2.append(constant);
+ * v2.extend(v1);
+ * int len = sum(v2);
+ * v3 = range(len);
+ * }
+ */
+
+ CreateRangeFunction create_range_fn;
+ ConcatVectorsFunction extend_fn;
+ GenericAppendFunction append_fn{CPPType::get<int>()};
+ SumVectorFunction sum_elements_fn;
+ CustomMF_Constant<int> constant_5_fn{5};
+
+ MFProcedure procedure;
+ MFProcedureBuilder builder{procedure};
+
+ MFVariable *var_v1 = &builder.add_input_parameter(MFDataType::ForVector<int>());
+ MFVariable *var_v2 = &builder.add_parameter(MFParamType::ForMutableVector(CPPType::get<int>()));
+ builder.add_call(extend_fn, {var_v1, var_v2});
+ auto [var_constant] = builder.add_call<1>(constant_5_fn);
+ builder.add_call(append_fn, {var_v2, var_constant});
+ builder.add_destruct(*var_constant);
+ builder.add_call(extend_fn, {var_v2, var_v1});
+ auto [var_len] = builder.add_call<1>(sum_elements_fn, {var_v2});
+ auto [var_v3] = builder.add_call<1>(create_range_fn, {var_len});
+ builder.add_destruct({var_v1, var_len});
+ builder.add_output_parameter(*var_v3);
+
+ MFProcedureExecutor procedure_fn{"Vectors", procedure};
+ MFParamsBuilder params{procedure_fn, 5};
+
+ Array<int> v1 = {5, 2, 3};
+ GVectorArray v2{CPPType::get<int>(), 5};
+ GVectorArray v3{CPPType::get<int>(), 5};
+
+ int value_10 = 10;
+ v2.append(0, &value_10);
+ v2.append(4, &value_10);
+
+ params.add_readonly_vector_input(v1.as_span());
+ params.add_vector_mutable(v2);
+ params.add_vector_output(v3);
+
+ MFContextBuilder context;
+ procedure_fn.call({0, 1, 3, 4}, params, context);
+
+ EXPECT_EQ(v2[0].size(), 6);
+ EXPECT_EQ(v2[1].size(), 4);
+ EXPECT_EQ(v2[2].size(), 0);
+ EXPECT_EQ(v2[3].size(), 4);
+ EXPECT_EQ(v2[4].size(), 6);
+
+ EXPECT_EQ(v3[0].size(), 35);
+ EXPECT_EQ(v3[1].size(), 15);
+ EXPECT_EQ(v3[2].size(), 0);
+ EXPECT_EQ(v3[3].size(), 15);
+ EXPECT_EQ(v3[4].size(), 35);
+}
+
+} // namespace blender::fn::tests