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 */
237 public static void writeTo(OutputStream out, Op op, Option... options) {
238 writeTo(new OutputStreamWriter(out, StandardCharsets.UTF_8), op, options);
239 }
240
241 /**
242 * Writes a code model (an operation) to the character stream.
243 * <p>
244 * The character stream will be flushed after the model is writen.
245 *
246 * @param w the character stream
247 * @param op the code model
248 * @param options the writer options
249 */
250 public static void writeTo(Writer w, Op op, Option... options) {
251 OpWriter ow = new OpWriter(w, options);
252 ow.writeOp(op);
253 try {
254 // @@@ Is this needed?
255 w.flush();
256 } catch (IOException e) {
257 throw new UncheckedIOException(e);
258 }
259 }
260
261 /**
262 * Writes a code model (an operation) to a string.
263 *
264 * @param op the code model
265 * @param options the writer options
266 */
267 public static String toText(Op op, OpWriter.Option... options) {
268 StringWriter w = new StringWriter();
269 writeTo(w, op, options);
270 return w.toString();
271 }
272
273 /**
274 * An option that affects the writing operations.
275 */
276 public sealed interface Option {
277 }
278
279 /**
280 * An option describing the function to use for naming code items.
281 */
282 public sealed interface CodeItemNamerOption extends Option
283 permits NamerOptionImpl {
284
285 static CodeItemNamerOption of(Function<CodeItem, String> named) {
286 return new NamerOptionImpl(named);
287 }
288
289 static CodeItemNamerOption defaultValue() {
290 return of(new GlobalValueBlockNaming());
291 }
292
293 Function<CodeItem, String> namer();
294 }
295 private record NamerOptionImpl(Function<CodeItem, String> namer) implements CodeItemNamerOption {
296 }
297
298 /**
299 * An option describing whether location information should be written or dropped.
300 */
301 public enum LocationOption implements Option {
302 /** Writes location */
303 WRITE_LOCATION,
304 /** Drops location */
305 DROP_LOCATION;
306
307 public static LocationOption defaultValue() {
308 return WRITE_LOCATION;
309 }
310 }
311
312 /**
313 * An option describing whether an operation's descendant code elements should be written or dropped.
314 */
315 public enum OpDescendantsOption implements Option {
316 /** Writes descendants of an operation, if any */
317 WRITE_DESCENDANTS,
318 /** Drops descendants of an operation, if any */
319 DROP_DESCENDANTS;
320
321 public static OpDescendantsOption defaultValue() {
322 return WRITE_DESCENDANTS;
323 }
324 }
325
326 /**
327 * An option describing whether an operation's result be written or dropped if its type is void.
328 */
329 public enum VoidOpResultOption implements Option {
330 /** Writes void operation result */
331 WRITE_VOID,
332 /** Drops void operation result */
333 DROP_VOID;
334
335 public static VoidOpResultOption defaultValue() {
336 return DROP_VOID;
337 }
338 }
339
340 final Function<CodeItem, String> namer;
341 final IndentWriter w;
342 final boolean dropLocation;
343 final boolean dropOpDescendants;
344 final boolean writeVoidOpResult;
345
346 /**
347 * Creates a writer of code models (operations) to their textual form.
348 *
349 * @param w the character stream writer to write the textual form.
350 */
351 public OpWriter(Writer w) {
352 this.w = new IndentWriter(w);
353 this.namer = new GlobalValueBlockNaming();
354 this.dropLocation = false;
355 this.dropOpDescendants = false;
356 this.writeVoidOpResult = false;
357 }
358
359 /**
360 * Creates a writer of code models (operations) to their textual form.
361 *
362 * @param w the character stream writer to write the textual form.
363 * @param options the writer options
364 */
365 public OpWriter(Writer w, Option... options) {
366 Function<CodeItem, String> namer = null;
367 boolean dropLocation = false;
368 boolean dropOpDescendants = false;
369 boolean writeVoidOpResult = false;
370 for (Option option : options) {
371 switch (option) {
372 case CodeItemNamerOption namerOption -> {
373 namer = namerOption.namer();
374 }
375 case LocationOption locationOption -> {
376 dropLocation = locationOption ==
377 LocationOption.DROP_LOCATION;
378 }
379 case OpDescendantsOption opDescendantsOption -> {
380 dropOpDescendants = opDescendantsOption ==
381 OpDescendantsOption.DROP_DESCENDANTS;
382 }
383 case VoidOpResultOption voidOpResultOption -> {
384 writeVoidOpResult = voidOpResultOption == VoidOpResultOption.WRITE_VOID;
385 }
386 }
387 }
388
389 this.w = new IndentWriter(w);
390 this.namer = (namer == null) ? new GlobalValueBlockNaming() : namer;
391 this.dropLocation = dropLocation;
392 this.dropOpDescendants = dropOpDescendants;
393 this.writeVoidOpResult = writeVoidOpResult;
394 }
395
396 /**
397 * {@return the function that names blocks and values.}
398 */
399 public Function<CodeItem, String> namer() {
400 return namer;
401 }
402
403 /**
404 * Writes a code model, an operation, to the character stream.
405 *
406 * @param op the code model
407 */
408 public void writeOp(Op op) {
409 if (op.parent() != null) {
410 Op.Result opr = op.result();
411 if (writeVoidOpResult || !opr.type().equals(JavaType.VOID)) {
412 writeValueDeclaration(opr);
413 write(" = ");
414 }
415 }
416 write(op.externalizeOpName());
417
418 if (!op.operands().isEmpty()) {
419 write(" ");
420 writeSpaceSeparatedList(op.operands(), this::writeValueUse);
421 }
422
423 if (!op.successors().isEmpty()) {
424 write(" ");
425 writeSpaceSeparatedList(op.successors(), this::writeSuccessor);
426 }
427
428 if (!dropLocation) {
429 Location location = op.location();
430 if (location != null) {
431 write(" ");
432 writeAttribute(ATTRIBUTE_LOCATION, op.location());
433 }
434 }
435 Map<String, Object> attributes = op.externalize();
436 if (!attributes.isEmpty()) {
437 write(" ");
438 writeSpaceSeparatedList(attributes.entrySet(), e -> writeAttribute(e.getKey(), e.getValue()));
439 }
440
441 if (!dropOpDescendants && !op.bodies().isEmpty()) {
442 int nBodies = op.bodies().size();
443 if (nBodies == 1) {
444 write(" ");
445 } else {
446 write("\n");
447 w.in();
448 w.in();
449 }
450 boolean first = true;
451 for (Body body : op.bodies()) {
452 if (!first) {
453 write("\n");
454 }
455 writeBody(body);
456 first = false;
457 }
458 if (nBodies > 1) {
459 w.out();
460 w.out();
461 }
462 }
463
464 write(";");
465 }
466
467 void writeSuccessor(Block.Reference successor) {
468 writeBlockName(successor.targetBlock());
469 if (!successor.arguments().isEmpty()) {
470 write("(");
471 writeCommaSeparatedList(successor.arguments(), this::writeValueUse);
472 write(")");
473 }
474 }
475
476 void writeAttribute(String name, Object value) {
477 write("@");
478 if (!name.isEmpty()) {
479 write(name);
480 write("=");
481 }
482 write(AttributeMapper.toString(value));
483 }
484
485 void writeBody(Body body) {
486 Block eb = body.entryBlock();
487 write("(");
488 writeCommaSeparatedList(eb.parameters(), this::writeValueDeclaration);
489 write(")");
490 writeType(body.bodyType().returnType());
491 write(" -> {\n");
492 w.in();
493 for (Block b : body.blocks()) {
494 if (!b.isEntryBlock()) {
495 write("\n");
496 }
497 writeBlock(b);
498 }
499 w.out();
500 write("}");
501 }
502
503 void writeBlock(Block block) {
504 if (!block.isEntryBlock()) {
505 writeBlockName(block);
506 if (!block.parameters().isEmpty()) {
507 write("(");
508 writeCommaSeparatedList(block.parameters(), this::writeValueDeclaration);
509 write(")");
510 }
511 write(":\n");
512 }
513 w.in();
514 for (Op op : block.ops()) {
515 writeOp(op);
516 write("\n");
517 }
518 w.out();
519 }
520
521 void writeBlockName(Block b) {
522 write("^");
523 write(namer.apply(b));
524 }
525
526 void writeValueUse(Value v) {
527 write("%");
528 write(namer.apply(v));
529 }
530
531 void writeValueDeclaration(Value v) {
532 write("%");
533 write(namer.apply(v));
534 write(" : ");
535 writeType(v.type());
536 }
537
538 <T> void writeSpaceSeparatedList(Iterable<T> l, Consumer<T> c) {
539 writeSeparatedList(" ", l, c);
540 }
541
542 <T> void writeCommaSeparatedList(Iterable<T> l, Consumer<T> c) {
543 writeSeparatedList(", ", l, c);
544 }
545
546 <T> void writeSeparatedList(String separator, Iterable<T> l, Consumer<T> c) {
547 boolean first = true;
548 for (T t : l) {
549 if (!first) {
550 write(separator);
551 }
552 c.accept(t);
553 first = false;
554 }
555 }
556
557 void writeType(TypeElement te) {
558 write(JavaTypeUtils.flatten(te.externalize()).toString());
559 }
560
561 void write(String s) {
562 try {
563 w.write(s);
564 } catch (IOException e) {
565 throw new UncheckedIOException(e);
566 }
567 }
568 }