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