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