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