package org.graalvm.compiler.printer;
import static org.graalvm.compiler.graph.Edges.Type.Inputs;
import static org.graalvm.compiler.graph.Edges.Type.Successors;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
import org.graalvm.compiler.core.common.cfg.BlockMap;
import org.graalvm.compiler.debug.Debug;
import org.graalvm.compiler.debug.GraalDebugConfig.Options;
import org.graalvm.compiler.graph.CachedGraph;
import org.graalvm.compiler.graph.Edges;
import org.graalvm.compiler.graph.Graph;
import org.graalvm.compiler.graph.InputEdges;
import org.graalvm.compiler.graph.Node;
import org.graalvm.compiler.graph.NodeClass;
import org.graalvm.compiler.graph.NodeList;
import org.graalvm.compiler.graph.NodeMap;
import org.graalvm.compiler.nodes.AbstractBeginNode;
import org.graalvm.compiler.nodes.AbstractEndNode;
import org.graalvm.compiler.nodes.AbstractMergeNode;
import org.graalvm.compiler.nodes.ConstantNode;
import org.graalvm.compiler.nodes.ControlSinkNode;
import org.graalvm.compiler.nodes.ControlSplitNode;
import org.graalvm.compiler.nodes.FixedNode;
import org.graalvm.compiler.nodes.PhiNode;
import org.graalvm.compiler.nodes.ProxyNode;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult;
import org.graalvm.compiler.nodes.VirtualState;
import org.graalvm.compiler.nodes.cfg.Block;
import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
import org.graalvm.compiler.phases.schedule.SchedulePhase;
import jdk.vm.ci.meta.JavaType;
import jdk.vm.ci.meta.ResolvedJavaField;
import jdk.vm.ci.meta.ResolvedJavaMethod;
import jdk.vm.ci.meta.Signature;
public class BinaryGraphPrinter implements GraphPrinter {
private static final int CONSTANT_POOL_MAX_SIZE = 8000;
private static final int BEGIN_GROUP = 0x00;
private static final int BEGIN_GRAPH = 0x01;
private static final int CLOSE_GROUP = 0x02;
private static final int POOL_NEW = 0x00;
private static final int POOL_STRING = 0x01;
private static final int POOL_ENUM = 0x02;
private static final int POOL_CLASS = 0x03;
private static final int POOL_METHOD = 0x04;
private static final int POOL_NULL = 0x05;
private static final int POOL_NODE_CLASS = 0x06;
private static final int POOL_FIELD = 0x07;
private static final int POOL_SIGNATURE = 0x08;
private static final int PROPERTY_POOL = 0x00;
private static final int PROPERTY_INT = 0x01;
private static final int PROPERTY_LONG = 0x02;
private static final int PROPERTY_DOUBLE = 0x03;
private static final int PROPERTY_FLOAT = 0x04;
private static final int PROPERTY_TRUE = 0x05;
private static final int PROPERTY_FALSE = 0x06;
private static final int PROPERTY_ARRAY = 0x07;
private static final int PROPERTY_SUBGRAPH = 0x08;
private static final int KLASS = 0x00;
private static final int ENUM_KLASS = 0x01;
static final int CURRENT_MAJOR_VERSION = 1;
static final int CURRENT_MINOR_VERSION = 0;
static final byte[] MAGIC_BYTES = {'B', 'I', 'G', 'V'};
private void writeVersion() throws IOException {
writeBytesRaw(MAGIC_BYTES);
writeByte(CURRENT_MAJOR_VERSION);
writeByte(CURRENT_MINOR_VERSION);
}
private static final class ConstantPool extends LinkedHashMap<Object, Character> {
private final LinkedList<Character> availableIds;
private char nextId;
private static final long serialVersionUID = -2676889957907285681L;
ConstantPool() {
super(50, 0.65f);
availableIds = new LinkedList<>();
}
@Override
protected boolean removeEldestEntry(java.util.Map.Entry<Object, Character> eldest) {
if (size() > CONSTANT_POOL_MAX_SIZE) {
availableIds.addFirst(eldest.getValue());
return true;
}
return false;
}
private Character nextAvailableId() {
if (!availableIds.isEmpty()) {
return availableIds.removeFirst();
}
return nextId++;
}
public char add(Object obj) {
Character id = nextAvailableId();
put(obj, id);
return id;
}
}
private final ConstantPool constantPool;
private final ByteBuffer buffer;
private final WritableByteChannel channel;
private final SnippetReflectionProvider snippetReflection;
private static final Charset utf8 = Charset.forName("UTF-8");
public BinaryGraphPrinter(WritableByteChannel channel, SnippetReflectionProvider snippetReflection) throws IOException {
constantPool = new ConstantPool();
buffer = ByteBuffer.allocateDirect(256 * 1024);
this.snippetReflection = snippetReflection;
this.channel = channel;
writeVersion();
}
@Override
public SnippetReflectionProvider getSnippetReflectionProvider() {
return snippetReflection;
}
@Override
public void print(Graph graph, String title, Map<Object, Object> properties) throws IOException {
writeByte(BEGIN_GRAPH);
writePoolObject(title);
writeGraph(graph, properties);
flush();
}
private void writeGraph(Graph graph, Map<Object, Object> properties) throws IOException {
ScheduleResult scheduleResult = null;
if (graph instanceof StructuredGraph) {
StructuredGraph structuredGraph = (StructuredGraph) graph;
scheduleResult = structuredGraph.getLastSchedule();
if (scheduleResult == null) {
if (Options.PrintIdealGraphSchedule.getValue() || Debug.contextLookup(Throwable.class) != null) {
try {
SchedulePhase schedule = new SchedulePhase();
schedule.apply(structuredGraph);
scheduleResult = structuredGraph.getLastSchedule();
} catch (Throwable t) {
}
}
}
}
ControlFlowGraph cfg = scheduleResult == null ? Debug.contextLookup(ControlFlowGraph.class) : scheduleResult.getCFG();
BlockMap<List<Node>> blockToNodes = scheduleResult == null ? null : scheduleResult.getBlockToNodesMap();
NodeMap<Block> nodeToBlocks = scheduleResult == null ? null : scheduleResult.getNodeToBlockMap();
List<Block> blocks = cfg == null ? null : Arrays.asList(cfg.getBlocks());
writeProperties(properties);
writeNodes(graph, nodeToBlocks, cfg);
writeBlocks(blocks, blockToNodes);
}
private void flush() throws IOException {
buffer.flip();
channel.write(buffer);
buffer.compact();
}
private void ensureAvailable(int i) throws IOException {
assert buffer.capacity() >= i : "Can not make " + i + " bytes available, buffer is too small";
while (buffer.remaining() < i) {
flush();
}
}
private void writeByte(int b) throws IOException {
ensureAvailable(1);
buffer.put((byte) b);
}
private void writeInt(int b) throws IOException {
ensureAvailable(4);
buffer.putInt(b);
}
private void writeLong(long b) throws IOException {
ensureAvailable(8);
buffer.putLong(b);
}
private void writeDouble(double b) throws IOException {
ensureAvailable(8);
buffer.putDouble(b);
}
private void writeFloat(float b) throws IOException {
ensureAvailable(4);
buffer.putFloat(b);
}
private void writeShort(char b) throws IOException {
ensureAvailable(2);
buffer.putChar(b);
}
private void writeString(String str) throws IOException {
byte[] bytes = str.getBytes(utf8);
writeBytes(bytes);
}
private void writeBytes(byte[] b) throws IOException {
if (b == null) {
writeInt(-1);
} else {
writeInt(b.length);
writeBytesRaw(b);
}
}
private void writeBytesRaw(byte[] b) throws IOException {
int bytesWritten = 0;
while (bytesWritten < b.length) {
int toWrite = Math.min(b.length - bytesWritten, buffer.capacity());
ensureAvailable(toWrite);
buffer.put(b, bytesWritten, toWrite);
bytesWritten += toWrite;
}
}
private void writeInts(int[] b) throws IOException {
if (b == null) {
writeInt(-1);
} else {
writeInt(b.length);
int sizeInBytes = b.length * 4;
ensureAvailable(sizeInBytes);
buffer.asIntBuffer().put(b);
buffer.position(buffer.position() + sizeInBytes);
}
}
private void writeDoubles(double[] b) throws IOException {
if (b == null) {
writeInt(-1);
} else {
writeInt(b.length);
int sizeInBytes = b.length * 8;
ensureAvailable(sizeInBytes);
buffer.asDoubleBuffer().put(b);
buffer.position(buffer.position() + sizeInBytes);
}
}
private void writePoolObject(Object object) throws IOException {
if (object == null) {
writeByte(POOL_NULL);
return;
}
Character id = constantPool.get(object);
if (id == null) {
addPoolEntry(object);
} else {
if (object instanceof Enum<?>) {
writeByte(POOL_ENUM);
} else if (object instanceof Class<?> || object instanceof JavaType) {
writeByte(POOL_CLASS);
} else if (object instanceof NodeClass) {
writeByte(POOL_NODE_CLASS);
} else if (object instanceof ResolvedJavaMethod) {
writeByte(POOL_METHOD);
} else if (object instanceof ResolvedJavaField) {
writeByte(POOL_FIELD);
} else if (object instanceof Signature) {
writeByte(POOL_SIGNATURE);
} else {
writeByte(POOL_STRING);
}
writeShort(id.charValue());
}
}
private static String getClassName(Class<?> klass) {
if (!klass.isArray()) {
return klass.getName();
}
return getClassName(klass.getComponentType()) + "[]";
}
private void addPoolEntry(Object object) throws IOException {
char index = constantPool.add(object);
writeByte(POOL_NEW);
writeShort(index);
if (object instanceof Class<?>) {
Class<?> klass = (Class<?>) object;
writeByte(POOL_CLASS);
writeString(getClassName(klass));
if (klass.isEnum()) {
writeByte(ENUM_KLASS);
Object[] enumConstants = klass.getEnumConstants();
writeInt(enumConstants.length);
for (Object o : enumConstants) {
writePoolObject(((Enum<?>) o).name());
}
} else {
writeByte(KLASS);
}
} else if (object instanceof Enum<?>) {
writeByte(POOL_ENUM);
writePoolObject(object.getClass());
writeInt(((Enum<?>) object).ordinal());
} else if (object instanceof JavaType) {
JavaType type = (JavaType) object;
writeByte(POOL_CLASS);
writeString(type.toJavaName());
writeByte(KLASS);
} else if (object instanceof NodeClass) {
NodeClass<?> nodeClass = (NodeClass<?>) object;
writeByte(POOL_NODE_CLASS);
writeString(nodeClass.getJavaClass().getSimpleName());
writeString(nodeClass.getNameTemplate());
writeEdgesInfo(nodeClass, Inputs);
writeEdgesInfo(nodeClass, Successors);
} else if (object instanceof ResolvedJavaMethod) {
writeByte(POOL_METHOD);
ResolvedJavaMethod method = ((ResolvedJavaMethod) object);
writePoolObject(method.getDeclaringClass());
writePoolObject(method.getName());
writePoolObject(method.getSignature());
writeInt(method.getModifiers());
writeBytes(method.getCode());
} else if (object instanceof ResolvedJavaField) {
writeByte(POOL_FIELD);
ResolvedJavaField field = ((ResolvedJavaField) object);
writePoolObject(field.getDeclaringClass());
writePoolObject(field.getName());
writePoolObject(field.getType().getName());
writeInt(field.getModifiers());
} else if (object instanceof Signature) {
writeByte(POOL_SIGNATURE);
Signature signature = ((Signature) object);
int args = signature.getParameterCount(false);
writeShort((char) args);
for (int i = 0; i < args; i++) {
writePoolObject(signature.getParameterType(i, null).getName());
}
writePoolObject(signature.getReturnType(null).getName());
} else {
writeByte(POOL_STRING);
writeString(object.toString());
}
}
private void writeEdgesInfo(NodeClass<?> nodeClass, Edges.Type type) throws IOException {
Edges edges = nodeClass.getEdges(type);
writeShort((char) edges.getCount());
for (int i = 0; i < edges.getCount(); i++) {
writeByte(i < edges.getDirectCount() ? 0 : 1);
writePoolObject(edges.getName(i));
if (type == Inputs) {
writePoolObject(((InputEdges) edges).getInputType(i));
}
}
}
private void writePropertyObject(Object obj) throws IOException {
if (obj instanceof Integer) {
writeByte(PROPERTY_INT);
writeInt(((Integer) obj).intValue());
} else if (obj instanceof Long) {
writeByte(PROPERTY_LONG);
writeLong(((Long) obj).longValue());
} else if (obj instanceof Double) {
writeByte(PROPERTY_DOUBLE);
writeDouble(((Double) obj).doubleValue());
} else if (obj instanceof Float) {
writeByte(PROPERTY_FLOAT);
writeFloat(((Float) obj).floatValue());
} else if (obj instanceof Boolean) {
if (((Boolean) obj).booleanValue()) {
writeByte(PROPERTY_TRUE);
} else {
writeByte(PROPERTY_FALSE);
}
} else if (obj instanceof Graph) {
writeByte(PROPERTY_SUBGRAPH);
writeGraph((Graph) obj, null);
} else if (obj instanceof CachedGraph) {
writeByte(PROPERTY_SUBGRAPH);
writeGraph(((CachedGraph<?>) obj).getReadonlyCopy(), null);
} else if (obj != null && obj.getClass().isArray()) {
Class<?> componentType = obj.getClass().getComponentType();
if (componentType.isPrimitive()) {
if (componentType == Double.TYPE) {
writeByte(PROPERTY_ARRAY);
writeByte(PROPERTY_DOUBLE);
writeDoubles((double[]) obj);
} else if (componentType == Integer.TYPE) {
writeByte(PROPERTY_ARRAY);
writeByte(PROPERTY_INT);
writeInts((int[]) obj);
} else {
writeByte(PROPERTY_POOL);
writePoolObject(obj);
}
} else {
writeByte(PROPERTY_ARRAY);
writeByte(PROPERTY_POOL);
Object[] array = (Object[]) obj;
writeInt(array.length);
for (Object o : array) {
writePoolObject(o);
}
}
} else {
writeByte(PROPERTY_POOL);
writePoolObject(obj);
}
}
@SuppressWarnings("deprecation")
private static int getNodeId(Node node) {
return node.getId();
}
private Object getBlockForNode(Node node, NodeMap<Block> nodeToBlocks) {
if (nodeToBlocks.isNew(node)) {
return "NEW (not in schedule)";
} else {
Block block = nodeToBlocks.get(node);
if (block != null) {
return block.getId();
} else if (node instanceof PhiNode) {
return getBlockForNode(((PhiNode) node).merge(), nodeToBlocks);
}
}
return null;
}
private void writeNodes(Graph graph, NodeMap<Block> nodeToBlocks, ControlFlowGraph cfg) throws IOException {
Map<Object, Object> props = new HashMap<>();
writeInt(graph.getNodeCount());
for (Node node : graph.getNodes()) {
NodeClass<?> nodeClass = node.getNodeClass();
node.getDebugProperties(props);
if (cfg != null && Options.PrintGraphProbabilities.getValue() && node instanceof FixedNode) {
try {
props.put("probability", cfg.blockFor(node).probability());
} catch (Throwable t) {
props.put("probability", 0.0);
props.put("probability-exception", t);
}
}
if (nodeToBlocks != null) {
Object block = getBlockForNode(node, nodeToBlocks);
if (block != null) {
props.put("node-to-block", block);
}
}
if (node instanceof ControlSinkNode) {
props.put("category", "controlSink");
} else if (node instanceof ControlSplitNode) {
props.put("category", "controlSplit");
} else if (node instanceof AbstractMergeNode) {
props.put("category", "merge");
} else if (node instanceof AbstractBeginNode) {
props.put("category", "begin");
} else if (node instanceof AbstractEndNode) {
props.put("category", "end");
} else if (node instanceof FixedNode) {
props.put("category", "fixed");
} else if (node instanceof VirtualState) {
props.put("category", "state");
} else if (node instanceof PhiNode) {
props.put("category", "phi");
} else if (node instanceof ProxyNode) {
props.put("category", "proxy");
} else {
if (node instanceof ConstantNode) {
ConstantNode cn = (ConstantNode) node;
updateStringPropertiesForConstant(props, cn);
}
props.put("category", "floating");
}
writeInt(getNodeId(node));
writePoolObject(nodeClass);
writeByte(node.predecessor() == null ? 0 : 1);
writeProperties(props);
writeEdges(node, Inputs);
writeEdges(node, Successors);
props.clear();
}
}
private void writeProperties(Map<Object, Object> props) throws IOException {
if (props == null) {
writeShort((char) 0);
return;
}
writeShort((char) props.size());
for (Entry<Object, Object> entry : props.entrySet()) {
String key = entry.getKey().toString();
writePoolObject(key);
writePropertyObject(entry.getValue());
}
}
private void writeEdges(Node node, Edges.Type type) throws IOException {
NodeClass<?> nodeClass = node.getNodeClass();
Edges edges = nodeClass.getEdges(type);
final long[] curOffsets = edges.getOffsets();
for (int i = 0; i < edges.getDirectCount(); i++) {
writeNodeRef(Edges.getNode(node, curOffsets, i));
}
for (int i = edges.getDirectCount(); i < edges.getCount(); i++) {
NodeList<Node> list = Edges.getNodeList(node, curOffsets, i);
if (list == null) {
writeShort((char) 0);
} else {
int listSize = list.count();
assert listSize == ((char) listSize);
writeShort((char) listSize);
for (Node edge : list) {
writeNodeRef(edge);
}
}
}
}
private void writeNodeRef(Node edge) throws IOException {
if (edge != null) {
writeInt(getNodeId(edge));
} else {
writeInt(-1);
}
}
private void writeBlocks(List<Block> blocks, BlockMap<List<Node>> blockToNodes) throws IOException {
if (blocks != null && blockToNodes != null) {
for (Block block : blocks) {
List<Node> nodes = blockToNodes.get(block);
if (nodes == null) {
writeInt(0);
return;
}
}
writeInt(blocks.size());
for (Block block : blocks) {
List<Node> nodes = blockToNodes.get(block);
List<Node> extraNodes = new LinkedList<>();
writeInt(block.getId());
for (Node node : nodes) {
if (node instanceof AbstractMergeNode) {
AbstractMergeNode merge = (AbstractMergeNode) node;
for (PhiNode phi : merge.phis()) {
if (!nodes.contains(phi)) {
extraNodes.add(phi);
}
}
}
}
writeInt(nodes.size() + extraNodes.size());
for (Node node : nodes) {
writeInt(getNodeId(node));
}
for (Node node : extraNodes) {
writeInt(getNodeId(node));
}
writeInt(block.getSuccessors().length);
for (Block sux : block.getSuccessors()) {
writeInt(sux.getId());
}
}
} else {
writeInt(0);
}
}
@Override
public void beginGroup(String name, String shortName, ResolvedJavaMethod method, int bci, Map<Object, Object> properties) throws IOException {
writeByte(BEGIN_GROUP);
writePoolObject(name);
writePoolObject(shortName);
writePoolObject(method);
writeInt(bci);
writeProperties(properties);
}
@Override
public void endGroup() throws IOException {
writeByte(CLOSE_GROUP);
}
@Override
public void close() {
try {
flush();
channel.close();
} catch (IOException ex) {
throw new Error(ex);
}
}
}