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