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