Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  mod.rs   Sprache: unbekannt

 
/*!
Backend for [DOT][dot] (Graphviz).

This backend writes a graph in the DOT language, for the ease
of IR inspection and debugging.

[dot]: https://graphviz.org/doc/info/lang.html
*/

use crate::{
    arena::Handle,
    valid::{FunctionInfo, ModuleInfo},
};

use std::{
    borrow::Cow,
    fmt::{Error as FmtError, Write as _},
};

/// Configuration options for the dot backend
#[derive(Clone, Default)]
pub struct Options {
    /// Only emit function bodies
    pub cfg_only: bool,
}

/// Identifier used to address a graph node
type NodeId = usize;

/// Stores the target nodes for control flow statements
#[derive(Default, Clone, Copy)]
struct Targets {
    /// The node, if some, where continue operations will land
    continue_target: Option<usize>,
    /// The node, if some, where break operations will land
    break_target: Option<usize>,
}

/// Stores information about the graph of statements
#[derive(Default)]
struct StatementGraph {
    /// List of node names
    nodes: Vec<&'static str>,
    /// List of edges of the control flow, the items are defined as
    /// (from, to, label)
    flow: Vec<(NodeId, NodeId, &'static str)>,
    /// List of implicit edges of the control flow, used for jump
    /// operations such as continue or break, the items are defined as
    /// (from, to, label, color_id)
    jumps: Vec<(NodeId, NodeId, &'static str, usize)>,
    /// List of dependency relationships between a statement node and
    /// expressions
    dependencies: Vec<(NodeId, Handle<crate::Expression>, &'static str)>,
    /// List of expression emitted by statement node
    emits: Vec<(NodeId, Handle<crate::Expression>)>,
    /// List of function call by statement node
    calls: Vec<(NodeId, Handle<crate::Function>)>,
}

impl StatementGraph {
    /// Adds a new block to the statement graph, returning the first and last node, respectively
    fn add(&mut self, block: &[crate::Statement], targets: Targets) -> (NodeId, NodeId) {
        use crate::Statement as S;

        // The first node of the block isn't a statement but a virtual node
        let root = self.nodes.len();
        self.nodes.push(if root == 0 { "Root" } else { "Node" });
        // Track the last placed node, this will be returned to the caller and
        // will also be used to generate the control flow edges
        let mut last_node = root;
        for statement in block {
            // Reserve a new node for the current statement and link it to the
            // node of the previous statement
            let id = self.nodes.len();
            self.flow.push((last_node, id, ""));
            self.nodes.push(""); // reserve space

            // Track the node identifier for the merge node, the merge node is
            // the last node of a statement, normally this is the node itself,
            // but for control flow statements such as `if`s and `switch`s this
            // is a virtual node where all branches merge back.
            let mut merge_id = id;

            self.nodes[id] = match *statement {
                S::Emit(ref range) => {
                    for handle in range.clone() {
                        self.emits.push((id, handle));
                    }
                    "Emit"
                }
                S::Kill => "Kill", //TODO: link to the beginning
                S::Break => {
                    // Try to link to the break target, otherwise produce
                    // a broken connection
                    if let Some(target) = targets.break_target {
                        self.jumps.push((id, target, "Break", 5))
                    } else {
                        self.jumps.push((id, root, "Broken", 7))
                    }
                    "Break"
                }
                S::Continue => {
                    // Try to link to the continue target, otherwise produce
                    // a broken connection
                    if let Some(target) = targets.continue_target {
                        self.jumps.push((id, target, "Continue", 5))
                    } else {
                        self.jumps.push((id, root, "Broken", 7))
                    }
                    "Continue"
                }
                S::Barrier(_flags) => "Barrier",
                S::Block(ref b) => {
                    let (other, last) = self.add(b, targets);
                    self.flow.push((id, other, ""));
                    // All following nodes should connect to the end of the block
                    // statement so change the merge id to it.
                    merge_id = last;
                    "Block"
                }
                S::If {
                    condition,
                    ref accept,
                    ref reject,
                } => {
                    self.dependencies.push((id, condition, "condition"));
                    let (accept_id, accept_last) = self.add(accept, targets);
                    self.flow.push((id, accept_id, "accept"));
                    let (reject_id, reject_last) = self.add(reject, targets);
                    self.flow.push((id, reject_id, "reject"));

                    // Create a merge node, link the branches to it and set it
                    // as the merge node to make the next statement node link to it
                    merge_id = self.nodes.len();
                    self.nodes.push("Merge");
                    self.flow.push((accept_last, merge_id, ""));
                    self.flow.push((reject_last, merge_id, ""));

                    "If"
                }
                S::Switch {
                    selector,
                    ref cases,
                } => {
                    self.dependencies.push((id, selector, "selector"));

                    // Create a merge node and set it as the merge node to make
                    // the next statement node link to it
                    merge_id = self.nodes.len();
                    self.nodes.push("Merge");

                    // Create a new targets structure and set the break target
                    // to the merge node
                    let mut targets = targets;
                    targets.break_target = Some(merge_id);

                    for case in cases {
                        let (case_id, case_last) = self.add(&case.body, targets);
                        let label = match case.value {
                            crate::SwitchValue::Default => "default",
                            _ => "case",
                        };
                        self.flow.push((id, case_id, label));
                        // Link the last node of the branch to the merge node
                        self.flow.push((case_last, merge_id, ""));
                    }
                    "Switch"
                }
                S::Loop {
                    ref body,
                    ref continuing,
                    break_if,
                } => {
                    // Create a new targets structure and set the break target
                    // to the merge node, this must happen before generating the
                    // continuing block since it can break.
                    let mut targets = targets;
                    targets.break_target = Some(id);

                    let (continuing_id, continuing_last) = self.add(continuing, targets);

                    // Set the the continue target to the beginning
                    // of the newly generated continuing block
                    targets.continue_target = Some(continuing_id);

                    let (body_id, body_last) = self.add(body, targets);

                    self.flow.push((id, body_id, "body"));

                    // Link the last node of the body to the continuing block
                    self.flow.push((body_last, continuing_id, "continuing"));
                    // Link the last node of the continuing block back to the
                    // beginning of the loop body
                    self.flow.push((continuing_last, body_id, "continuing"));

                    if let Some(expr) = break_if {
                        self.dependencies.push((continuing_id, expr, "break if"));
                    }

                    "Loop"
                }
                S::Return { value } => {
                    if let Some(expr) = value {
                        self.dependencies.push((id, expr, "value"));
                    }
                    "Return"
                }
                S::Store { pointer, value } => {
                    self.dependencies.push((id, value, "value"));
                    self.emits.push((id, pointer));
                    "Store"
                }
                S::ImageStore {
                    image,
                    coordinate,
                    array_index,
                    value,
                } => {
                    self.dependencies.push((id, image, "image"));
                    self.dependencies.push((id, coordinate, "coordinate"));
                    if let Some(expr) = array_index {
                        self.dependencies.push((id, expr, "array_index"));
                    }
                    self.dependencies.push((id, value, "value"));
                    "ImageStore"
                }
                S::Call {
                    function,
                    ref arguments,
                    result,
                } => {
                    for &arg in arguments {
                        self.dependencies.push((id, arg, "arg"));
                    }
                    if let Some(expr) = result {
                        self.emits.push((id, expr));
                    }
                    self.calls.push((id, function));
                    "Call"
                }
                S::Atomic {
                    pointer,
                    ref fun,
                    value,
                    result,
                } => {
                    if let Some(result) = result {
                        self.emits.push((id, result));
                    }
                    self.dependencies.push((id, pointer, "pointer"));
                    self.dependencies.push((id, value, "value"));
                    if let crate::AtomicFunction::Exchange { compare: Some(cmp) } = *fun {
                        self.dependencies.push((id, cmp, "cmp"));
                    }
                    "Atomic"
                }
                S::ImageAtomic {
                    image,
                    coordinate,
                    array_index,
                    fun: _,
                    value,
                } => {
                    self.dependencies.push((id, image, "image"));
                    self.dependencies.push((id, coordinate, "coordinate"));
                    if let Some(expr) = array_index {
                        self.dependencies.push((id, expr, "array_index"));
                    }
                    self.dependencies.push((id, value, "value"));
                    "ImageAtomic"
                }
                S::WorkGroupUniformLoad { pointer, result } => {
                    self.emits.push((id, result));
                    self.dependencies.push((id, pointer, "pointer"));
                    "WorkGroupUniformLoad"
                }
                S::RayQuery { query, ref fun } => {
                    self.dependencies.push((id, query, "query"));
                    match *fun {
                        crate::RayQueryFunction::Initialize {
                            acceleration_structure,
                            descriptor,
                        } => {
                            self.dependencies.push((
                                id,
                                acceleration_structure,
                                "acceleration_structure",
                            ));
                            self.dependencies.push((id, descriptor, "descriptor"));
                            "RayQueryInitialize"
                        }
                        crate::RayQueryFunction::Proceed { result } => {
                            self.emits.push((id, result));
                            "RayQueryProceed"
                        }
                        crate::RayQueryFunction::Terminate => "RayQueryTerminate",
                    }
                }
                S::SubgroupBallot { result, predicate } => {
                    if let Some(predicate) = predicate {
                        self.dependencies.push((id, predicate, "predicate"));
                    }
                    self.emits.push((id, result));
                    "SubgroupBallot"
                }
                S::SubgroupCollectiveOperation {
                    op,
                    collective_op,
                    argument,
                    result,
                } => {
                    self.dependencies.push((id, argument, "arg"));
                    self.emits.push((id, result));
                    match (collective_op, op) {
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => {
                            "SubgroupAll"
                        }
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => {
                            "SubgroupAny"
                        }
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => {
                            "SubgroupAdd"
                        }
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => {
                            "SubgroupMul"
                        }
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => {
                            "SubgroupMax"
                        }
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => {
                            "SubgroupMin"
                        }
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => {
                            "SubgroupAnd"
                        }
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => {
                            "SubgroupOr"
                        }
                        (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => {
                            "SubgroupXor"
                        }
                        (
                            crate::CollectiveOperation::ExclusiveScan,
                            crate::SubgroupOperation::Add,
                        ) => "SubgroupExclusiveAdd",
                        (
                            crate::CollectiveOperation::ExclusiveScan,
                            crate::SubgroupOperation::Mul,
                        ) => "SubgroupExclusiveMul",
                        (
                            crate::CollectiveOperation::InclusiveScan,
                            crate::SubgroupOperation::Add,
                        ) => "SubgroupInclusiveAdd",
                        (
                            crate::CollectiveOperation::InclusiveScan,
                            crate::SubgroupOperation::Mul,
                        ) => "SubgroupInclusiveMul",
                        _ => unimplemented!(),
                    }
                }
                S::SubgroupGather {
                    mode,
                    argument,
                    result,
                } => {
                    match mode {
                        crate::GatherMode::BroadcastFirst => {}
                        crate::GatherMode::Broadcast(index)
                        | crate::GatherMode::Shuffle(index)
                        | crate::GatherMode::ShuffleDown(index)
                        | crate::GatherMode::ShuffleUp(index)
                        | crate::GatherMode::ShuffleXor(index) => {
                            self.dependencies.push((id, index, "index"))
                        }
                    }
                    self.dependencies.push((id, argument, "arg"));
                    self.emits.push((id, result));
                    match mode {
                        crate::GatherMode::BroadcastFirst => "SubgroupBroadcastFirst",
                        crate::GatherMode::Broadcast(_) => "SubgroupBroadcast",
                        crate::GatherMode::Shuffle(_) => "SubgroupShuffle",
                        crate::GatherMode::ShuffleDown(_) => "SubgroupShuffleDown",
                        crate::GatherMode::ShuffleUp(_) => "SubgroupShuffleUp",
                        crate::GatherMode::ShuffleXor(_) => "SubgroupShuffleXor",
                    }
                }
            };
            // Set the last node to the merge node
            last_node = merge_id;
        }
        (root, last_node)
    }
}

fn name(option: &Option<String>) -> &str {
    option.as_deref().unwrap_or_default()
}

/// set39 color scheme from <https://graphviz.org/doc/info/colors.html>
const COLORS: &[&str] = &[
    "white", // pattern starts at 1
    "#8dd3c7", "#ffffb3", "#bebada", "#fb8072", "#80b1d3", "#fdb462", "#b3de69", "#fccde5",
    "#d9d9d9",
];

struct Prefixed<T>(Handle<T>);

impl std::fmt::Display for Prefixed<crate::Expression> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.write_prefixed(f, "e")
    }
}

impl std::fmt::Display for Prefixed<crate::LocalVariable> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.write_prefixed(f, "l")
    }
}

impl std::fmt::Display for Prefixed<crate::GlobalVariable> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.write_prefixed(f, "g")
    }
}

impl std::fmt::Display for Prefixed<crate::Function> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.write_prefixed(f, "f")
    }
}

fn write_fun(
    output: &mut String,
    prefix: String,
    fun: &crate::Function,
    info: Option<&FunctionInfo>,
    options: &Options,
) -> Result<(), FmtError> {
    writeln!(output, "\t\tnode [ style=filled ]")?;

    if !options.cfg_only {
        for (handle, var) in fun.local_variables.iter() {
            writeln!(
                output,
                "\t\t{}_{} [ shape=hexagon label=\"{:?} '{}'\" ]",
                prefix,
                Prefixed(handle),
                handle,
                name(&var.name),
            )?;
        }

        write_function_expressions(output, &prefix, fun, info)?;
    }

    let mut sg = StatementGraph::default();
    sg.add(&fun.body, Targets::default());
    for (index, label) in sg.nodes.into_iter().enumerate() {
        writeln!(
            output,
            "\t\t{prefix}_s{index} [ shape=square label=\"{label}\" ]",
        )?;
    }
    for (from, to, label) in sg.flow {
        writeln!(
            output,
            "\t\t{prefix}_s{from} -> {prefix}_s{to} [ arrowhead=tee label=\"{label}\" ]",
        )?;
    }
    for (from, to, label, color_id) in sg.jumps {
        writeln!(
            output,
            "\t\t{}_s{} -> {}_s{} [ arrowhead=tee style=dashed color=\"{}\" label=\"{}\" ]",
            prefix, from, prefix, to, COLORS[color_id], label,
        )?;
    }

    if !options.cfg_only {
        for (to, expr, label) in sg.dependencies {
            writeln!(
                output,
                "\t\t{}_{} -> {}_s{} [ label=\"{}\" ]",
                prefix,
                Prefixed(expr),
                prefix,
                to,
                label,
            )?;
        }
        for (from, to) in sg.emits {
            writeln!(
                output,
                "\t\t{}_s{} -> {}_{} [ style=dotted ]",
                prefix,
                from,
                prefix,
                Prefixed(to),
            )?;
        }
    }

    assert!(sg.calls.is_empty());
    for (from, function) in sg.calls {
        writeln!(
            output,
            "\t\t{}_s{} -> {}_s0",
            prefix,
            from,
            Prefixed(function),
        )?;
    }

    Ok(())
}

fn write_function_expressions(
    output: &mut String,
    prefix: &str,
    fun: &crate::Function,
    info: Option<&FunctionInfo>,
) -> Result<(), FmtError> {
    enum Payload<'a> {
        Arguments(&'a [Handle<crate::Expression>]),
        Local(Handle<crate::LocalVariable>),
        Global(Handle<crate::GlobalVariable>),
    }

    let mut edges = crate::FastHashMap::<&str, _>::default();
    let mut payload = None;
    for (handle, expression) in fun.expressions.iter() {
        use crate::Expression as E;
        let (label, color_id) = match *expression {
            E::Literal(_) => ("Literal".into(), 2),
            E::Constant(_) => ("Constant".into(), 2),
            E::Override(_) => ("Override".into(), 2),
            E::ZeroValue(_) => ("ZeroValue".into(), 2),
            E::Compose { ref components, .. } => {
                payload = Some(Payload::Arguments(components));
                ("Compose".into(), 3)
            }
            E::Access { base, index } => {
                edges.insert("base", base);
                edges.insert("index", index);
                ("Access".into(), 1)
            }
            E::AccessIndex { base, index } => {
                edges.insert("base", base);
                (format!("AccessIndex[{index}]").into(), 1)
            }
            E::Splat { size, value } => {
                edges.insert("value", value);
                (format!("Splat{size:?}").into(), 3)
            }
            E::Swizzle {
                size,
                vector,
                pattern,
            } => {
                edges.insert("vector", vector);
                (format!("Swizzle{:?}", &pattern[..size as usize]).into(), 3)
            }
            E::FunctionArgument(index) => (format!("Argument[{index}]").into(), 1),
            E::GlobalVariable(h) => {
                payload = Some(Payload::Global(h));
                ("Global".into(), 2)
            }
            E::LocalVariable(h) => {
                payload = Some(Payload::Local(h));
                ("Local".into(), 1)
            }
            E::Load { pointer } => {
                edges.insert("pointer", pointer);
                ("Load".into(), 4)
            }
            E::ImageSample {
                image,
                sampler,
                gather,
                coordinate,
                array_index,
                offset: _,
                level,
                depth_ref,
            } => {
                edges.insert("image", image);
                edges.insert("sampler", sampler);
                edges.insert("coordinate", coordinate);
                if let Some(expr) = array_index {
                    edges.insert("array_index", expr);
                }
                match level {
                    crate::SampleLevel::Auto => {}
                    crate::SampleLevel::Zero => {}
                    crate::SampleLevel::Exact(expr) => {
                        edges.insert("level", expr);
                    }
                    crate::SampleLevel::Bias(expr) => {
                        edges.insert("bias", expr);
                    }
                    crate::SampleLevel::Gradient { x, y } => {
                        edges.insert("grad_x", x);
                        edges.insert("grad_y", y);
                    }
                }
                if let Some(expr) = depth_ref {
                    edges.insert("depth_ref", expr);
                }
                let string = match gather {
                    Some(component) => Cow::Owned(format!("ImageGather{component:?}")),
                    _ => Cow::Borrowed("ImageSample"),
                };
                (string, 5)
            }
            E::ImageLoad {
                image,
                coordinate,
                array_index,
                sample,
                level,
            } => {
                edges.insert("image", image);
                edges.insert("coordinate", coordinate);
                if let Some(expr) = array_index {
                    edges.insert("array_index", expr);
                }
                if let Some(sample) = sample {
                    edges.insert("sample", sample);
                }
                if let Some(level) = level {
                    edges.insert("level", level);
                }
                ("ImageLoad".into(), 5)
            }
            E::ImageQuery { image, query } => {
                edges.insert("image", image);
                let args = match query {
                    crate::ImageQuery::Size { level } => {
                        if let Some(expr) = level {
                            edges.insert("level", expr);
                        }
                        Cow::from("ImageSize")
                    }
                    _ => Cow::Owned(format!("{query:?}")),
                };
                (args, 7)
            }
            E::Unary { op, expr } => {
                edges.insert("expr", expr);
                (format!("{op:?}").into(), 6)
            }
            E::Binary { op, left, right } => {
                edges.insert("left", left);
                edges.insert("right", right);
                (format!("{op:?}").into(), 6)
            }
            E::Select {
                condition,
                accept,
                reject,
            } => {
                edges.insert("condition", condition);
                edges.insert("accept", accept);
                edges.insert("reject", reject);
                ("Select".into(), 3)
            }
            E::Derivative { axis, ctrl, expr } => {
                edges.insert("", expr);
                (format!("d{axis:?}{ctrl:?}").into(), 8)
            }
            E::Relational { fun, argument } => {
                edges.insert("arg", argument);
                (format!("{fun:?}").into(), 6)
            }
            E::Math {
                fun,
                arg,
                arg1,
                arg2,
                arg3,
            } => {
                edges.insert("arg", arg);
                if let Some(expr) = arg1 {
                    edges.insert("arg1", expr);
                }
                if let Some(expr) = arg2 {
                    edges.insert("arg2", expr);
                }
                if let Some(expr) = arg3 {
                    edges.insert("arg3", expr);
                }
                (format!("{fun:?}").into(), 7)
            }
            E::As {
                kind,
                expr,
                convert,
            } => {
                edges.insert("", expr);
                let string = match convert {
                    Some(width) => format!("Convert<{kind:?},{width}>"),
                    None => format!("Bitcast<{kind:?}>"),
                };
                (string.into(), 3)
            }
            E::CallResult(_function) => ("CallResult".into(), 4),
            E::AtomicResult { .. } => ("AtomicResult".into(), 4),
            E::WorkGroupUniformLoadResult { .. } => ("WorkGroupUniformLoadResult".into(), 4),
            E::ArrayLength(expr) => {
                edges.insert("", expr);
                ("ArrayLength".into(), 7)
            }
            E::RayQueryProceedResult => ("rayQueryProceedResult".into(), 4),
            E::RayQueryGetIntersection { query, committed } => {
                edges.insert("", query);
                let ty = if committed { "Committed" } else { "Candidate" };
                (format!("rayQueryGet{ty}Intersection").into(), 4)
            }
            E::SubgroupBallotResult => ("SubgroupBallotResult".into(), 4),
            E::SubgroupOperationResult { .. } => ("SubgroupOperationResult".into(), 4),
        };

        // give uniform expressions an outline
        let color_attr = match info {
            Some(info) if info[handle].uniformity.non_uniform_result.is_none() => "fillcolor",
            _ => "color",
        };
        writeln!(
            output,
            "\t\t{}_{} [ {}=\"{}\" label=\"{:?} {}\" ]",
            prefix,
            Prefixed(handle),
            color_attr,
            COLORS[color_id],
            handle,
            label,
        )?;

        for (key, edge) in edges.drain() {
            writeln!(
                output,
                "\t\t{}_{} -> {}_{} [ label=\"{}\" ]",
                prefix,
                Prefixed(edge),
                prefix,
                Prefixed(handle),
                key,
            )?;
        }
        match payload.take() {
            Some(Payload::Arguments(list)) => {
                write!(output, "\t\t{{")?;
                for &comp in list {
                    write!(output, " {}_{}", prefix, Prefixed(comp))?;
                }
                writeln!(output, " }} -> {}_{}", prefix, Prefixed(handle))?;
            }
            Some(Payload::Local(h)) => {
                writeln!(
                    output,
                    "\t\t{}_{} -> {}_{}",
                    prefix,
                    Prefixed(h),
                    prefix,
                    Prefixed(handle),
                )?;
            }
            Some(Payload::Global(h)) => {
                writeln!(
                    output,
                    "\t\t{} -> {}_{} [fillcolor=gray]",
                    Prefixed(h),
                    prefix,
                    Prefixed(handle),
                )?;
            }
            None => {}
        }
    }

    Ok(())
}

/// Write shader module to a [`String`].
pub fn write(
    module: &crate::Module,
    mod_info: Option<&ModuleInfo>,
    options: Options,
) -> Result<String, FmtError> {
    use std::fmt::Write as _;

    let mut output = String::new();
    output += "digraph Module {\n";

    if !options.cfg_only {
        writeln!(output, "\tsubgraph cluster_globals {{")?;
        writeln!(output, "\t\tlabel=\"Globals\"")?;
        for (handle, var) in module.global_variables.iter() {
            writeln!(
                output,
                "\t\t{} [ shape=hexagon label=\"{:?} {:?}/'{}'\" ]",
                Prefixed(handle),
                handle,
                var.space,
                name(&var.name),
            )?;
        }
        writeln!(output, "\t}}")?;
    }

    for (handle, fun) in module.functions.iter() {
        let prefix = Prefixed(handle).to_string();
        writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
        writeln!(
            output,
            "\t\tlabel=\"Function{:?}/'{}'\"",
            handle,
            name(&fun.name)
        )?;
        let info = mod_info.map(|a| &a[handle]);
        write_fun(&mut output, prefix, fun, info, &options)?;
        writeln!(output, "\t}}")?;
    }
    for (ep_index, ep) in module.entry_points.iter().enumerate() {
        let prefix = format!("ep{ep_index}");
        writeln!(output, "\tsubgraph cluster_{prefix} {{")?;
        writeln!(output, "\t\tlabel=\"{:?}/'{}'\"", ep.stage, ep.name)?;
        let info = mod_info.map(|a| a.get_entry_point(ep_index));
        write_fun(&mut output, prefix, &ep.function, info, &options)?;
        writeln!(output, "\t}}")?;
    }

    output += "}\n";
    Ok(output)
}

[ Dauer der Verarbeitung: 0.42 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge