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.analysis;
 27 
 28 import jdk.incubator.code.*;
 29 import jdk.incubator.code.op.AnfDialect;
 30 import jdk.incubator.code.op.CoreOp;
 31 import jdk.incubator.code.type.FunctionType;
 32 import java.util.*;
 33 import java.util.function.Function;
 34 
 35 public class AnfTransformer {
 36 
 37 
 38     final CoreOp.FuncOp sourceOp;
 39     final Map<Block, Function<Body.Builder, AnfDialect.AnfFuncOp>> fBuilders = new HashMap<>();
 40     final Body.Builder outerBodyBuilder;
 41     final ImmediateDominatorMap idomMap;
 42     final Map<Block, Value> funMap = new HashMap<>();
 43     final Map<Block, Value> funMap2 = new HashMap<>();
 44 
 45     public AnfTransformer(CoreOp.FuncOp funcOp) {
 46         sourceOp = funcOp;
 47         outerBodyBuilder = Body.Builder.of(null, FunctionType.functionType(funcOp.body().yieldType()));
 48         idomMap = new ImmediateDominatorMap(funcOp.body());
 49     }
 50 
 51     public AnfDialect.AnfFuncOp transform() {
 52         return transformOuterBody(sourceOp.body());
 53     }
 54 
 55     //Outer body corresponds to outermost letrec
 56     public AnfDialect.AnfFuncOp transformOuterBody(Body b) {
 57         var entry = b.entryBlock();
 58 
 59         var builderEntry = outerBodyBuilder.entryBlock();
 60 
 61         var selfRefP = builderEntry.parameter(((CoreOp.FuncOp) b.parentOp()).invokableType());
 62         funMap.put(entry, selfRefP);
 63 
 64         for (Block.Parameter p : entry.parameters()) {
 65             var newP = builderEntry.parameter(p.type());
 66             builderEntry.context().mapValue(p,newP);
 67         }
 68 
 69         var outerLetRecBody = Body.Builder.of(outerBodyBuilder, FunctionType.functionType(b.yieldType(), List.of()), CopyContext.create(builderEntry.context()));
 70 
 71         List<Block> dominatedBlocks = idomMap.idominates(entry);
 72         List<AnfDialect.AnfFuncOp> funs = dominatedBlocks.stream().map(block -> transformBlock(block, outerLetRecBody)).toList();
 73 
 74         var res = transformBlock(entry, outerLetRecBody);
 75         return res;
 76 
 77     }
 78 
 79     public AnfDialect.AnfFuncOp transformBlock(Block b, Body.Builder bodyBuilder) {
 80         if (idomMap.idominates(b).isEmpty()) {
 81             return transformLeafBlock(b, bodyBuilder);
 82         }
 83         return transformDomBlock(b, bodyBuilder);
 84     }
 85 
 86     //"Leaf" in this case is a leaf of the dominator tree
 87     public AnfDialect.AnfFuncOp transformLeafBlock(Block b, Body.Builder ancestorBodyBuilder) {
 88         var blockReturnType = getBlockReturnType(b);
 89         var blockFType = FunctionType.functionType(blockReturnType);
 90 
 91         List<TypeElement> synthParamTypes = new ArrayList<>();
 92         synthParamTypes.add(blockFType);
 93 
 94         var blockFTypeSynth = FunctionType.functionType(blockReturnType, synthParamTypes);
 95 
 96         Body.Builder newBodyBuilder = Body.Builder.of(ancestorBodyBuilder, blockFTypeSynth, CopyContext.create(ancestorBodyBuilder.entryBlock().context()));
 97 
 98         var selfRefParam = newBodyBuilder.entryBlock().parameters().get(0);
 99         funMap.put(b, selfRefParam);
100 
101         for (Block.Parameter param : b.parameters()) {
102             var p = newBodyBuilder.entryBlock().parameter(param.type());
103             newBodyBuilder.entryBlock().context().mapValue(param, p);
104         }
105 
106         var letBody = Body.Builder.of(newBodyBuilder, FunctionType.functionType(blockReturnType, List.of()), CopyContext.create(newBodyBuilder.entryBlock().context()));
107 
108         AnfDialect.AnfLetOp let = transformOps(b, letBody);
109         newBodyBuilder.entryBlock().op(let);
110         return AnfDialect.func(b.toString(), newBodyBuilder);
111     }
112 
113     //Non leaf nodes of the dominator tree
114     public AnfDialect.AnfFuncOp transformDomBlock(Block b, Body.Builder ancestorBodyBuilder) {
115         var blockReturnType = getBlockReturnType(b);
116         var blockFType = FunctionType.functionType(blockReturnType);
117 
118         List<TypeElement> synthParamTypes = new ArrayList<>();
119         synthParamTypes.add(blockFType);
120 
121         var blockFTypeSynth = FunctionType.functionType(blockReturnType, synthParamTypes);
122 
123         //Function body contains letrec and its bodies
124         Body.Builder funcBodyBuilder = Body.Builder.of(ancestorBodyBuilder, blockFTypeSynth, CopyContext.create(ancestorBodyBuilder.entryBlock().context()));
125 
126         //Self param
127         var selfRefParam = funcBodyBuilder.entryBlock().parameters().get(0);
128         funMap.put(b, selfRefParam);
129 
130         for (Block.Parameter param : b.parameters()) {
131             var p = funcBodyBuilder.entryBlock().parameter(param.type());
132             funcBodyBuilder.entryBlock().context().mapValue(param, p);
133         }
134 
135         //letrec inner body
136         Body.Builder letrecBody = Body.Builder.of(funcBodyBuilder, FunctionType.functionType(blockReturnType, List.of()), CopyContext.create(funcBodyBuilder.entryBlock().context()));
137 
138         List<Block> dominates = idomMap.idominates(b);
139         for (Block dblock : dominates) {
140             var res = transformDomBlock(dblock, letrecBody);
141             var fval = letrecBody.entryBlock().op(res);
142             funMap2.put(dblock, fval);
143         }
144 
145         var letBody = Body.Builder.of(letrecBody, letrecBody.bodyType(), CopyContext.create(letrecBody.entryBlock().context()));
146         transformBlockOps(b, letBody.entryBlock());
147         var let = AnfDialect.let(letBody);
148 
149         letrecBody.entryBlock().op(let);
150 
151         var letrec = AnfDialect.letrec(letrecBody);
152         funcBodyBuilder.entryBlock().op(letrec);
153         return AnfDialect.func(b.toString(), funcBodyBuilder);
154 
155     }
156 
157     private TypeElement getBlockReturnType(Block b) {
158         var op = b.ops().getLast();
159         if (op instanceof Op.Terminating) {
160             List<Block.Reference> destBlocks = new ArrayList<>();
161             if (op instanceof CoreOp.ReturnOp ro) {
162                 return ro.returnValue().type();
163             } else if (op instanceof CoreOp.YieldOp yo) {
164                 return yo.yieldValue().type();
165             } else if (op instanceof CoreOp.BranchOp bop) {
166                 destBlocks.addAll(bop.successors());
167             } else if (op instanceof CoreOp.ConditionalBranchOp cbop) {
168                 destBlocks.addAll(cbop.successors());
169             }
170             //Traverse until we find a yield or return type, TODO: not going to try to unify types
171 
172             Set<Block> visitedBlocks = new HashSet<>();
173             visitedBlocks.add(b);
174 
175             while (!destBlocks.isEmpty()) {
176                 var block = destBlocks.removeFirst().targetBlock();
177                 if (visitedBlocks.contains(block)) {
178                     continue;
179                 }
180 
181                 //Discovered a terminator with a return value, use its type
182                 if (block.successors().isEmpty()) {
183                     var o = block.ops().getLast();
184                     if (o instanceof CoreOp.ReturnOp ro) {
185                         return ro.returnValue().type();
186                     } else if (o instanceof CoreOp.YieldOp yo) {
187                         return yo.yieldValue().type();
188                     } else {
189                         throw new UnsupportedOperationException("Unsupported terminator encountered: " + o.opName());
190                     }
191                 } else {
192                     visitedBlocks.add(block);
193                     var newDests = block.successors().stream().filter((s) -> !visitedBlocks.contains(s.targetBlock())).toList();
194                     destBlocks.addAll(newDests);
195                 }
196             }
197 
198         }
199 
200         throw new RuntimeException("Encountered Block with no return " + op.opName());
201     }
202 
203     private Block.Builder transformEndOp(Block.Builder b, Op op) {
204         if (op instanceof Op.Terminating t) {
205             switch (t) {
206                 case CoreOp.ConditionalBranchOp c -> {
207                     var tbranch_args = c.trueBranch().arguments();
208                     tbranch_args = tbranch_args.stream().map(b.context()::getValue).toList();
209                     var fbranch_args = c.falseBranch().arguments();
210                     fbranch_args = fbranch_args.stream().map(b.context()::getValue).toList();
211 
212                     List<Value> trueArgs = new ArrayList<>();
213                     trueArgs.addAll(tbranch_args);
214 
215                     List<Value> falseArgs = new ArrayList<>();
216                     falseArgs.addAll(fbranch_args);
217 
218 
219                     var ifExp = AnfDialect.if_(b.parentBody(),
220                                     getBlockReturnType(c.trueBranch().targetBlock()),
221                                     b.context().getValue(c.predicate()))
222                             .if_((bodyBuilder) -> bindFunApp(bodyBuilder, trueArgs, c.trueBranch().targetBlock()))
223                             .else_((bodyBuilder) -> bindFunApp(bodyBuilder, falseArgs, c.falseBranch().targetBlock()));
224 
225                     b.op(ifExp);
226 
227                     return b;
228                 }
229                 case CoreOp.BranchOp br -> {
230                     var args = br.branch().arguments();
231                     args = args.stream().map(b.context()::getValue).toList();
232 
233                     List<Value> funcArgs = new ArrayList<>();
234                     funcArgs.addAll(args);
235                     bindFunApp(b, funcArgs, br.branch().targetBlock());
236 
237                     return b;
238                 }
239                 case CoreOp.ReturnOp ro -> {
240                     var rval = b.context().getValue(ro.returnValue());
241                     b.op(CoreOp._yield(rval));
242                     return b;
243                 }
244                 case CoreOp.YieldOp y ->  {
245                     var rval = b.context().getValue(y.yieldValue());
246                     b.op(CoreOp._yield(rval));
247                     return b;
248                 }
249                 default -> {
250                     throw new UnsupportedOperationException("Unsupported terminating op encountered: " + op);
251                 }
252             }
253         } else {
254             b.op(op);
255             return b;
256         }
257     }
258 
259 
260     private void bindFunApp(Block.Builder b, List<Value> args, Block target) {
261 
262         List<Value> synthArgs = new ArrayList<>();
263         synthArgs.addAll(args);
264         synthArgs.addFirst(funMap.get(target));
265         try {
266             b.op(AnfDialect.apply(synthArgs));
267             return;
268         } catch (IllegalStateException e) {}
269 
270         synthArgs.removeFirst();
271         synthArgs.addFirst(funMap2.get(target));
272 
273         try {
274             b.op(AnfDialect.apply(synthArgs));
275         } catch (IllegalStateException e) {
276             throw new IllegalStateException("No valid mapping to FuncOp for apply");
277         }
278 
279     }
280 
281 
282     public AnfDialect.AnfLetOp transformOps(Block b, Body.Builder bodyBuilder) {
283         Block.Builder blockb = bodyBuilder.entryBlock();
284         return transformOps(b, blockb);
285     }
286 
287     public AnfDialect.AnfLetOp transformOps(Block b, Block.Builder blockBuilder) {
288         transformBlockOps(b, blockBuilder);
289         return AnfDialect.let(blockBuilder.parentBody());
290     }
291 
292     public void transformBlockOps(Block b, Block.Builder blockBuilder) {
293         for (var op : b.ops()) {
294             transformEndOp(blockBuilder, op);
295         }
296     }
297 
298     static class ImmediateDominatorMap {
299 
300         private final Map <Block, List<Block>> dominatesMap;
301         private final Map <Block, Block> dominatorsMap;
302 
303         public ImmediateDominatorMap(Body b) {
304             dominatorsMap = b.immediateDominators();
305             dominatesMap = new HashMap<>();
306 
307             //Reverse the idom relation
308             b.immediateDominators().forEach((dominated, dominator) -> {
309                 if (!dominated.equals(dominator)) {
310                     dominatesMap.compute(dominator, (k, v) -> {
311                         if (v == null) {
312                             var newList = new ArrayList<Block>();
313                             newList.add(dominated);
314                             return newList;
315                         } else {
316                             v.add(dominated);
317                             return v;
318                         }
319                     });
320                 }
321             });
322 
323         }
324 
325         //Looks "down" the dominator tree toward leaves
326         public List<Block> idominates(Block b) {
327             return dominatesMap.getOrDefault(b, List.of());
328         }
329 
330         //Looks "up" the dominator tree toward start node
331         public Block idominatedBy(Block b) {
332             return dominatorsMap.get(b);
333         }
334     }
335 }