1 /*
   2  * Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.printer;
  24 
  25 import static org.graalvm.compiler.graph.Edges.Type.Inputs;
  26 import static org.graalvm.compiler.graph.Edges.Type.Successors;
  27 
  28 import java.io.IOException;
  29 import java.nio.ByteBuffer;
  30 import java.nio.channels.WritableByteChannel;
  31 import java.nio.charset.Charset;
  32 import java.util.Arrays;
  33 import java.util.HashMap;
  34 import java.util.LinkedHashMap;
  35 import java.util.LinkedList;
  36 import java.util.List;
  37 import java.util.Map;
  38 import java.util.Map.Entry;
  39 
  40 import org.graalvm.compiler.api.replacements.SnippetReflectionProvider;
  41 import org.graalvm.compiler.bytecode.Bytecode;
  42 import org.graalvm.compiler.core.common.cfg.BlockMap;
  43 import org.graalvm.compiler.debug.DebugContext;
  44 import org.graalvm.compiler.debug.DebugOptions;
  45 import org.graalvm.compiler.graph.CachedGraph;
  46 import org.graalvm.compiler.graph.Edges;
  47 import org.graalvm.compiler.graph.Graph;
  48 import org.graalvm.compiler.graph.InputEdges;
  49 import org.graalvm.compiler.graph.Node;
  50 import org.graalvm.compiler.graph.NodeClass;
  51 import org.graalvm.compiler.graph.NodeList;
  52 import org.graalvm.compiler.graph.NodeMap;
  53 import org.graalvm.compiler.nodes.AbstractBeginNode;
  54 import org.graalvm.compiler.nodes.AbstractEndNode;
  55 import org.graalvm.compiler.nodes.AbstractMergeNode;
  56 import org.graalvm.compiler.nodes.ConstantNode;
  57 import org.graalvm.compiler.nodes.ControlSinkNode;
  58 import org.graalvm.compiler.nodes.ControlSplitNode;
  59 import org.graalvm.compiler.nodes.FixedNode;
  60 import org.graalvm.compiler.nodes.PhiNode;
  61 import org.graalvm.compiler.nodes.ProxyNode;
  62 import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult;
  63 import org.graalvm.compiler.nodes.VirtualState;
  64 import org.graalvm.compiler.nodes.cfg.Block;
  65 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph;
  66 
  67 import jdk.vm.ci.meta.JavaType;
  68 import jdk.vm.ci.meta.ResolvedJavaField;
  69 import jdk.vm.ci.meta.ResolvedJavaMethod;
  70 import jdk.vm.ci.meta.Signature;
  71 import org.graalvm.compiler.graph.NodeSourcePosition;
  72 
  73 public class BinaryGraphPrinter implements GraphPrinter {
  74 
  75     private static final int CONSTANT_POOL_MAX_SIZE = 8000;
  76 
  77     private static final int BEGIN_GROUP = 0x00;
  78     private static final int BEGIN_GRAPH = 0x01;
  79     private static final int CLOSE_GROUP = 0x02;
  80 
  81     private static final int POOL_NEW = 0x00;
  82     private static final int POOL_STRING = 0x01;
  83     private static final int POOL_ENUM = 0x02;
  84     private static final int POOL_CLASS = 0x03;
  85     private static final int POOL_METHOD = 0x04;
  86     private static final int POOL_NULL = 0x05;
  87     private static final int POOL_NODE_CLASS = 0x06;
  88     private static final int POOL_FIELD = 0x07;
  89     private static final int POOL_SIGNATURE = 0x08;
  90     private static final int POOL_NODE_SOURCE_POSITION = 0x09;
  91 
  92     private static final int PROPERTY_POOL = 0x00;
  93     private static final int PROPERTY_INT = 0x01;
  94     private static final int PROPERTY_LONG = 0x02;
  95     private static final int PROPERTY_DOUBLE = 0x03;
  96     private static final int PROPERTY_FLOAT = 0x04;
  97     private static final int PROPERTY_TRUE = 0x05;
  98     private static final int PROPERTY_FALSE = 0x06;
  99     private static final int PROPERTY_ARRAY = 0x07;
 100     private static final int PROPERTY_SUBGRAPH = 0x08;
 101 
 102     private static final int KLASS = 0x00;
 103     private static final int ENUM_KLASS = 0x01;
 104 
 105     static final int CURRENT_MAJOR_VERSION = 4;
 106     static final int CURRENT_MINOR_VERSION = 0;
 107 
 108     static final byte[] MAGIC_BYTES = {'B', 'I', 'G', 'V'};
 109 
 110     private void writeVersion() throws IOException {
 111         writeBytesRaw(MAGIC_BYTES);
 112         writeByte(CURRENT_MAJOR_VERSION);
 113         writeByte(CURRENT_MINOR_VERSION);
 114     }
 115 
 116     private static final class ConstantPool extends LinkedHashMap<Object, Character> {
 117 
 118         private final LinkedList<Character> availableIds;
 119         private char nextId;
 120         private static final long serialVersionUID = -2676889957907285681L;
 121 
 122         ConstantPool() {
 123             super(50, 0.65f);
 124             availableIds = new LinkedList<>();
 125         }
 126 
 127         @Override
 128         protected boolean removeEldestEntry(java.util.Map.Entry<Object, Character> eldest) {
 129             if (size() > CONSTANT_POOL_MAX_SIZE) {
 130                 availableIds.addFirst(eldest.getValue());
 131                 return true;
 132             }
 133             return false;
 134         }
 135 
 136         private Character nextAvailableId() {
 137             if (!availableIds.isEmpty()) {
 138                 return availableIds.removeFirst();
 139             }
 140             return nextId++;
 141         }
 142 
 143         public char add(Object obj) {
 144             Character id = nextAvailableId();
 145             put(obj, id);
 146             return id;
 147         }
 148     }
 149 
 150     private final ConstantPool constantPool;
 151     private final ByteBuffer buffer;
 152     private final WritableByteChannel channel;
 153     private final SnippetReflectionProvider snippetReflection;
 154 
 155     private static final Charset utf8 = Charset.forName("UTF-8");
 156 
 157     public BinaryGraphPrinter(WritableByteChannel channel, SnippetReflectionProvider snippetReflection) throws IOException {
 158         constantPool = new ConstantPool();
 159         this.snippetReflection = snippetReflection;
 160         buffer = ByteBuffer.allocateDirect(256 * 1024);
 161         this.channel = channel;
 162         writeVersion();
 163     }
 164 
 165     @Override
 166     public SnippetReflectionProvider getSnippetReflectionProvider() {
 167         return snippetReflection;
 168     }
 169 
 170     @SuppressWarnings("all")
 171     @Override
 172     public void print(DebugContext debug, Graph graph, Map<Object, Object> properties, int id, String format, Object... args) throws IOException {
 173         writeByte(BEGIN_GRAPH);
 174         if (CURRENT_MAJOR_VERSION >= 3) {
 175             writeInt(id);
 176             writeString(format);
 177             writeInt(args.length);
 178             for (Object a : args) {
 179                 writePropertyObject(debug, a);
 180             }
 181         } else {
 182             writePoolObject(id + ": " + String.format(format, simplifyClassArgs(args)));
 183         }
 184         writeGraph(debug, graph, properties);
 185         flush();
 186     }
 187 
 188     private void writeGraph(DebugContext debug, Graph graph, Map<Object, Object> properties) throws IOException {
 189         boolean needSchedule = DebugOptions.PrintGraphWithSchedule.getValue(graph.getOptions()) || debug.contextLookup(Throwable.class) != null;
 190         ScheduleResult scheduleResult = needSchedule ? GraphPrinter.getScheduleOrNull(graph) : null;
 191         ControlFlowGraph cfg = scheduleResult == null ? debug.contextLookup(ControlFlowGraph.class) : scheduleResult.getCFG();
 192         BlockMap<List<Node>> blockToNodes = scheduleResult == null ? null : scheduleResult.getBlockToNodesMap();
 193         NodeMap<Block> nodeToBlocks = scheduleResult == null ? null : scheduleResult.getNodeToBlockMap();
 194         List<Block> blocks = cfg == null ? null : Arrays.asList(cfg.getBlocks());
 195         writeProperties(debug, properties);
 196         writeNodes(debug, graph, nodeToBlocks, cfg);
 197         writeBlocks(blocks, blockToNodes);
 198     }
 199 
 200     private void flush() throws IOException {
 201         buffer.flip();
 202         /*
 203          * Try not to let interrupted threads abort the write. There's still a race here but an
 204          * interrupt that's been pending for a long time shouldn't stop this writing.
 205          */
 206         boolean interrupted = Thread.interrupted();
 207         try {
 208             channel.write(buffer);
 209         } finally {
 210             if (interrupted) {
 211                 Thread.currentThread().interrupt();
 212             }
 213         }
 214         buffer.compact();
 215     }
 216 
 217     private void ensureAvailable(int i) throws IOException {
 218         assert buffer.capacity() >= i : "Can not make " + i + " bytes available, buffer is too small";
 219         while (buffer.remaining() < i) {
 220             flush();
 221         }
 222     }
 223 
 224     private void writeByte(int b) throws IOException {
 225         ensureAvailable(1);
 226         buffer.put((byte) b);
 227     }
 228 
 229     private void writeInt(int b) throws IOException {
 230         ensureAvailable(4);
 231         buffer.putInt(b);
 232     }
 233 
 234     private void writeLong(long b) throws IOException {
 235         ensureAvailable(8);
 236         buffer.putLong(b);
 237     }
 238 
 239     private void writeDouble(double b) throws IOException {
 240         ensureAvailable(8);
 241         buffer.putDouble(b);
 242     }
 243 
 244     private void writeFloat(float b) throws IOException {
 245         ensureAvailable(4);
 246         buffer.putFloat(b);
 247     }
 248 
 249     private void writeShort(char b) throws IOException {
 250         ensureAvailable(2);
 251         buffer.putChar(b);
 252     }
 253 
 254     private void writeString(String str) throws IOException {
 255         byte[] bytes = str.getBytes(utf8);
 256         writeBytes(bytes);
 257     }
 258 
 259     private void writeBytes(byte[] b) throws IOException {
 260         if (b == null) {
 261             writeInt(-1);
 262         } else {
 263             writeInt(b.length);
 264             writeBytesRaw(b);
 265         }
 266     }
 267 
 268     private void writeBytesRaw(byte[] b) throws IOException {
 269         int bytesWritten = 0;
 270         while (bytesWritten < b.length) {
 271             int toWrite = Math.min(b.length - bytesWritten, buffer.capacity());
 272             ensureAvailable(toWrite);
 273             buffer.put(b, bytesWritten, toWrite);
 274             bytesWritten += toWrite;
 275         }
 276     }
 277 
 278     private void writeInts(int[] b) throws IOException {
 279         if (b == null) {
 280             writeInt(-1);
 281         } else {
 282             writeInt(b.length);
 283             int sizeInBytes = b.length * 4;
 284             ensureAvailable(sizeInBytes);
 285             buffer.asIntBuffer().put(b);
 286             buffer.position(buffer.position() + sizeInBytes);
 287         }
 288     }
 289 
 290     private void writeDoubles(double[] b) throws IOException {
 291         if (b == null) {
 292             writeInt(-1);
 293         } else {
 294             writeInt(b.length);
 295             int sizeInBytes = b.length * 8;
 296             ensureAvailable(sizeInBytes);
 297             buffer.asDoubleBuffer().put(b);
 298             buffer.position(buffer.position() + sizeInBytes);
 299         }
 300     }
 301 
 302     private void writePoolObject(Object object) throws IOException {
 303         if (object == null) {
 304             writeByte(POOL_NULL);
 305             return;
 306         }
 307         Character id = constantPool.get(object);
 308         if (id == null) {
 309             addPoolEntry(object);
 310         } else {
 311             if (object instanceof Enum<?>) {
 312                 writeByte(POOL_ENUM);
 313             } else if (object instanceof Class<?> || object instanceof JavaType) {
 314                 writeByte(POOL_CLASS);
 315             } else if (object instanceof NodeClass) {
 316                 writeByte(POOL_NODE_CLASS);
 317             } else if (object instanceof ResolvedJavaMethod || object instanceof Bytecode) {
 318                 writeByte(POOL_METHOD);
 319             } else if (object instanceof ResolvedJavaField) {
 320                 writeByte(POOL_FIELD);
 321             } else if (object instanceof Signature) {
 322                 writeByte(POOL_SIGNATURE);
 323             } else if (CURRENT_MAJOR_VERSION >= 4 && object instanceof NodeSourcePosition) {
 324                 writeByte(POOL_NODE_SOURCE_POSITION);
 325             } else {
 326                 writeByte(POOL_STRING);
 327             }
 328             writeShort(id.charValue());
 329         }
 330     }
 331 
 332     private static String getClassName(Class<?> klass) {
 333         if (!klass.isArray()) {
 334             return klass.getName();
 335         }
 336         return getClassName(klass.getComponentType()) + "[]";
 337     }
 338 
 339     @SuppressWarnings("all")
 340     private void addPoolEntry(Object object) throws IOException {
 341         char index = constantPool.add(object);
 342         writeByte(POOL_NEW);
 343         writeShort(index);
 344         if (object instanceof Class<?>) {
 345             Class<?> klass = (Class<?>) object;
 346             writeByte(POOL_CLASS);
 347             writeString(getClassName(klass));
 348             if (klass.isEnum()) {
 349                 writeByte(ENUM_KLASS);
 350                 Object[] enumConstants = klass.getEnumConstants();
 351                 writeInt(enumConstants.length);
 352                 for (Object o : enumConstants) {
 353                     writePoolObject(((Enum<?>) o).name());
 354                 }
 355             } else {
 356                 writeByte(KLASS);
 357             }
 358         } else if (object instanceof Enum<?>) {
 359             writeByte(POOL_ENUM);
 360             writePoolObject(object.getClass());
 361             writeInt(((Enum<?>) object).ordinal());
 362         } else if (object instanceof JavaType) {
 363             JavaType type = (JavaType) object;
 364             writeByte(POOL_CLASS);
 365             writeString(type.toJavaName());
 366             writeByte(KLASS);
 367         } else if (object instanceof NodeClass) {
 368             NodeClass<?> nodeClass = (NodeClass<?>) object;
 369             writeByte(POOL_NODE_CLASS);
 370             if (CURRENT_MAJOR_VERSION >= 3) {
 371                 writePoolObject(nodeClass.getJavaClass());
 372                 writeString(nodeClass.getNameTemplate());
 373             } else {
 374                 writeString(nodeClass.getJavaClass().getSimpleName());
 375                 String nameTemplate = nodeClass.getNameTemplate();
 376                 writeString(nameTemplate.isEmpty() ? nodeClass.shortName() : nameTemplate);
 377             }
 378             writeEdgesInfo(nodeClass, Inputs);
 379             writeEdgesInfo(nodeClass, Successors);
 380         } else if (object instanceof ResolvedJavaMethod || object instanceof Bytecode) {
 381             writeByte(POOL_METHOD);
 382             ResolvedJavaMethod method;
 383             if (object instanceof Bytecode) {
 384                 method = ((Bytecode) object).getMethod();
 385             } else {
 386                 method = ((ResolvedJavaMethod) object);
 387             }
 388             writePoolObject(method.getDeclaringClass());
 389             writePoolObject(method.getName());
 390             writePoolObject(method.getSignature());
 391             writeInt(method.getModifiers());
 392             writeBytes(method.getCode());
 393         } else if (object instanceof ResolvedJavaField) {
 394             writeByte(POOL_FIELD);
 395             ResolvedJavaField field = ((ResolvedJavaField) object);
 396             writePoolObject(field.getDeclaringClass());
 397             writePoolObject(field.getName());
 398             writePoolObject(field.getType().getName());
 399             writeInt(field.getModifiers());
 400         } else if (object instanceof Signature) {
 401             writeByte(POOL_SIGNATURE);
 402             Signature signature = ((Signature) object);
 403             int args = signature.getParameterCount(false);
 404             writeShort((char) args);
 405             for (int i = 0; i < args; i++) {
 406                 writePoolObject(signature.getParameterType(i, null).getName());
 407             }
 408             writePoolObject(signature.getReturnType(null).getName());
 409         } else if (CURRENT_MAJOR_VERSION >= 4 && object instanceof NodeSourcePosition) {
 410             writeByte(POOL_NODE_SOURCE_POSITION);
 411             NodeSourcePosition pos = (NodeSourcePosition) object;
 412             ResolvedJavaMethod method = pos.getMethod();
 413             writePoolObject(method);
 414             final int bci = pos.getBCI();
 415             writeInt(bci);
 416             StackTraceElement ste = method.asStackTraceElement(bci);
 417             if (ste != null) {
 418                 String fn = ste.getFileName();
 419                 writePoolObject(fn);
 420                 if (fn != null) {
 421                     writeInt(ste.getLineNumber());
 422                 }
 423             } else {
 424                 writePoolObject(null);
 425             }
 426             writePoolObject(pos.getCaller());
 427         } else {
 428             writeByte(POOL_STRING);
 429             writeString(object.toString());
 430         }
 431     }
 432 
 433     private void writeEdgesInfo(NodeClass<?> nodeClass, Edges.Type type) throws IOException {
 434         Edges edges = nodeClass.getEdges(type);
 435         writeShort((char) edges.getCount());
 436         for (int i = 0; i < edges.getCount(); i++) {
 437             writeByte(i < edges.getDirectCount() ? 0 : 1);
 438             writePoolObject(edges.getName(i));
 439             if (type == Inputs) {
 440                 writePoolObject(((InputEdges) edges).getInputType(i));
 441             }
 442         }
 443     }
 444 
 445     private void writePropertyObject(DebugContext debug, Object obj) throws IOException {
 446         if (obj instanceof Integer) {
 447             writeByte(PROPERTY_INT);
 448             writeInt(((Integer) obj).intValue());
 449         } else if (obj instanceof Long) {
 450             writeByte(PROPERTY_LONG);
 451             writeLong(((Long) obj).longValue());
 452         } else if (obj instanceof Double) {
 453             writeByte(PROPERTY_DOUBLE);
 454             writeDouble(((Double) obj).doubleValue());
 455         } else if (obj instanceof Float) {
 456             writeByte(PROPERTY_FLOAT);
 457             writeFloat(((Float) obj).floatValue());
 458         } else if (obj instanceof Boolean) {
 459             if (((Boolean) obj).booleanValue()) {
 460                 writeByte(PROPERTY_TRUE);
 461             } else {
 462                 writeByte(PROPERTY_FALSE);
 463             }
 464         } else if (obj instanceof Graph) {
 465             writeByte(PROPERTY_SUBGRAPH);
 466             writeGraph(debug, (Graph) obj, null);
 467         } else if (obj instanceof CachedGraph) {
 468             writeByte(PROPERTY_SUBGRAPH);
 469             writeGraph(debug, ((CachedGraph<?>) obj).getReadonlyCopy(), null);
 470         } else if (obj != null && obj.getClass().isArray()) {
 471             Class<?> componentType = obj.getClass().getComponentType();
 472             if (componentType.isPrimitive()) {
 473                 if (componentType == Double.TYPE) {
 474                     writeByte(PROPERTY_ARRAY);
 475                     writeByte(PROPERTY_DOUBLE);
 476                     writeDoubles((double[]) obj);
 477                 } else if (componentType == Integer.TYPE) {
 478                     writeByte(PROPERTY_ARRAY);
 479                     writeByte(PROPERTY_INT);
 480                     writeInts((int[]) obj);
 481                 } else {
 482                     writeByte(PROPERTY_POOL);
 483                     writePoolObject(obj);
 484                 }
 485             } else {
 486                 writeByte(PROPERTY_ARRAY);
 487                 writeByte(PROPERTY_POOL);
 488                 Object[] array = (Object[]) obj;
 489                 writeInt(array.length);
 490                 for (Object o : array) {
 491                     writePoolObject(o);
 492                 }
 493             }
 494         } else {
 495             writeByte(PROPERTY_POOL);
 496             writePoolObject(obj);
 497         }
 498     }
 499 
 500     @SuppressWarnings("deprecation")
 501     private static int getNodeId(Node node) {
 502         return node.getId();
 503     }
 504 
 505     private Object getBlockForNode(Node node, NodeMap<Block> nodeToBlocks) {
 506         if (nodeToBlocks.isNew(node)) {
 507             return "NEW (not in schedule)";
 508         } else {
 509             Block block = nodeToBlocks.get(node);
 510             if (block != null) {
 511                 return block.getId();
 512             } else if (node instanceof PhiNode) {
 513                 return getBlockForNode(((PhiNode) node).merge(), nodeToBlocks);
 514             }
 515         }
 516         return null;
 517     }
 518 
 519     private void writeNodes(DebugContext debug, Graph graph, NodeMap<Block> nodeToBlocks, ControlFlowGraph cfg) throws IOException {
 520         Map<Object, Object> props = new HashMap<>();
 521 
 522         writeInt(graph.getNodeCount());
 523 
 524         for (Node node : graph.getNodes()) {
 525             NodeClass<?> nodeClass = node.getNodeClass();
 526             node.getDebugProperties(props);
 527             if (cfg != null && DebugOptions.PrintGraphProbabilities.getValue(graph.getOptions()) && node instanceof FixedNode) {
 528                 try {
 529                     props.put("probability", cfg.blockFor(node).probability());
 530                 } catch (Throwable t) {
 531                     props.put("probability", 0.0);
 532                     props.put("probability-exception", t);
 533                 }
 534             }
 535 
 536             try {
 537                 props.put("NodeCost-Size", node.estimatedNodeSize());
 538                 props.put("NodeCost-Cycles", node.estimatedNodeCycles());
 539             } catch (Throwable t) {
 540                 props.put("node-cost-exception", t.getMessage());
 541             }
 542 
 543             if (nodeToBlocks != null) {
 544                 Object block = getBlockForNode(node, nodeToBlocks);
 545                 if (block != null) {
 546                     props.put("node-to-block", block);
 547                 }
 548             }
 549 
 550             if (node instanceof ControlSinkNode) {
 551                 props.put("category", "controlSink");
 552             } else if (node instanceof ControlSplitNode) {
 553                 props.put("category", "controlSplit");
 554             } else if (node instanceof AbstractMergeNode) {
 555                 props.put("category", "merge");
 556             } else if (node instanceof AbstractBeginNode) {
 557                 props.put("category", "begin");
 558             } else if (node instanceof AbstractEndNode) {
 559                 props.put("category", "end");
 560             } else if (node instanceof FixedNode) {
 561                 props.put("category", "fixed");
 562             } else if (node instanceof VirtualState) {
 563                 props.put("category", "state");
 564             } else if (node instanceof PhiNode) {
 565                 props.put("category", "phi");
 566             } else if (node instanceof ProxyNode) {
 567                 props.put("category", "proxy");
 568             } else {
 569                 if (node instanceof ConstantNode) {
 570                     ConstantNode cn = (ConstantNode) node;
 571                     updateStringPropertiesForConstant(props, cn);
 572                 }
 573                 props.put("category", "floating");
 574             }
 575 
 576             writeInt(getNodeId(node));
 577             writePoolObject(nodeClass);
 578             writeByte(node.predecessor() == null ? 0 : 1);
 579             writeProperties(debug, props);
 580             writeEdges(node, Inputs);
 581             writeEdges(node, Successors);
 582 
 583             props.clear();
 584         }
 585     }
 586 
 587     private void writeProperties(DebugContext debug, Map<Object, Object> props) throws IOException {
 588         if (props == null) {
 589             writeShort((char) 0);
 590             return;
 591         }
 592         // properties
 593         writeShort((char) props.size());
 594         for (Entry<Object, Object> entry : props.entrySet()) {
 595             String key = entry.getKey().toString();
 596             writePoolObject(key);
 597             writePropertyObject(debug, entry.getValue());
 598         }
 599     }
 600 
 601     private void writeEdges(Node node, Edges.Type type) throws IOException {
 602         NodeClass<?> nodeClass = node.getNodeClass();
 603         Edges edges = nodeClass.getEdges(type);
 604         final long[] curOffsets = edges.getOffsets();
 605         for (int i = 0; i < edges.getDirectCount(); i++) {
 606             writeNodeRef(Edges.getNode(node, curOffsets, i));
 607         }
 608         for (int i = edges.getDirectCount(); i < edges.getCount(); i++) {
 609             NodeList<Node> list = Edges.getNodeList(node, curOffsets, i);
 610             if (list == null) {
 611                 writeShort((char) 0);
 612             } else {
 613                 int listSize = list.count();
 614                 assert listSize == ((char) listSize);
 615                 writeShort((char) listSize);
 616                 for (Node edge : list) {
 617                     writeNodeRef(edge);
 618                 }
 619             }
 620         }
 621     }
 622 
 623     private void writeNodeRef(Node edge) throws IOException {
 624         if (edge != null) {
 625             writeInt(getNodeId(edge));
 626         } else {
 627             writeInt(-1);
 628         }
 629     }
 630 
 631     private void writeBlocks(List<Block> blocks, BlockMap<List<Node>> blockToNodes) throws IOException {
 632         if (blocks != null && blockToNodes != null) {
 633             for (Block block : blocks) {
 634                 List<Node> nodes = blockToNodes.get(block);
 635                 if (nodes == null) {
 636                     writeInt(0);
 637                     return;
 638                 }
 639             }
 640             writeInt(blocks.size());
 641             for (Block block : blocks) {
 642                 List<Node> nodes = blockToNodes.get(block);
 643                 List<Node> extraNodes = new LinkedList<>();
 644                 writeInt(block.getId());
 645                 for (Node node : nodes) {
 646                     if (node instanceof AbstractMergeNode) {
 647                         AbstractMergeNode merge = (AbstractMergeNode) node;
 648                         for (PhiNode phi : merge.phis()) {
 649                             if (!nodes.contains(phi)) {
 650                                 extraNodes.add(phi);
 651                             }
 652                         }
 653                     }
 654                 }
 655                 writeInt(nodes.size() + extraNodes.size());
 656                 for (Node node : nodes) {
 657                     writeInt(getNodeId(node));
 658                 }
 659                 for (Node node : extraNodes) {
 660                     writeInt(getNodeId(node));
 661                 }
 662                 writeInt(block.getSuccessors().length);
 663                 for (Block sux : block.getSuccessors()) {
 664                     writeInt(sux.getId());
 665                 }
 666             }
 667         } else {
 668             writeInt(0);
 669         }
 670     }
 671 
 672     @Override
 673     public void beginGroup(DebugContext debug, String name, String shortName, ResolvedJavaMethod method, int bci, Map<Object, Object> properties) throws IOException {
 674         writeByte(BEGIN_GROUP);
 675         writePoolObject(name);
 676         writePoolObject(shortName);
 677         writePoolObject(method);
 678         writeInt(bci);
 679         writeProperties(debug, properties);
 680     }
 681 
 682     @Override
 683     public void endGroup() throws IOException {
 684         writeByte(CLOSE_GROUP);
 685     }
 686 
 687     @Override
 688     public void close() {
 689         try {
 690             flush();
 691             channel.close();
 692         } catch (IOException ex) {
 693             throw new Error(ex);
 694         }
 695     }
 696 }