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