Goals: * Better high level control over where devirtualization occurs. There is always a trade-off between performance and compile-time/binary-size. * Simplify using array devirtualization. * Better performance for cases where devirtualization wasn't used before. Many geometry nodes accept fields as inputs. Internally, that means that the execution functions have to accept so called "virtual arrays" as inputs. Those can be e.g. actual arrays, just single values, or lazily computed arrays. Due to these different possible virtual arrays implementations, access to individual elements is slower than it would be if everything was just a normal array (access does through a virtual function call). For more complex execution functions, this overhead does not matter, but for small functions (like a simple addition) it very much does. The virtual function call also prevents the compiler from doing some optimizations (e.g. loop unrolling and inserting simd instructions). The solution is to "devirtualize" the virtual arrays for small functions where the overhead is measurable. Essentially, the function is generated many times with different array types as input. Then there is a run-time dispatch that calls the best implementation. We have been doing devirtualization in e.g. math nodes for a long time already. This patch just generalizes the concept and makes it easier to control. It also makes it easier to investigate the different trade-offs when it comes to devirtualization. Nodes that we've optimized using devirtualization before didn't get a speedup. However, a couple of nodes are using devirtualization now, that didn't before. Those got a 2-4x speedup in common cases. * Map Range * Random Value * Switch * Combine XYZ Differential Revision: https://developer.blender.org/D14628
865 lines
26 KiB
C++
865 lines
26 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "FN_multi_function_procedure.hh"
|
|
|
|
#include "BLI_dot_export.hh"
|
|
#include "BLI_stack.hh"
|
|
|
|
namespace blender::fn {
|
|
|
|
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;
|
|
}
|
|
|
|
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, true});
|
|
}
|
|
if (instruction != nullptr) {
|
|
instruction->prev_.append({*this, true});
|
|
}
|
|
branch_true_ = instruction;
|
|
}
|
|
|
|
void MFBranchInstruction::set_branch_false(MFInstruction *instruction)
|
|
{
|
|
if (branch_false_ != nullptr) {
|
|
branch_false_->prev_.remove_first_occurrence_and_reorder({*this, false});
|
|
}
|
|
if (instruction != nullptr) {
|
|
instruction->prev_.append({*this, false});
|
|
}
|
|
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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
if (entry_ != nullptr) {
|
|
entry_->prev_.remove_first_occurrence_and_reorder(MFInstructionCursor::ForEntry());
|
|
}
|
|
entry_ = &entry;
|
|
entry_->prev_.append(MFInstructionCursor::ForEntry());
|
|
}
|
|
|
|
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_) {
|
|
const MultiFunction &fn = instruction->fn();
|
|
for (const int param_index : fn.param_indices()) {
|
|
const MFParamType param_type = fn.param_type(param_index);
|
|
if (param_type.category() == MFParamCategory::SingleOutput) {
|
|
/* Single outputs are optional. */
|
|
continue;
|
|
}
|
|
const MFVariable *variable = instruction->params_[param_index];
|
|
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];
|
|
if (variable == nullptr) {
|
|
continue;
|
|
}
|
|
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);
|
|
/* If the parameter was an unneeded output, it could be null. */
|
|
if (!instruction->params_[param_index]) {
|
|
continue;
|
|
}
|
|
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;
|
|
for (const MFInstructionCursor &cursor : target_instruction.prev_) {
|
|
if (cursor.instruction() != nullptr) {
|
|
instructions_to_check.push(cursor.instruction());
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
for (const MFInstructionCursor &cursor : instruction.prev_) {
|
|
if (cursor.instruction() != nullptr) {
|
|
instructions_to_check.push(cursor.instruction());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (ELEM(instruction.prev()[0].type(),
|
|
MFInstructionCursor::Type::Branch,
|
|
MFInstructionCursor::Type::Entry)) {
|
|
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)) {
|
|
current = current->prev()[0].instruction();
|
|
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();
|
|
this->instruction_name_format(fn.debug_name() + ": ", ss);
|
|
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
|