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