1 /* 2 * Copyright (c) 2024, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.incubator.code.writer; 27 28 import java.io.IOException; 29 import java.io.StringWriter; 30 import java.io.UncheckedIOException; 31 import java.io.Writer; 32 import jdk.incubator.code.*; 33 import jdk.incubator.code.op.ExternalizableOp; 34 import jdk.incubator.code.type.JavaType; 35 import jdk.incubator.code.type.impl.JavaTypeUtils; 36 37 import java.lang.reflect.Array; 38 import java.util.Arrays; 39 import java.util.HashMap; 40 import java.util.Map; 41 import java.util.function.Consumer; 42 import java.util.function.Function; 43 44 /** 45 * A writer of code models to the textual form. 46 * <p> 47 * A code model in textual form may be parsed back into the runtime form by parsing it. 48 */ 49 // @@@ We cannot link to OpParser since this code is copied into the jdk.compiler module 50 public final class OpWriter { 51 52 static final class GlobalValueBlockNaming implements Function<CodeItem, String> { 53 final Map<CodeItem, String> gn; 54 int valueOrdinal = 0; 55 56 GlobalValueBlockNaming() { 57 this.gn = new HashMap<>(); 58 } 59 60 private String name(Block b) { 61 Block p = b.parentBody().parentOp().parentBlock(); 62 return (p == null ? "block_" : name(p) + "_") + b.index(); 63 } 64 65 @Override 66 public String apply(CodeItem codeItem) { 67 return switch (codeItem) { 68 case Block block -> gn.computeIfAbsent(block, _b -> name(block)); 69 case Value value -> gn.computeIfAbsent(value, _v -> String.valueOf(valueOrdinal++)); 70 default -> throw new IllegalStateException("Unexpected code item: " + codeItem); 71 }; 72 } 73 } 74 75 static final class AttributeMapper { 76 static String toString(Object value) { 77 if (value == ExternalizableOp.NULL_ATTRIBUTE_VALUE) { 78 return "null"; 79 } 80 81 StringBuilder sb = new StringBuilder(); 82 toString(value, sb); 83 return sb.toString(); 84 } 85 86 static void toString(Object o, StringBuilder sb) { 87 if (o.getClass().isArray()) { 88 // note, while we can't parse back the array representation, this might be useful 89 // for non-externalizable ops that want better string representation of array attribute values (e.g. ONNX) 90 arrayToString(o, sb); 91 } else { 92 switch (o) { 93 case Integer i -> sb.append(i); 94 case Long l -> sb.append(l).append('L'); 95 case Float f -> sb.append(f).append('f'); 96 case Double d -> sb.append(d).append('d'); 97 case Character c -> sb.append('\'').append(c).append('\''); 98 case Boolean b -> sb.append(b); 99 case TypeElement te -> sb.append(JavaTypeUtils.flatten(te.externalize())); 100 default -> { // fallback to a string 101 sb.append('"'); 102 quote(o.toString(), sb); 103 sb.append('"'); 104 } 105 } 106 } 107 } 108 109 static void arrayToString(Object a, StringBuilder sb) { 110 boolean first = true; 111 sb.append("["); 112 for (int i = 0; i < Array.getLength(a); i++) { 113 if (!first) { 114 sb.append(", "); 115 } 116 117 toString(Array.get(a, i), sb); 118 first = false; 119 } 120 sb.append("]"); 121 } 122 } 123 124 static void quote(String s, StringBuilder sb) { 125 for (int i = 0; i < s.length(); i++) { 126 sb.append(quote(s.charAt(i))); 127 } 128 } 129 130 /** 131 * Escapes a character if it has an escape sequence or is 132 * non-printable ASCII. Leaves non-ASCII characters alone. 133 */ 134 // Copied from com.sun.tools.javac.util.Convert 135 static String quote(char ch) { 136 return switch (ch) { 137 case '\b' -> "\\b"; 138 case '\f' -> "\\f"; 139 case '\n' -> "\\n"; 140 case '\r' -> "\\r"; 141 case '\t' -> "\\t"; 142 case '\'' -> "\\'"; 143 case '\"' -> "\\\""; 144 case '\\' -> "\\\\"; 145 default -> (isPrintableAscii(ch)) 146 ? String.valueOf(ch) 147 : String.format("\\u%04x", (int) ch); 148 }; 149 } 150 151 /** 152 * Is a character printable ASCII? 153 */ 154 static boolean isPrintableAscii(char ch) { 155 return ch >= ' ' && ch <= '~'; 156 } 157 158 static final class IndentWriter extends Writer { 159 static final int INDENT = 2; 160 161 final Writer w; 162 int indent; 163 boolean writeIndent = true; 164 165 IndentWriter(Writer w) { 166 this(w, 0); 167 } 168 169 IndentWriter(Writer w, int indent) { 170 this.w = w; 171 this.indent = indent; 172 } 173 174 @Override 175 public void write(char[] cbuf, int off, int len) throws IOException { 176 if (writeIndent) { 177 w.write(" ".repeat(indent)); 178 writeIndent = false; 179 } 180 w.write(cbuf, off, len); 181 if (len > 0 && cbuf[off + len - 1] == '\n') { 182 writeIndent = true; 183 } 184 } 185 186 @Override 187 public void flush() throws IOException { 188 w.flush(); 189 } 190 191 @Override 192 public void close() throws IOException { 193 w.close(); 194 } 195 196 void in() { 197 in(INDENT); 198 } 199 200 void in(int i) { 201 indent += i; 202 } 203 204 void out() { 205 out(INDENT); 206 } 207 208 void out(int i) { 209 indent -= i; 210 } 211 } 212 213 /** 214 * Computes global names for blocks and values in a code model. 215 * <p> 216 * The code model is traversed in the same order as if the model 217 * was written. Therefore, the names in the returned map will the 218 * same as the names that are written. This can be useful for debugging 219 * and testing. 220 * 221 * @param root the code model 222 * @return the map of computed names, modifiable 223 */ 224 public static Function<CodeItem, String> computeGlobalNames(Op root) { 225 OpWriter w = new OpWriter(Writer.nullWriter()); 226 w.writeOp(root); 227 return w.namer(); 228 } 229 230 /** 231 * Writes a code model (an operation) to the character stream. 232 * <p> 233 * The character stream will be flushed after the model is writen. 234 * 235 * @param w the character stream 236 * @param op the code model 237 */ 238 public static void writeTo(Writer w, Op op) { 239 OpWriter ow = new OpWriter(w); 240 ow.writeOp(op); 241 try { 242 w.flush(); 243 } catch (IOException e) { 244 throw new UncheckedIOException(e); 245 } 246 } 247 248 /** 249 * Writes a code model (an operation) to the character stream. 250 * <p> 251 * The character stream will be flushed after the model is writen. 252 * 253 * @param w the character stream 254 * @param op the code model 255 * @param options the writer options 256 */ 257 public static void writeTo(Writer w, Op op, Option... options) { 258 OpWriter ow = new OpWriter(w, options); 259 ow.writeOp(op); 260 try { 261 // @@@ Is this needed? 262 w.flush(); 263 } catch (IOException e) { 264 throw new UncheckedIOException(e); 265 } 266 } 267 268 /** 269 * Writes a code model (an operation) to a string. 270 * 271 * @param op the code model 272 */ 273 public static String toText(Op op) { 274 StringWriter w = new StringWriter(); 275 writeTo(w, op); 276 return w.toString(); 277 } 278 279 /** 280 * Writes a code model (an operation) to a string. 281 * 282 * @param op the code model 283 * @param options the writer options 284 */ 285 public static String toText(Op op, OpWriter.Option... options) { 286 StringWriter w = new StringWriter(); 287 writeTo(w, op, options); 288 return w.toString(); 289 } 290 291 /** 292 * An option that affects the writing operations. 293 */ 294 public sealed interface Option { 295 } 296 297 /** 298 * An option describing the function to use for naming code items. 299 */ 300 public sealed interface CodeItemNamerOption extends Option 301 permits NamerOptionImpl { 302 303 static CodeItemNamerOption of(Function<CodeItem, String> named) { 304 return new NamerOptionImpl(named); 305 } 306 307 static CodeItemNamerOption defaultValue() { 308 return of(new GlobalValueBlockNaming()); 309 } 310 311 Function<CodeItem, String> namer(); 312 } 313 private record NamerOptionImpl(Function<CodeItem, String> namer) implements CodeItemNamerOption { 314 } 315 316 /** 317 * An option describing whether location information should be written or dropped. 318 */ 319 public enum LocationOption implements Option { 320 /** Writes location */ 321 WRITE_LOCATION, 322 /** Drops location */ 323 DROP_LOCATION; 324 325 public static LocationOption defaultValue() { 326 return WRITE_LOCATION; 327 } 328 } 329 330 /** 331 * An option describing whether an operation's descendant code elements should be written or dropped. 332 */ 333 public enum OpDescendantsOption implements Option { 334 /** Writes descendants of an operation, if any */ 335 WRITE_DESCENDANTS, 336 /** Drops descendants of an operation, if any */ 337 DROP_DESCENDANTS; 338 339 public static OpDescendantsOption defaultValue() { 340 return WRITE_DESCENDANTS; 341 } 342 } 343 344 /** 345 * An option describing whether an operation's result be written or dropped if its type is void. 346 */ 347 public enum VoidOpResultOption implements Option { 348 /** Writes void operation result */ 349 WRITE_VOID, 350 /** Drops void operation result */ 351 DROP_VOID; 352 353 public static VoidOpResultOption defaultValue() { 354 return DROP_VOID; 355 } 356 } 357 358 final Function<CodeItem, String> namer; 359 final IndentWriter w; 360 final boolean dropLocation; 361 final boolean dropOpDescendants; 362 final boolean writeVoidOpResult; 363 364 /** 365 * Creates a writer of code models (operations) to their textual form. 366 * 367 * @param w the character stream writer to write the textual form. 368 */ 369 public OpWriter(Writer w) { 370 this.w = new IndentWriter(w); 371 this.namer = new GlobalValueBlockNaming(); 372 this.dropLocation = false; 373 this.dropOpDescendants = false; 374 this.writeVoidOpResult = false; 375 } 376 377 /** 378 * Creates a writer of code models (operations) to their textual form. 379 * 380 * @param w the character stream writer to write the textual form. 381 * @param options the writer options 382 */ 383 public OpWriter(Writer w, Option... options) { 384 Function<CodeItem, String> namer = null; 385 boolean dropLocation = false; 386 boolean dropOpDescendants = false; 387 boolean writeVoidOpResult = false; 388 for (Option option : options) { 389 switch (option) { 390 case CodeItemNamerOption namerOption -> { 391 namer = namerOption.namer(); 392 } 393 case LocationOption locationOption -> { 394 dropLocation = locationOption == 395 LocationOption.DROP_LOCATION; 396 } 397 case OpDescendantsOption opDescendantsOption -> { 398 dropOpDescendants = opDescendantsOption == 399 OpDescendantsOption.DROP_DESCENDANTS; 400 } 401 case VoidOpResultOption voidOpResultOption -> { 402 writeVoidOpResult = voidOpResultOption == VoidOpResultOption.WRITE_VOID; 403 } 404 } 405 } 406 407 this.w = new IndentWriter(w); 408 this.namer = (namer == null) ? new GlobalValueBlockNaming() : namer; 409 this.dropLocation = dropLocation; 410 this.dropOpDescendants = dropOpDescendants; 411 this.writeVoidOpResult = writeVoidOpResult; 412 } 413 414 /** 415 * {@return the function that names blocks and values.} 416 */ 417 public Function<CodeItem, String> namer() { 418 return namer; 419 } 420 421 /** 422 * Writes a code model, an operation, to the character stream. 423 * 424 * @param op the code model 425 */ 426 public void writeOp(Op op) { 427 if (op.parent() != null) { 428 Op.Result opr = op.result(); 429 if (writeVoidOpResult || !opr.type().equals(JavaType.VOID)) { 430 writeValueDeclaration(opr); 431 write(" = "); 432 } 433 } 434 write(op.opName()); 435 436 if (!op.operands().isEmpty()) { 437 write(" "); 438 writeSpaceSeparatedList(op.operands(), this::writeValueUse); 439 } 440 441 if (!op.successors().isEmpty()) { 442 write(" "); 443 writeSpaceSeparatedList(op.successors(), this::writeSuccessor); 444 } 445 446 Map<String, Object> attributes = op instanceof ExternalizableOp exop ? exop.attributes() : Map.of(); 447 if (dropLocation && !attributes.isEmpty() && 448 attributes.containsKey(ExternalizableOp.ATTRIBUTE_LOCATION)) { 449 attributes = new HashMap<>(attributes); 450 attributes.remove(ExternalizableOp.ATTRIBUTE_LOCATION); 451 } 452 if (!attributes.isEmpty()) { 453 write(" "); 454 writeSpaceSeparatedList(attributes.entrySet(), e -> writeAttribute(e.getKey(), e.getValue())); 455 } 456 457 if (!dropOpDescendants && !op.bodies().isEmpty()) { 458 int nBodies = op.bodies().size(); 459 if (nBodies == 1) { 460 write(" "); 461 } else { 462 write("\n"); 463 w.in(); 464 w.in(); 465 } 466 boolean first = true; 467 for (Body body : op.bodies()) { 468 if (!first) { 469 write("\n"); 470 } 471 writeBody(body); 472 first = false; 473 } 474 if (nBodies > 1) { 475 w.out(); 476 w.out(); 477 } 478 } 479 480 write(";"); 481 } 482 483 void writeSuccessor(Block.Reference successor) { 484 writeBlockName(successor.targetBlock()); 485 if (!successor.arguments().isEmpty()) { 486 write("("); 487 writeCommaSeparatedList(successor.arguments(), this::writeValueUse); 488 write(")"); 489 } 490 } 491 492 void writeAttribute(String name, Object value) { 493 write("@"); 494 if (!name.isEmpty()) { 495 write(name); 496 write("="); 497 } 498 write(AttributeMapper.toString(value)); 499 } 500 501 void writeBody(Body body) { 502 Block eb = body.entryBlock(); 503 write("("); 504 writeCommaSeparatedList(eb.parameters(), this::writeValueDeclaration); 505 write(")"); 506 writeType(body.bodyType().returnType()); 507 write(" -> {\n"); 508 w.in(); 509 for (Block b : body.blocks()) { 510 if (!b.isEntryBlock()) { 511 write("\n"); 512 } 513 writeBlock(b); 514 } 515 w.out(); 516 write("}"); 517 } 518 519 void writeBlock(Block block) { 520 if (!block.isEntryBlock()) { 521 writeBlockName(block); 522 if (!block.parameters().isEmpty()) { 523 write("("); 524 writeCommaSeparatedList(block.parameters(), this::writeValueDeclaration); 525 write(")"); 526 } 527 write(":\n"); 528 } 529 w.in(); 530 for (Op op : block.ops()) { 531 writeOp(op); 532 write("\n"); 533 } 534 w.out(); 535 } 536 537 void writeBlockName(Block b) { 538 write("^"); 539 write(namer.apply(b)); 540 } 541 542 void writeValueUse(Value v) { 543 write("%"); 544 write(namer.apply(v)); 545 } 546 547 void writeValueDeclaration(Value v) { 548 write("%"); 549 write(namer.apply(v)); 550 write(" : "); 551 writeType(v.type()); 552 } 553 554 <T> void writeSpaceSeparatedList(Iterable<T> l, Consumer<T> c) { 555 writeSeparatedList(" ", l, c); 556 } 557 558 <T> void writeCommaSeparatedList(Iterable<T> l, Consumer<T> c) { 559 writeSeparatedList(", ", l, c); 560 } 561 562 <T> void writeSeparatedList(String separator, Iterable<T> l, Consumer<T> c) { 563 boolean first = true; 564 for (T t : l) { 565 if (!first) { 566 write(separator); 567 } 568 c.accept(t); 569 first = false; 570 } 571 } 572 573 void writeType(TypeElement te) { 574 write(JavaTypeUtils.flatten(te.externalize()).toString()); 575 } 576 577 void write(String s) { 578 try { 579 w.write(s); 580 } catch (IOException e) { 581 throw new UncheckedIOException(e); 582 } 583 } 584 }