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