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