Package jdk.graal.compiler.graphio


package jdk.graal.compiler.graphio
Send your graphs to IGV via a socket or a file. This package allows one to easily encode any graph-like data structure and send it for visualization to OracleLab's Ideal Graph Visualizer tool. Assuming you already have your own data structure that contains nodes and edges among them, creating a GraphOutput specialized for your data is a matter of implementing a single interface:
class AcmeGraphStructure implements
GraphStructure<AcmeGraph, AcmeNode, AcmeNodeType, AcmePorts> {

    @Override
    public AcmeGraph graph(AcmeGraph currentGraph, Object obj) {
        return obj instanceof AcmeGraph ? (AcmeGraph) obj : null;
    }

    @Override
    public Iterable<? extends AcmeNode> nodes(AcmeGraph graph) {
        return graph.allNodes();
    }

    @Override
    public int nodesCount(AcmeGraph graph) {
        return graph.allNodes().size();
    }

    @Override
    public int nodeId(AcmeNode node) {
        return node.id;
    }

    @Override
    public boolean nodeHasPredecessor(AcmeNode node) {
        return node.id > 0;
    }

    @Override
    public void nodeProperties(
        AcmeGraph graph, AcmeNode node, Map<String, ? super Object> properties
    ) {
        properties.put("id", node.id);
    }

    @Override
    public AcmeNodeType nodeClass(Object obj) {
        return obj instanceof AcmeNodeType ? (AcmeNodeType) obj : null;
    }

    @Override
    public AcmeNode node(Object obj) {
        return obj instanceof AcmeNode ? (AcmeNode) obj : null;
    }

    @Override
    public AcmeNodeType classForNode(AcmeNode node) {
        // we have only one type of nodes
        return AcmeNodeType.STANDARD;
    }


    @Override
    public String nameTemplate(AcmeNodeType nodeClass) {
        return "Acme ({p#id})";
    }

    @Override
    public Object nodeClassType(AcmeNodeType nodeClass) {
        return nodeClass.getClass();
    }

    @Override
    public AcmePorts portInputs(AcmeNodeType nodeClass) {
        return AcmePorts.INPUT;
    }

    @Override
    public AcmePorts portOutputs(AcmeNodeType nodeClass) {
        return AcmePorts.OUTPUT;
    }

    @Override
    public int portSize(AcmePorts port) {
        return port == AcmePorts.OUTPUT ? 1 : 0;
    }

    @Override
    public boolean edgeDirect(AcmePorts port, int index) {
        return false;
    }

    @Override
    public String edgeName(AcmePorts port, int index) {
        return port.name();
    }

    @Override
    public Object edgeType(AcmePorts port, int index) {
        return port;
    }

    @Override
    public Collection<? extends AcmeNode> edgeNodes(
        AcmeGraph graph, AcmeNode node, AcmePorts port, int index
    ) {
        if (port == AcmePorts.OUTPUT) {
            return node.outgoing.targets;
        }
        return null;
    }
}

The GraphStructure interface defines the set of operations that are needed by the graph protocol to encode a graph into the IGV expected format. The graph structure is implemented as a so called singletonizer API pattern: there is no need to change your data structures or implement some special interfaces - everything needed is provided by implementing the GraphStructure operations.

The next step is to turn this graph structure into an instance of GraphOutput. To do so use the associated builder just like shown in the following method:

static GraphOutput<AcmeGraph, ?> buildOutput(WritableByteChannel channel)
throws IOException {
    return GraphOutput.newBuilder(acmeGraphStructure()).
        // use the latest version; currently 6.0
        protocolVersion(6, 0).
        build(channel);
}
Now you are ready to dump your graph into IGV. Where to obtain the right channel? One option is to create a FileChannel and dump the data into a file (preferrably with .bgv extension). The other is to open a socket to port 4445 (the default port IGV listens to) and dump the data there. Here is an example:
static void dump(File toFile) throws IOException {
    try (
        FileChannel ch = new FileOutputStream(toFile).getChannel();
        GraphOutput<AcmeGraph, ?> output = buildOutput(ch);
    ) {
        AcmeNode root = new AcmeNode(0);
        AcmeNode n1 = new AcmeNode(1);
        AcmeNode n2 = new AcmeNode(2);
        AcmeNode n3 = new AcmeNode(3);

        root.linkTo(n1);
        root.linkTo(n2);
        n1.linkTo(n3);
        n2.linkTo(n3);

        AcmeGraph diamondGraph = new AcmeGraph(root);

        output.beginGroup(diamondGraph, "Diamond", "dia", null, 0, null);
        output.print(diamondGraph, null, 0, "Diamond graph #%d", 1);
        output.endGroup();
    }
}
Call the dump method with pointer to file diamond.bgv and then you can open the file in IGV. The result will look like this:

You can verify the behavior directly in the IGV by downloading diamond.bgv file generated from the above diamond structure graph.

The primary IGV focus is on graphs used by the compiler. As such they aren't plain graphs, but contain various compiler oriented attributes:

all these additional interfaces (GraphBlocks, GraphElements and GraphTypes) are optional - they don't have to be provided. As such they can be specified via GraphOutput.Builder instance methods, which may, but need not be called at all. Here is an example:
static GraphOutput<AcmeGraph, ?> buildAll(WritableByteChannel channel)
throws IOException {
    GraphBlocks<AcmeGraph, AcmeBlocks, AcmeNode> graphBlocks = acmeBlocks();
    GraphElements<AcmeMethod, AcmeField,
        AcmeSignature, AcmeCodePosition> graphElements = acmeElements();
    GraphTypes graphTypes = acmeTypes();

    return GraphOutput.newBuilder(acmeGraphStructure()).
        protocolVersion(6, 0).
        blocks(graphBlocks).
        elements(graphElements).
        types(graphTypes).
        build(channel);
}
All these interfaces follow the singletonizer API pattern again - e.g. no need to change your existing data structures, just implement the operations provided by the interfaces you pass into the builder. By combining these interfaces together you can get as rich, colorful, source linked graphs as the compiler produces to describe its optimizations.
  • Class
    Description
    Special support for dealing with blocks.
    GraphElements<M,F,S,P>
    Representation of methods, fields, their signatures and code locations.
    Provides source location information about compiled code.
    Instance of output to dump informations about a compiler compilations.
    Builder to configure and create an instance of GraphOutput.
    Interface that defines structure of a compiler graph.
    Special support for dealing with enums.