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