2022-02-11 09:07:11 +11:00
|
|
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
#include "FN_multi_function_procedure.hh"
|
|
|
|
|
|
|
|
#include "BLI_dot_export.hh"
|
|
|
|
#include "BLI_stack.hh"
|
|
|
|
|
|
|
|
namespace blender::fn {
|
|
|
|
|
2021-09-11 11:43:59 +02:00
|
|
|
void MFInstructionCursor::set_next(MFProcedure &procedure, MFInstruction *new_instruction) const
|
|
|
|
{
|
|
|
|
switch (type_) {
|
|
|
|
case Type::None: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Entry: {
|
|
|
|
procedure.set_entry(*new_instruction);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Call: {
|
|
|
|
static_cast<MFCallInstruction *>(instruction_)->set_next(new_instruction);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::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 Type::Destruct: {
|
|
|
|
static_cast<MFDestructInstruction *>(instruction_)->set_next(new_instruction);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Type::Dummy: {
|
|
|
|
static_cast<MFDummyInstruction *>(instruction_)->set_next(new_instruction);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MFInstruction *MFInstructionCursor::next(MFProcedure &procedure) const
|
|
|
|
{
|
|
|
|
switch (type_) {
|
|
|
|
case Type::None:
|
|
|
|
return nullptr;
|
|
|
|
case Type::Entry:
|
|
|
|
return procedure.entry();
|
|
|
|
case Type::Call:
|
|
|
|
return static_cast<MFCallInstruction *>(instruction_)->next();
|
|
|
|
case Type::Branch: {
|
|
|
|
MFBranchInstruction &branch_instruction = *static_cast<MFBranchInstruction *>(instruction_);
|
|
|
|
if (branch_output_) {
|
|
|
|
return branch_instruction.branch_true();
|
|
|
|
}
|
|
|
|
return branch_instruction.branch_false();
|
|
|
|
}
|
|
|
|
case Type::Destruct:
|
|
|
|
return static_cast<MFDestructInstruction *>(instruction_)->next();
|
|
|
|
case Type::Dummy:
|
|
|
|
return static_cast<MFDummyInstruction *>(instruction_)->next();
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2021-09-09 12:54:20 +02:00
|
|
|
void MFVariable::set_name(std::string name)
|
|
|
|
{
|
|
|
|
name_ = std::move(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MFCallInstruction::set_next(MFInstruction *instruction)
|
|
|
|
{
|
|
|
|
if (next_ != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
if (instruction != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
instruction->prev_.append(*this);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
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) {
|
2021-09-11 11:43:59 +02:00
|
|
|
branch_true_->prev_.remove_first_occurrence_and_reorder({*this, true});
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
if (instruction != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
instruction->prev_.append({*this, true});
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
branch_true_ = instruction;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
|
|
|
|
{
|
|
|
|
if (branch_false_ != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
branch_false_->prev_.remove_first_occurrence_and_reorder({*this, false});
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
if (instruction != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
instruction->prev_.append({*this, false});
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
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) {
|
2021-09-11 11:43:59 +02:00
|
|
|
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
if (instruction != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
instruction->prev_.append(*this);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
next_ = instruction;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MFDummyInstruction::set_next(MFInstruction *instruction)
|
|
|
|
{
|
|
|
|
if (next_ != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
if (instruction != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
instruction->prev_.append(*this);
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
MFReturnInstruction &MFProcedure::new_return_instruction()
|
|
|
|
{
|
|
|
|
MFReturnInstruction &instruction = *allocator_.construct<MFReturnInstruction>().release();
|
|
|
|
instruction.type_ = MFInstructionType::Return;
|
|
|
|
return_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)
|
|
|
|
{
|
2021-09-11 12:41:46 +02:00
|
|
|
if (entry_ != nullptr) {
|
2021-09-11 11:43:59 +02:00
|
|
|
entry_->prev_.remove_first_occurrence_and_reorder(MFInstructionCursor::ForEntry());
|
|
|
|
}
|
2021-09-09 12:54:20 +02:00
|
|
|
entry_ = &entry;
|
2021-09-11 11:43:59 +02:00
|
|
|
entry_->prev_.append(MFInstructionCursor::ForEntry());
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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 (MFReturnInstruction *instruction : return_instructions_) {
|
|
|
|
instruction->~MFReturnInstruction();
|
|
|
|
}
|
|
|
|
for (MFVariable *variable : variables_) {
|
|
|
|
variable->~MFVariable();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MFProcedure::validate() const
|
|
|
|
{
|
|
|
|
if (entry_ == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this->validate_all_instruction_pointers_set()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this->validate_all_params_provided()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this->validate_same_variables_in_one_call()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this->validate_parameters()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!this->validate_initialization()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MFProcedure::validate_all_instruction_pointers_set() const
|
|
|
|
{
|
|
|
|
for (const MFCallInstruction *instruction : call_instructions_) {
|
|
|
|
if (instruction->next_ == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
|
|
|
if (instruction->next_ == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
|
|
|
if (instruction->branch_true_ == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (instruction->branch_false_ == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const MFDummyInstruction *instruction : dummy_instructions_) {
|
|
|
|
if (instruction->next_ == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MFProcedure::validate_all_params_provided() const
|
|
|
|
{
|
|
|
|
for (const MFCallInstruction *instruction : call_instructions_) {
|
2021-09-14 14:52:44 +02:00
|
|
|
const MultiFunction &fn = instruction->fn();
|
|
|
|
for (const int param_index : fn.param_indices()) {
|
|
|
|
const MFParamType param_type = fn.param_type(param_index);
|
2022-04-26 17:12:34 +02:00
|
|
|
if (param_type.category() == MFParamCategory::SingleOutput) {
|
2021-09-14 14:52:44 +02:00
|
|
|
/* Single outputs are optional. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const MFVariable *variable = instruction->params_[param_index];
|
2021-09-09 12:54:20 +02:00
|
|
|
if (variable == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
|
|
|
if (instruction->condition_ == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
|
|
|
if (instruction->variable_ == nullptr) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MFProcedure::validate_same_variables_in_one_call() const
|
|
|
|
{
|
|
|
|
for (const MFCallInstruction *instruction : call_instructions_) {
|
|
|
|
const MultiFunction &fn = *instruction->fn_;
|
|
|
|
for (const int param_index : fn.param_indices()) {
|
|
|
|
const MFParamType param_type = fn.param_type(param_index);
|
|
|
|
const MFVariable *variable = instruction->params_[param_index];
|
2021-09-14 14:52:44 +02:00
|
|
|
if (variable == nullptr) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-09-09 12:54:20 +02:00
|
|
|
for (const int other_param_index : fn.param_indices()) {
|
|
|
|
if (other_param_index == param_index) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const MFVariable *other_variable = instruction->params_[other_param_index];
|
|
|
|
if (other_variable != variable) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (ELEM(param_type.interface_type(), MFParamType::Mutable, MFParamType::Output)) {
|
|
|
|
/* When a variable is used as mutable or output parameter, it can only be used once. */
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
const MFParamType other_param_type = fn.param_type(other_param_index);
|
|
|
|
/* A variable is allowed to be used as input more than once. */
|
|
|
|
if (other_param_type.interface_type() != MFParamType::Input) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MFProcedure::validate_parameters() const
|
|
|
|
{
|
|
|
|
Set<const MFVariable *> variables;
|
|
|
|
for (const MFParameter ¶m : params_) {
|
|
|
|
/* One variable cannot be used as multiple parameters. */
|
|
|
|
if (!variables.add(param.variable)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool MFProcedure::validate_initialization() const
|
|
|
|
{
|
|
|
|
/* TODO: Issue warning when it maybe wrongly initialized. */
|
|
|
|
for (const MFDestructInstruction *instruction : destruct_instructions_) {
|
|
|
|
const MFVariable &variable = *instruction->variable_;
|
|
|
|
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
|
|
|
variable);
|
|
|
|
if (!state.can_be_initialized) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const MFBranchInstruction *instruction : branch_instructions_) {
|
|
|
|
const MFVariable &variable = *instruction->condition_;
|
|
|
|
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
|
|
|
variable);
|
|
|
|
if (!state.can_be_initialized) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const MFCallInstruction *instruction : call_instructions_) {
|
|
|
|
const MultiFunction &fn = *instruction->fn_;
|
|
|
|
for (const int param_index : fn.param_indices()) {
|
|
|
|
const MFParamType param_type = fn.param_type(param_index);
|
2021-09-20 20:22:52 -05:00
|
|
|
/* If the parameter was an unneeded output, it could be null. */
|
|
|
|
if (!instruction->params_[param_index]) {
|
|
|
|
continue;
|
|
|
|
}
|
2021-09-09 12:54:20 +02:00
|
|
|
const MFVariable &variable = *instruction->params_[param_index];
|
|
|
|
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
|
|
|
variable);
|
|
|
|
switch (param_type.interface_type()) {
|
|
|
|
case MFParamType::Input:
|
|
|
|
case MFParamType::Mutable: {
|
|
|
|
if (!state.can_be_initialized) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFParamType::Output: {
|
|
|
|
if (!state.can_be_uninitialized) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Set<const MFVariable *> variables_that_should_be_initialized_on_return;
|
|
|
|
for (const MFParameter ¶m : params_) {
|
|
|
|
if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
|
|
|
|
variables_that_should_be_initialized_on_return.add_new(param.variable);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const MFReturnInstruction *instruction : return_instructions_) {
|
|
|
|
for (const MFVariable *variable : variables_) {
|
|
|
|
const InitState init_state = this->find_initialization_state_before_instruction(*instruction,
|
|
|
|
*variable);
|
|
|
|
if (variables_that_should_be_initialized_on_return.contains(variable)) {
|
|
|
|
if (!init_state.can_be_initialized) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!init_state.can_be_uninitialized) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
MFProcedure::InitState MFProcedure::find_initialization_state_before_instruction(
|
|
|
|
const MFInstruction &target_instruction, const MFVariable &target_variable) const
|
|
|
|
{
|
|
|
|
InitState state;
|
|
|
|
|
|
|
|
auto check_entry_instruction = [&]() {
|
|
|
|
bool caller_initialized_variable = false;
|
|
|
|
for (const MFParameter ¶m : params_) {
|
|
|
|
if (param.variable == &target_variable) {
|
|
|
|
if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
|
|
|
|
caller_initialized_variable = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (caller_initialized_variable) {
|
|
|
|
state.can_be_initialized = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
state.can_be_uninitialized = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if (&target_instruction == entry_) {
|
|
|
|
check_entry_instruction();
|
|
|
|
}
|
|
|
|
|
|
|
|
Set<const MFInstruction *> checked_instructions;
|
|
|
|
Stack<const MFInstruction *> instructions_to_check;
|
2021-09-11 11:43:59 +02:00
|
|
|
for (const MFInstructionCursor &cursor : target_instruction.prev_) {
|
|
|
|
if (cursor.instruction() != nullptr) {
|
|
|
|
instructions_to_check.push(cursor.instruction());
|
|
|
|
}
|
|
|
|
}
|
2021-09-09 12:54:20 +02:00
|
|
|
|
|
|
|
while (!instructions_to_check.is_empty()) {
|
|
|
|
const MFInstruction &instruction = *instructions_to_check.pop();
|
|
|
|
if (!checked_instructions.add(&instruction)) {
|
|
|
|
/* Skip if the instruction has been checked already. */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
bool state_modified = false;
|
|
|
|
switch (instruction.type_) {
|
|
|
|
case MFInstructionType::Call: {
|
|
|
|
const MFCallInstruction &call_instruction = static_cast<const MFCallInstruction &>(
|
|
|
|
instruction);
|
|
|
|
const MultiFunction &fn = *call_instruction.fn_;
|
|
|
|
for (const int param_index : fn.param_indices()) {
|
|
|
|
if (call_instruction.params_[param_index] == &target_variable) {
|
|
|
|
const MFParamType param_type = fn.param_type(param_index);
|
|
|
|
if (param_type.interface_type() == MFParamType::Output) {
|
|
|
|
state.can_be_initialized = true;
|
|
|
|
state_modified = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Destruct: {
|
|
|
|
const MFDestructInstruction &destruct_instruction =
|
|
|
|
static_cast<const MFDestructInstruction &>(instruction);
|
|
|
|
if (destruct_instruction.variable_ == &target_variable) {
|
|
|
|
state.can_be_uninitialized = true;
|
|
|
|
state_modified = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Branch:
|
|
|
|
case MFInstructionType::Dummy:
|
|
|
|
case MFInstructionType::Return: {
|
|
|
|
/* These instruction types don't change the initialization state of variables. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!state_modified) {
|
|
|
|
if (&instruction == entry_) {
|
|
|
|
check_entry_instruction();
|
|
|
|
}
|
2021-09-11 11:43:59 +02:00
|
|
|
for (const MFInstructionCursor &cursor : instruction.prev_) {
|
|
|
|
if (cursor.instruction() != nullptr) {
|
|
|
|
instructions_to_check.push(cursor.instruction());
|
|
|
|
}
|
|
|
|
}
|
2021-09-09 12:54:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
|
|
|
class MFProcedureDotExport {
|
|
|
|
private:
|
|
|
|
const MFProcedure &procedure_;
|
|
|
|
dot::DirectedGraph digraph_;
|
|
|
|
Map<const MFInstruction *, dot::Node *> dot_nodes_by_begin_;
|
|
|
|
Map<const MFInstruction *, dot::Node *> dot_nodes_by_end_;
|
|
|
|
|
|
|
|
public:
|
|
|
|
MFProcedureDotExport(const MFProcedure &procedure) : procedure_(procedure)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string generate()
|
|
|
|
{
|
|
|
|
this->create_nodes();
|
|
|
|
this->create_edges();
|
|
|
|
return digraph_.to_dot_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
void create_nodes()
|
|
|
|
{
|
|
|
|
Vector<const MFInstruction *> all_instructions;
|
|
|
|
auto add_instructions = [&](auto instructions) {
|
|
|
|
all_instructions.extend(instructions.begin(), instructions.end());
|
|
|
|
};
|
|
|
|
add_instructions(procedure_.call_instructions_);
|
|
|
|
add_instructions(procedure_.branch_instructions_);
|
|
|
|
add_instructions(procedure_.destruct_instructions_);
|
|
|
|
add_instructions(procedure_.dummy_instructions_);
|
|
|
|
add_instructions(procedure_.return_instructions_);
|
|
|
|
|
|
|
|
Set<const MFInstruction *> handled_instructions;
|
|
|
|
|
|
|
|
for (const MFInstruction *representative : all_instructions) {
|
|
|
|
if (handled_instructions.contains(representative)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
Vector<const MFInstruction *> block_instructions = this->get_instructions_in_block(
|
|
|
|
*representative);
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "<";
|
|
|
|
|
|
|
|
for (const MFInstruction *current : block_instructions) {
|
|
|
|
handled_instructions.add_new(current);
|
|
|
|
switch (current->type()) {
|
|
|
|
case MFInstructionType::Call: {
|
|
|
|
this->instruction_to_string(*static_cast<const MFCallInstruction *>(current), ss);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Destruct: {
|
|
|
|
this->instruction_to_string(*static_cast<const MFDestructInstruction *>(current), ss);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Dummy: {
|
|
|
|
this->instruction_to_string(*static_cast<const MFDummyInstruction *>(current), ss);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Return: {
|
|
|
|
this->instruction_to_string(*static_cast<const MFReturnInstruction *>(current), ss);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Branch: {
|
|
|
|
this->instruction_to_string(*static_cast<const MFBranchInstruction *>(current), ss);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ss << R"(<br align="left" />)";
|
|
|
|
}
|
|
|
|
ss << ">";
|
|
|
|
|
|
|
|
dot::Node &dot_node = digraph_.new_node(ss.str());
|
|
|
|
dot_node.set_shape(dot::Attr_shape::Rectangle);
|
|
|
|
dot_nodes_by_begin_.add_new(block_instructions.first(), &dot_node);
|
|
|
|
dot_nodes_by_end_.add_new(block_instructions.last(), &dot_node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void create_edges()
|
|
|
|
{
|
|
|
|
auto create_edge = [&](dot::Node &from_node,
|
|
|
|
const MFInstruction *to_instruction) -> dot::DirectedEdge & {
|
|
|
|
if (to_instruction == nullptr) {
|
|
|
|
dot::Node &to_node = digraph_.new_node("missing");
|
|
|
|
to_node.set_shape(dot::Attr_shape::Diamond);
|
|
|
|
return digraph_.new_edge(from_node, to_node);
|
|
|
|
}
|
|
|
|
dot::Node &to_node = *dot_nodes_by_begin_.lookup(to_instruction);
|
|
|
|
return digraph_.new_edge(from_node, to_node);
|
|
|
|
};
|
|
|
|
|
|
|
|
for (auto item : dot_nodes_by_end_.items()) {
|
|
|
|
const MFInstruction &from_instruction = *item.key;
|
|
|
|
dot::Node &from_node = *item.value;
|
|
|
|
switch (from_instruction.type()) {
|
|
|
|
case MFInstructionType::Call: {
|
|
|
|
const MFInstruction *to_instruction =
|
|
|
|
static_cast<const MFCallInstruction &>(from_instruction).next();
|
|
|
|
create_edge(from_node, to_instruction);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Destruct: {
|
|
|
|
const MFInstruction *to_instruction =
|
|
|
|
static_cast<const MFDestructInstruction &>(from_instruction).next();
|
|
|
|
create_edge(from_node, to_instruction);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Dummy: {
|
|
|
|
const MFInstruction *to_instruction =
|
|
|
|
static_cast<const MFDummyInstruction &>(from_instruction).next();
|
|
|
|
create_edge(from_node, to_instruction);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Return: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Branch: {
|
|
|
|
const MFBranchInstruction &branch_instruction = static_cast<const MFBranchInstruction &>(
|
|
|
|
from_instruction);
|
|
|
|
const MFInstruction *to_true_instruction = branch_instruction.branch_true();
|
|
|
|
const MFInstruction *to_false_instruction = branch_instruction.branch_false();
|
|
|
|
create_edge(from_node, to_true_instruction).attributes.set("color", "#118811");
|
|
|
|
create_edge(from_node, to_false_instruction).attributes.set("color", "#881111");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dot::Node &entry_node = this->create_entry_node();
|
|
|
|
create_edge(entry_node, procedure_.entry());
|
|
|
|
}
|
|
|
|
|
|
|
|
bool has_to_be_block_begin(const MFInstruction &instruction)
|
|
|
|
{
|
|
|
|
if (instruction.prev().size() != 1) {
|
|
|
|
return true;
|
|
|
|
}
|
2021-09-14 14:52:44 +02:00
|
|
|
if (ELEM(instruction.prev()[0].type(),
|
|
|
|
MFInstructionCursor::Type::Branch,
|
|
|
|
MFInstructionCursor::Type::Entry)) {
|
2021-09-09 12:54:20 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const MFInstruction &get_first_instruction_in_block(const MFInstruction &representative)
|
|
|
|
{
|
|
|
|
const MFInstruction *current = &representative;
|
|
|
|
while (!this->has_to_be_block_begin(*current)) {
|
2021-09-11 11:43:59 +02:00
|
|
|
current = current->prev()[0].instruction();
|
2021-09-09 12:54:20 +02:00
|
|
|
if (current == &representative) {
|
|
|
|
/* There is a loop without entry or exit, just break it up here. */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return *current;
|
|
|
|
}
|
|
|
|
|
|
|
|
const MFInstruction *get_next_instruction_in_block(const MFInstruction &instruction,
|
|
|
|
const MFInstruction &block_begin)
|
|
|
|
{
|
|
|
|
const MFInstruction *next = nullptr;
|
|
|
|
switch (instruction.type()) {
|
|
|
|
case MFInstructionType::Call: {
|
|
|
|
next = static_cast<const MFCallInstruction &>(instruction).next();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Destruct: {
|
|
|
|
next = static_cast<const MFDestructInstruction &>(instruction).next();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Dummy: {
|
|
|
|
next = static_cast<const MFDummyInstruction &>(instruction).next();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFInstructionType::Return:
|
|
|
|
case MFInstructionType::Branch: {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (next == nullptr) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
if (next == &block_begin) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
if (this->has_to_be_block_begin(*next)) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
|
|
|
|
Vector<const MFInstruction *> get_instructions_in_block(const MFInstruction &representative)
|
|
|
|
{
|
|
|
|
Vector<const MFInstruction *> instructions;
|
|
|
|
const MFInstruction &begin = this->get_first_instruction_in_block(representative);
|
|
|
|
for (const MFInstruction *current = &begin; current != nullptr;
|
|
|
|
current = this->get_next_instruction_in_block(*current, begin)) {
|
|
|
|
instructions.append(current);
|
|
|
|
}
|
|
|
|
return instructions;
|
|
|
|
}
|
|
|
|
|
|
|
|
void variable_to_string(const MFVariable *variable, std::stringstream &ss)
|
|
|
|
{
|
|
|
|
if (variable == nullptr) {
|
|
|
|
ss << "null";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ss << "$" << variable->id();
|
|
|
|
if (!variable->name().is_empty()) {
|
|
|
|
ss << "(" << variable->name() << ")";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void instruction_name_format(StringRef name, std::stringstream &ss)
|
|
|
|
{
|
|
|
|
ss << name;
|
|
|
|
}
|
|
|
|
|
|
|
|
void instruction_to_string(const MFCallInstruction &instruction, std::stringstream &ss)
|
|
|
|
{
|
|
|
|
const MultiFunction &fn = instruction.fn();
|
2021-11-21 12:37:04 +01:00
|
|
|
this->instruction_name_format(fn.debug_name() + ": ", ss);
|
2021-09-09 12:54:20 +02:00
|
|
|
for (const int param_index : fn.param_indices()) {
|
|
|
|
const MFParamType param_type = fn.param_type(param_index);
|
|
|
|
const MFVariable *variable = instruction.params()[param_index];
|
|
|
|
ss << R"(<font color="grey30">)";
|
|
|
|
switch (param_type.interface_type()) {
|
|
|
|
case MFParamType::Input: {
|
|
|
|
ss << "in";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFParamType::Mutable: {
|
|
|
|
ss << "mut";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case MFParamType::Output: {
|
|
|
|
ss << "out";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ss << " </font> ";
|
|
|
|
variable_to_string(variable, ss);
|
|
|
|
if (param_index < fn.param_amount() - 1) {
|
|
|
|
ss << ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void instruction_to_string(const MFDestructInstruction &instruction, std::stringstream &ss)
|
|
|
|
{
|
|
|
|
instruction_name_format("Destruct ", ss);
|
|
|
|
variable_to_string(instruction.variable(), ss);
|
|
|
|
}
|
|
|
|
|
|
|
|
void instruction_to_string(const MFDummyInstruction &UNUSED(instruction), std::stringstream &ss)
|
|
|
|
{
|
|
|
|
instruction_name_format("Dummy ", ss);
|
|
|
|
}
|
|
|
|
|
|
|
|
void instruction_to_string(const MFReturnInstruction &UNUSED(instruction), std::stringstream &ss)
|
|
|
|
{
|
|
|
|
instruction_name_format("Return ", ss);
|
|
|
|
|
|
|
|
Vector<ConstMFParameter> outgoing_parameters;
|
|
|
|
for (const ConstMFParameter ¶m : procedure_.params()) {
|
|
|
|
if (ELEM(param.type, MFParamType::Mutable, MFParamType::Output)) {
|
|
|
|
outgoing_parameters.append(param);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const int param_index : outgoing_parameters.index_range()) {
|
|
|
|
const ConstMFParameter ¶m = outgoing_parameters[param_index];
|
|
|
|
variable_to_string(param.variable, ss);
|
|
|
|
if (param_index < outgoing_parameters.size() - 1) {
|
|
|
|
ss << ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void instruction_to_string(const MFBranchInstruction &instruction, std::stringstream &ss)
|
|
|
|
{
|
|
|
|
instruction_name_format("Branch ", ss);
|
|
|
|
variable_to_string(instruction.condition(), ss);
|
|
|
|
}
|
|
|
|
|
|
|
|
dot::Node &create_entry_node()
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Entry: ";
|
|
|
|
Vector<ConstMFParameter> incoming_parameters;
|
|
|
|
for (const ConstMFParameter ¶m : procedure_.params()) {
|
|
|
|
if (ELEM(param.type, MFParamType::Input, MFParamType::Mutable)) {
|
|
|
|
incoming_parameters.append(param);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const int param_index : incoming_parameters.index_range()) {
|
|
|
|
const ConstMFParameter ¶m = incoming_parameters[param_index];
|
|
|
|
variable_to_string(param.variable, ss);
|
|
|
|
if (param_index < incoming_parameters.size() - 1) {
|
|
|
|
ss << ", ";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dot::Node &node = digraph_.new_node(ss.str());
|
|
|
|
node.set_shape(dot::Attr_shape::Ellipse);
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::string MFProcedure::to_dot() const
|
|
|
|
{
|
|
|
|
MFProcedureDotExport dot_export{*this};
|
|
|
|
return dot_export.generate();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace blender::fn
|