Including <iostream> or similar headers is quite expensive, since it also pulls in things like <locale> and so on. In many BLI headers, iostreams are only used to implement some sort of "debug print", or an operator<< for ostream. Change some of the commonly used places to instead include <iosfwd>, which is the standard way of forward-declaring iostreams related classes, and move the actual debug-print / operator<< implementations into .cc files. This is not done for templated classes though (it would be possible to provide explicit operator<< instantiations somewhere in the source file, but that would lead to hard-to-figure-out linker error whenever someone would add a different template type). There, where possible, I changed from full <iostream> include to only the needed <ostream> part. For Span<T>, I just removed print_as_lines since it's not used by anything. It could be moved into a .cc file using a similar approach as above if needed. Doing full blender build changes include counts this way: - <iostream> 1986 -> 978 - <sstream> 2880 -> 925 It does not affect the total build time much though, mostly because towards the end of it there's just several CPU cores finishing compiling OpenVDB related source files. Pull Request: https://projects.blender.org/blender/blender/pulls/111046
869 lines
26 KiB
C++
869 lines
26 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include "FN_multi_function_procedure.hh"
|
|
|
|
#include "BLI_dot_export.hh"
|
|
#include "BLI_stack.hh"
|
|
|
|
#include <sstream>
|
|
|
|
namespace blender::fn::multi_function {
|
|
|
|
void InstructionCursor::set_next(Procedure &procedure, Instruction *new_instruction) const
|
|
{
|
|
switch (type_) {
|
|
case Type::None: {
|
|
break;
|
|
}
|
|
case Type::Entry: {
|
|
procedure.set_entry(*new_instruction);
|
|
break;
|
|
}
|
|
case Type::Call: {
|
|
static_cast<CallInstruction *>(instruction_)->set_next(new_instruction);
|
|
break;
|
|
}
|
|
case Type::Branch: {
|
|
BranchInstruction &branch_instruction = *static_cast<BranchInstruction *>(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<DestructInstruction *>(instruction_)->set_next(new_instruction);
|
|
break;
|
|
}
|
|
case Type::Dummy: {
|
|
static_cast<DummyInstruction *>(instruction_)->set_next(new_instruction);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Instruction *InstructionCursor::next(Procedure &procedure) const
|
|
{
|
|
switch (type_) {
|
|
case Type::None:
|
|
return nullptr;
|
|
case Type::Entry:
|
|
return procedure.entry();
|
|
case Type::Call:
|
|
return static_cast<CallInstruction *>(instruction_)->next();
|
|
case Type::Branch: {
|
|
BranchInstruction &branch_instruction = *static_cast<BranchInstruction *>(instruction_);
|
|
if (branch_output_) {
|
|
return branch_instruction.branch_true();
|
|
}
|
|
return branch_instruction.branch_false();
|
|
}
|
|
case Type::Destruct:
|
|
return static_cast<DestructInstruction *>(instruction_)->next();
|
|
case Type::Dummy:
|
|
return static_cast<DummyInstruction *>(instruction_)->next();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void Variable::set_name(std::string name)
|
|
{
|
|
name_ = std::move(name);
|
|
}
|
|
|
|
void CallInstruction::set_next(Instruction *instruction)
|
|
{
|
|
if (next_ != nullptr) {
|
|
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
|
}
|
|
if (instruction != nullptr) {
|
|
instruction->prev_.append(*this);
|
|
}
|
|
next_ = instruction;
|
|
}
|
|
|
|
void CallInstruction::set_param_variable(int param_index, Variable *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 CallInstruction::set_params(Span<Variable *> variables)
|
|
{
|
|
BLI_assert(variables.size() == params_.size());
|
|
for (const int i : variables.index_range()) {
|
|
this->set_param_variable(i, variables[i]);
|
|
}
|
|
}
|
|
|
|
void BranchInstruction::set_condition(Variable *variable)
|
|
{
|
|
if (condition_ != nullptr) {
|
|
condition_->users_.remove_first_occurrence_and_reorder(this);
|
|
}
|
|
if (variable != nullptr) {
|
|
variable->users_.append(this);
|
|
}
|
|
condition_ = variable;
|
|
}
|
|
|
|
void BranchInstruction::set_branch_true(Instruction *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 BranchInstruction::set_branch_false(Instruction *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 DestructInstruction::set_variable(Variable *variable)
|
|
{
|
|
if (variable_ != nullptr) {
|
|
variable_->users_.remove_first_occurrence_and_reorder(this);
|
|
}
|
|
if (variable != nullptr) {
|
|
variable->users_.append(this);
|
|
}
|
|
variable_ = variable;
|
|
}
|
|
|
|
void DestructInstruction::set_next(Instruction *instruction)
|
|
{
|
|
if (next_ != nullptr) {
|
|
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
|
}
|
|
if (instruction != nullptr) {
|
|
instruction->prev_.append(*this);
|
|
}
|
|
next_ = instruction;
|
|
}
|
|
|
|
void DummyInstruction::set_next(Instruction *instruction)
|
|
{
|
|
if (next_ != nullptr) {
|
|
next_->prev_.remove_first_occurrence_and_reorder(*this);
|
|
}
|
|
if (instruction != nullptr) {
|
|
instruction->prev_.append(*this);
|
|
}
|
|
next_ = instruction;
|
|
}
|
|
|
|
Variable &Procedure::new_variable(DataType data_type, std::string name)
|
|
{
|
|
Variable &variable = *allocator_.construct<Variable>().release();
|
|
variable.name_ = std::move(name);
|
|
variable.data_type_ = data_type;
|
|
variable.index_in_graph_ = variables_.size();
|
|
variables_.append(&variable);
|
|
return variable;
|
|
}
|
|
|
|
CallInstruction &Procedure::new_call_instruction(const MultiFunction &fn)
|
|
{
|
|
CallInstruction &instruction = *allocator_.construct<CallInstruction>().release();
|
|
instruction.type_ = InstructionType::Call;
|
|
instruction.fn_ = &fn;
|
|
instruction.params_ = allocator_.allocate_array<Variable *>(fn.param_amount());
|
|
instruction.params_.fill(nullptr);
|
|
call_instructions_.append(&instruction);
|
|
return instruction;
|
|
}
|
|
|
|
BranchInstruction &Procedure::new_branch_instruction()
|
|
{
|
|
BranchInstruction &instruction = *allocator_.construct<BranchInstruction>().release();
|
|
instruction.type_ = InstructionType::Branch;
|
|
branch_instructions_.append(&instruction);
|
|
return instruction;
|
|
}
|
|
|
|
DestructInstruction &Procedure::new_destruct_instruction()
|
|
{
|
|
DestructInstruction &instruction = *allocator_.construct<DestructInstruction>().release();
|
|
instruction.type_ = InstructionType::Destruct;
|
|
destruct_instructions_.append(&instruction);
|
|
return instruction;
|
|
}
|
|
|
|
DummyInstruction &Procedure::new_dummy_instruction()
|
|
{
|
|
DummyInstruction &instruction = *allocator_.construct<DummyInstruction>().release();
|
|
instruction.type_ = InstructionType::Dummy;
|
|
dummy_instructions_.append(&instruction);
|
|
return instruction;
|
|
}
|
|
|
|
ReturnInstruction &Procedure::new_return_instruction()
|
|
{
|
|
ReturnInstruction &instruction = *allocator_.construct<ReturnInstruction>().release();
|
|
instruction.type_ = InstructionType::Return;
|
|
return_instructions_.append(&instruction);
|
|
return instruction;
|
|
}
|
|
|
|
void Procedure::add_parameter(ParamType::InterfaceType interface_type, Variable &variable)
|
|
{
|
|
params_.append({interface_type, &variable});
|
|
}
|
|
|
|
void Procedure::set_entry(Instruction &entry)
|
|
{
|
|
if (entry_ != nullptr) {
|
|
entry_->prev_.remove_first_occurrence_and_reorder(InstructionCursor::ForEntry());
|
|
}
|
|
entry_ = &entry;
|
|
entry_->prev_.append(InstructionCursor::ForEntry());
|
|
}
|
|
|
|
Procedure::~Procedure()
|
|
{
|
|
for (CallInstruction *instruction : call_instructions_) {
|
|
instruction->~CallInstruction();
|
|
}
|
|
for (BranchInstruction *instruction : branch_instructions_) {
|
|
instruction->~BranchInstruction();
|
|
}
|
|
for (DestructInstruction *instruction : destruct_instructions_) {
|
|
instruction->~DestructInstruction();
|
|
}
|
|
for (DummyInstruction *instruction : dummy_instructions_) {
|
|
instruction->~DummyInstruction();
|
|
}
|
|
for (ReturnInstruction *instruction : return_instructions_) {
|
|
instruction->~ReturnInstruction();
|
|
}
|
|
for (Variable *variable : variables_) {
|
|
variable->~Variable();
|
|
}
|
|
}
|
|
|
|
bool Procedure::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 Procedure::validate_all_instruction_pointers_set() const
|
|
{
|
|
for (const CallInstruction *instruction : call_instructions_) {
|
|
if (instruction->next_ == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
for (const DestructInstruction *instruction : destruct_instructions_) {
|
|
if (instruction->next_ == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
for (const BranchInstruction *instruction : branch_instructions_) {
|
|
if (instruction->branch_true_ == nullptr) {
|
|
return false;
|
|
}
|
|
if (instruction->branch_false_ == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
for (const DummyInstruction *instruction : dummy_instructions_) {
|
|
if (instruction->next_ == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Procedure::validate_all_params_provided() const
|
|
{
|
|
for (const CallInstruction *instruction : call_instructions_) {
|
|
const MultiFunction &fn = instruction->fn();
|
|
for (const int param_index : fn.param_indices()) {
|
|
const ParamType param_type = fn.param_type(param_index);
|
|
if (param_type.category() == ParamCategory::SingleOutput) {
|
|
/* Single outputs are optional. */
|
|
continue;
|
|
}
|
|
const Variable *variable = instruction->params_[param_index];
|
|
if (variable == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
for (const BranchInstruction *instruction : branch_instructions_) {
|
|
if (instruction->condition_ == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
for (const DestructInstruction *instruction : destruct_instructions_) {
|
|
if (instruction->variable_ == nullptr) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Procedure::validate_same_variables_in_one_call() const
|
|
{
|
|
for (const CallInstruction *instruction : call_instructions_) {
|
|
const MultiFunction &fn = *instruction->fn_;
|
|
for (const int param_index : fn.param_indices()) {
|
|
const ParamType param_type = fn.param_type(param_index);
|
|
const Variable *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 Variable *other_variable = instruction->params_[other_param_index];
|
|
if (other_variable != variable) {
|
|
continue;
|
|
}
|
|
if (ELEM(param_type.interface_type(), ParamType::Mutable, ParamType::Output)) {
|
|
/* When a variable is used as mutable or output parameter, it can only be used once. */
|
|
return false;
|
|
}
|
|
const ParamType 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() != ParamType::Input) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Procedure::validate_parameters() const
|
|
{
|
|
Set<const Variable *> variables;
|
|
for (const Parameter ¶m : params_) {
|
|
/* One variable cannot be used as multiple parameters. */
|
|
if (!variables.add(param.variable)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Procedure::validate_initialization() const
|
|
{
|
|
/* TODO: Issue warning when it maybe wrongly initialized. */
|
|
for (const DestructInstruction *instruction : destruct_instructions_) {
|
|
const Variable &variable = *instruction->variable_;
|
|
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
|
variable);
|
|
if (!state.can_be_initialized) {
|
|
return false;
|
|
}
|
|
}
|
|
for (const BranchInstruction *instruction : branch_instructions_) {
|
|
const Variable &variable = *instruction->condition_;
|
|
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
|
variable);
|
|
if (!state.can_be_initialized) {
|
|
return false;
|
|
}
|
|
}
|
|
for (const CallInstruction *instruction : call_instructions_) {
|
|
const MultiFunction &fn = *instruction->fn_;
|
|
for (const int param_index : fn.param_indices()) {
|
|
const ParamType 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 Variable &variable = *instruction->params_[param_index];
|
|
const InitState state = this->find_initialization_state_before_instruction(*instruction,
|
|
variable);
|
|
switch (param_type.interface_type()) {
|
|
case ParamType::Input:
|
|
case ParamType::Mutable: {
|
|
if (!state.can_be_initialized) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case ParamType::Output: {
|
|
if (!state.can_be_uninitialized) {
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Set<const Variable *> variables_that_should_be_initialized_on_return;
|
|
for (const Parameter ¶m : params_) {
|
|
if (ELEM(param.type, ParamType::Mutable, ParamType::Output)) {
|
|
variables_that_should_be_initialized_on_return.add_new(param.variable);
|
|
}
|
|
}
|
|
for (const ReturnInstruction *instruction : return_instructions_) {
|
|
for (const Variable *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;
|
|
}
|
|
|
|
Procedure::InitState Procedure::find_initialization_state_before_instruction(
|
|
const Instruction &target_instruction, const Variable &target_variable) const
|
|
{
|
|
InitState state;
|
|
|
|
auto check_entry_instruction = [&]() {
|
|
bool caller_initialized_variable = false;
|
|
for (const Parameter ¶m : params_) {
|
|
if (param.variable == &target_variable) {
|
|
if (ELEM(param.type, ParamType::Input, ParamType::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 Instruction *> checked_instructions;
|
|
Stack<const Instruction *> instructions_to_check;
|
|
for (const InstructionCursor &cursor : target_instruction.prev_) {
|
|
if (cursor.instruction() != nullptr) {
|
|
instructions_to_check.push(cursor.instruction());
|
|
}
|
|
}
|
|
|
|
while (!instructions_to_check.is_empty()) {
|
|
const Instruction &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 InstructionType::Call: {
|
|
const CallInstruction &call_instruction = static_cast<const CallInstruction &>(
|
|
instruction);
|
|
const MultiFunction &fn = *call_instruction.fn_;
|
|
for (const int param_index : fn.param_indices()) {
|
|
if (call_instruction.params_[param_index] == &target_variable) {
|
|
const ParamType param_type = fn.param_type(param_index);
|
|
if (param_type.interface_type() == ParamType::Output) {
|
|
state.can_be_initialized = true;
|
|
state_modified = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case InstructionType::Destruct: {
|
|
const DestructInstruction &destruct_instruction = static_cast<const DestructInstruction &>(
|
|
instruction);
|
|
if (destruct_instruction.variable_ == &target_variable) {
|
|
state.can_be_uninitialized = true;
|
|
state_modified = true;
|
|
}
|
|
break;
|
|
}
|
|
case InstructionType::Branch:
|
|
case InstructionType::Dummy:
|
|
case InstructionType::Return: {
|
|
/* These instruction types don't change the initialization state of variables. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!state_modified) {
|
|
if (&instruction == entry_) {
|
|
check_entry_instruction();
|
|
}
|
|
for (const InstructionCursor &cursor : instruction.prev_) {
|
|
if (cursor.instruction() != nullptr) {
|
|
instructions_to_check.push(cursor.instruction());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
class ProcedureDotExport {
|
|
private:
|
|
const Procedure &procedure_;
|
|
dot::DirectedGraph digraph_;
|
|
Map<const Instruction *, dot::Node *> dot_nodes_by_begin_;
|
|
Map<const Instruction *, dot::Node *> dot_nodes_by_end_;
|
|
|
|
public:
|
|
ProcedureDotExport(const Procedure &procedure) : procedure_(procedure) {}
|
|
|
|
std::string generate()
|
|
{
|
|
this->create_nodes();
|
|
this->create_edges();
|
|
return digraph_.to_dot_string();
|
|
}
|
|
|
|
void create_nodes()
|
|
{
|
|
Vector<const Instruction *> 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 Instruction *> handled_instructions;
|
|
|
|
for (const Instruction *representative : all_instructions) {
|
|
if (handled_instructions.contains(representative)) {
|
|
continue;
|
|
}
|
|
Vector<const Instruction *> block_instructions = this->get_instructions_in_block(
|
|
*representative);
|
|
std::stringstream ss;
|
|
ss << "<";
|
|
|
|
for (const Instruction *current : block_instructions) {
|
|
handled_instructions.add_new(current);
|
|
switch (current->type()) {
|
|
case InstructionType::Call: {
|
|
this->instruction_to_string(*static_cast<const CallInstruction *>(current), ss);
|
|
break;
|
|
}
|
|
case InstructionType::Destruct: {
|
|
this->instruction_to_string(*static_cast<const DestructInstruction *>(current), ss);
|
|
break;
|
|
}
|
|
case InstructionType::Dummy: {
|
|
this->instruction_to_string(*static_cast<const DummyInstruction *>(current), ss);
|
|
break;
|
|
}
|
|
case InstructionType::Return: {
|
|
this->instruction_to_string(*static_cast<const ReturnInstruction *>(current), ss);
|
|
break;
|
|
}
|
|
case InstructionType::Branch: {
|
|
this->instruction_to_string(*static_cast<const BranchInstruction *>(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 Instruction *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 Instruction &from_instruction = *item.key;
|
|
dot::Node &from_node = *item.value;
|
|
switch (from_instruction.type()) {
|
|
case InstructionType::Call: {
|
|
const Instruction *to_instruction =
|
|
static_cast<const CallInstruction &>(from_instruction).next();
|
|
create_edge(from_node, to_instruction);
|
|
break;
|
|
}
|
|
case InstructionType::Destruct: {
|
|
const Instruction *to_instruction =
|
|
static_cast<const DestructInstruction &>(from_instruction).next();
|
|
create_edge(from_node, to_instruction);
|
|
break;
|
|
}
|
|
case InstructionType::Dummy: {
|
|
const Instruction *to_instruction =
|
|
static_cast<const DummyInstruction &>(from_instruction).next();
|
|
create_edge(from_node, to_instruction);
|
|
break;
|
|
}
|
|
case InstructionType::Return: {
|
|
break;
|
|
}
|
|
case InstructionType::Branch: {
|
|
const BranchInstruction &branch_instruction = static_cast<const BranchInstruction &>(
|
|
from_instruction);
|
|
const Instruction *to_true_instruction = branch_instruction.branch_true();
|
|
const Instruction *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 Instruction &instruction)
|
|
{
|
|
if (instruction.prev().size() != 1) {
|
|
return true;
|
|
}
|
|
if (ELEM(instruction.prev()[0].type(),
|
|
InstructionCursor::Type::Branch,
|
|
InstructionCursor::Type::Entry))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const Instruction &get_first_instruction_in_block(const Instruction &representative)
|
|
{
|
|
const Instruction *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 Instruction *get_next_instruction_in_block(const Instruction &instruction,
|
|
const Instruction &block_begin)
|
|
{
|
|
const Instruction *next = nullptr;
|
|
switch (instruction.type()) {
|
|
case InstructionType::Call: {
|
|
next = static_cast<const CallInstruction &>(instruction).next();
|
|
break;
|
|
}
|
|
case InstructionType::Destruct: {
|
|
next = static_cast<const DestructInstruction &>(instruction).next();
|
|
break;
|
|
}
|
|
case InstructionType::Dummy: {
|
|
next = static_cast<const DummyInstruction &>(instruction).next();
|
|
break;
|
|
}
|
|
case InstructionType::Return:
|
|
case InstructionType::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 Instruction *> get_instructions_in_block(const Instruction &representative)
|
|
{
|
|
Vector<const Instruction *> instructions;
|
|
const Instruction &begin = this->get_first_instruction_in_block(representative);
|
|
for (const Instruction *current = &begin; current != nullptr;
|
|
current = this->get_next_instruction_in_block(*current, begin))
|
|
{
|
|
instructions.append(current);
|
|
}
|
|
return instructions;
|
|
}
|
|
|
|
void variable_to_string(const Variable *variable, std::stringstream &ss)
|
|
{
|
|
if (variable == nullptr) {
|
|
ss << "null";
|
|
}
|
|
else {
|
|
ss << "$" << variable->index_in_procedure();
|
|
if (!variable->name().is_empty()) {
|
|
ss << "(" << variable->name() << ")";
|
|
}
|
|
}
|
|
}
|
|
|
|
void instruction_name_format(StringRef name, std::stringstream &ss)
|
|
{
|
|
ss << name;
|
|
}
|
|
|
|
void instruction_to_string(const CallInstruction &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 ParamType param_type = fn.param_type(param_index);
|
|
const Variable *variable = instruction.params()[param_index];
|
|
ss << R"(<font color="grey30">)";
|
|
switch (param_type.interface_type()) {
|
|
case ParamType::Input: {
|
|
ss << "in";
|
|
break;
|
|
}
|
|
case ParamType::Mutable: {
|
|
ss << "mut";
|
|
break;
|
|
}
|
|
case ParamType::Output: {
|
|
ss << "out";
|
|
break;
|
|
}
|
|
}
|
|
ss << " </font> ";
|
|
variable_to_string(variable, ss);
|
|
if (param_index < fn.param_amount() - 1) {
|
|
ss << ", ";
|
|
}
|
|
}
|
|
}
|
|
|
|
void instruction_to_string(const DestructInstruction &instruction, std::stringstream &ss)
|
|
{
|
|
instruction_name_format("Destruct ", ss);
|
|
variable_to_string(instruction.variable(), ss);
|
|
}
|
|
|
|
void instruction_to_string(const DummyInstruction & /*instruction*/, std::stringstream &ss)
|
|
{
|
|
instruction_name_format("Dummy ", ss);
|
|
}
|
|
|
|
void instruction_to_string(const ReturnInstruction & /*instruction*/, std::stringstream &ss)
|
|
{
|
|
instruction_name_format("Return ", ss);
|
|
|
|
Vector<ConstParameter> outgoing_parameters;
|
|
for (const ConstParameter ¶m : procedure_.params()) {
|
|
if (ELEM(param.type, ParamType::Mutable, ParamType::Output)) {
|
|
outgoing_parameters.append(param);
|
|
}
|
|
}
|
|
for (const int param_index : outgoing_parameters.index_range()) {
|
|
const ConstParameter ¶m = outgoing_parameters[param_index];
|
|
variable_to_string(param.variable, ss);
|
|
if (param_index < outgoing_parameters.size() - 1) {
|
|
ss << ", ";
|
|
}
|
|
}
|
|
}
|
|
|
|
void instruction_to_string(const BranchInstruction &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<ConstParameter> incoming_parameters;
|
|
for (const ConstParameter ¶m : procedure_.params()) {
|
|
if (ELEM(param.type, ParamType::Input, ParamType::Mutable)) {
|
|
incoming_parameters.append(param);
|
|
}
|
|
}
|
|
for (const int param_index : incoming_parameters.index_range()) {
|
|
const ConstParameter ¶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 Procedure::to_dot() const
|
|
{
|
|
ProcedureDotExport dot_export{*this};
|
|
return dot_export.generate();
|
|
}
|
|
|
|
} // namespace blender::fn::multi_function
|