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