1 import jdk.incubator.code.Block;
  2 import jdk.incubator.code.CodeReflection;
  3 import jdk.incubator.code.Op;
  4 import jdk.incubator.code.Quotable;
  5 import jdk.incubator.code.Quoted;
  6 import jdk.incubator.code.Value;
  7 import jdk.incubator.code.dialect.core.CoreOp;
  8 import jdk.incubator.code.dialect.java.JavaOp;
  9 import jdk.incubator.code.extern.OpParser;
 10 import org.testng.Assert;
 11 import org.testng.annotations.DataProvider;
 12 import org.testng.annotations.Test;
 13 
 14 import java.lang.reflect.Method;
 15 import java.util.Iterator;
 16 import java.util.LinkedHashMap;
 17 import java.util.LinkedHashSet;
 18 import java.util.Map;
 19 import java.util.SequencedMap;
 20 import java.util.SequencedSet;
 21 import java.util.function.IntUnaryOperator;
 22 
 23 /*
 24  * @test
 25  * @modules jdk.incubator.code
 26  * @run testng TestQuoteOp
 27  */
 28 public class TestQuoteOp {
 29 
 30     @CodeReflection
 31     public void f(int i) {
 32         String s = "abc";
 33         Runnable r = () -> {
 34             System.out.println(i + s + hashCode());
 35         };
 36     }
 37 
 38     @Test
 39     void testQuoteOpThatHasCaptures() throws NoSuchMethodException {
 40         Method f = getClass().getDeclaredMethod("f", int.class);
 41         CoreOp.FuncOp fm = Op.ofMethod(f).orElseThrow();
 42         Op lop = fm.body().entryBlock().ops().stream().filter(op -> op instanceof JavaOp.LambdaOp).findFirst().orElseThrow();
 43 
 44         CoreOp.FuncOp funcOp = Quoted.embedOp(lop);
 45 
 46         Object[] args = new Object[]{1, "a", this};
 47         Quoted quoted = Quoted.extractOp(funcOp, args);
 48         // op must have the same structure as lop
 49         // for the moment, we don't have utility to check that
 50 
 51         Assert.assertTrue(lop.getClass().isInstance(quoted.op()));
 52 
 53         Iterator<Object> iterator = quoted.capturedValues().values().iterator();
 54 
 55         Assert.assertEquals(((CoreOp.Var) iterator.next()).value(), args[0]);
 56         Assert.assertEquals(((CoreOp.Var) iterator.next()).value(), args[1]);
 57         Assert.assertEquals(iterator.next(), args[2]);
 58     }
 59 
 60     @CodeReflection
 61     static void g(String s) {
 62         boolean b = s.startsWith("a");
 63     }
 64 
 65     @Test
 66     void testQuoteOpThatHasOperands() throws NoSuchMethodException { // op with operands
 67         Method g = getClass().getDeclaredMethod("g", String.class);
 68         CoreOp.FuncOp gm = Op.ofMethod(g).orElseThrow();
 69         Op invOp = gm.body().entryBlock().ops().stream().filter(o -> o instanceof JavaOp.InvokeOp).findFirst().orElseThrow();
 70 
 71         CoreOp.FuncOp funcOp = Quoted.embedOp(invOp);
 72 
 73         Object[] args = {"abc", "b"};
 74         Quoted quoted = Quoted.extractOp(funcOp, args);
 75 
 76         Assert.assertTrue(invOp.getClass().isInstance(quoted.op()));
 77 
 78         Iterator<Object> iterator = quoted.operands().values().iterator();
 79 
 80         Assert.assertEquals(iterator.next(), args[0]);
 81         Assert.assertEquals(iterator.next(), args[1]);
 82     }
 83 
 84     @Test
 85     void testWithJavacModel() {
 86         final int y = 88;
 87         int z = 99;
 88         Quotable q = (IntUnaryOperator & Quotable) x -> x + y + z + hashCode();
 89 
 90         // access FuncOp created by javac
 91         Quoted quoted = Op.ofQuotable(q).orElseThrow();
 92         Op op = quoted.op();
 93         CoreOp.QuotedOp qop = ((CoreOp.QuotedOp) op.ancestorOp());
 94         CoreOp.FuncOp fop = ((CoreOp.FuncOp) qop.ancestorOp());
 95 
 96         Object[] args = {this, 111};
 97         Quoted quoted2 = Quoted.extractOp(fop, args);
 98 
 99         Iterator<Object> iterator = quoted2.capturedValues().values().iterator();
100 
101         Assert.assertEquals(((CoreOp.Var) iterator.next()).value(), y);
102         Assert.assertEquals(((CoreOp.Var) iterator.next()).value(), args[1]);
103         Assert.assertEquals(iterator.next(), args[0]);
104     }
105 
106     @DataProvider
107     Object[][] invalidCases() {
108         return new Object[][]{
109               // TODO describe error in a comment
110                 {
111                         // func op must have one block
112                         """
113 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
114     branch ^block_1;
115 
116   ^block_1:
117     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
118         %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
119             return;
120         };
121         yield %6;
122     };
123     return %5;
124 };
125 """, new Object[]{}
126                 },
127                 {
128               // before last op must be QuotedOp
129               """
130 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
131     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
132       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
133           return;
134       };
135       yield %6;
136     };
137     %0 : java.type:"boolean" = constant @false;
138     return %5;
139 };
140 """, new Object[]{}
141                 },
142                 {
143                         // last op must be ReturnOp
144                         """
145 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
146     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
147       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
148           return;
149       };
150       yield %6;
151     };
152     yield %5;
153 };
154 """, new Object[]{}
155                 },
156                 {
157                         // the result of QuotedOp must be returned
158                         """
159 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
160     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
161       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
162           return;
163       };
164       yield %6;
165     };
166     return;
167 };
168 """, new Object[]{}
169                 },
170                 {
171                         // the result of QuotedOp must be returned
172                         """
173 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
174     %0 : java.type:"int" = constant @1;
175     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
176       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
177           return;
178       };
179       yield %6;
180     };
181     return %0;
182 };
183 """, new Object[]{}
184                 },
185                 {
186                         // param must be used
187                         """
188 func @"q" (%0 : java.type:"Object")java.type:"jdk.incubator.code.Quoted" -> {
189     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
190       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
191           return;
192       };
193       yield %6;
194     };
195     return %5;
196 };
197 """, new Object[]{"s"}
198                 },
199                 {
200                         // param used more than once, all uses must be as operand or capture of quoted op
201                         """
202 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
203     %2 : Var<java.type:"int"> = var %0 @"y";
204     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
205         %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
206             return %0;
207         };
208         yield %6;
209     };
210     return %5;
211 };
212 """, new Object[]{1}
213                 },
214                 {
215                         // param used once by a VarOp, the VarOp must be used
216                         """
217 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
218     %2 : Var<java.type:"int"> = var %0 @"y";
219     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
220         %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
221             return;
222         };
223         yield %6;
224     };
225     return %5;
226 };
227 """, new Object[]{2}
228                 },
229                 {
230                         // param used once by a VarOp, the VarOp must be used as operand or capture
231                         """
232 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
233     %1 : Var<java.type:"int"> = var %0 @"y";
234     %2 : Var<Var<java.type:"int">> = var %1 @"z";
235     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
236         %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
237             return;
238         };
239         yield %6;
240     };
241     return %5;
242 };
243 """, new Object[]{3}
244                 },
245                 {
246                         // operations before quoted op must be ConstantOp or VarOp
247                         """
248 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
249     %0 : java.type:"java.lang.String" = new @java.ref:"java.lang.String::()";
250     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
251       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
252            %7 : java.type:"int" = invoke %0 @java.ref:"java.lang.String::length():int";
253            return %7;
254       };
255       yield %6;
256     };
257     return %5;
258 };
259 """, new Object[]{}
260                 },
261                 {
262                         // constant op must be used
263                         """
264 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
265     %0 : java.type:"int" = constant @1;
266     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
267       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
268           return;
269       };
270       yield %6;
271     };
272     return %5;
273 };
274 """, new Object[]{}
275                 },
276                 {
277                         // constant used more than once, all its uses must be as operand or capture of quoted op
278                         """
279 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
280     %0 : java.type:"int" = constant @1;
281     %1 : Var<java.type:"int"> = var %0 @"y";
282     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
283       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
284             %7 : java.type:"int" = var.load %1;
285             %8 : java.type:"int" = add %0 %7;
286             return %8;
287       };
288       yield %6;
289     };
290     return %5;
291 };
292 """, new Object[]{}
293                 },
294                 {
295                         // var op must be initialized with param or result of constant op
296                         """
297 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
298     %1 : Var<java.type:"int"> = var @"y";
299     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
300       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
301             %7 : java.type:"int" = var.load %1;
302             return %7;
303       };
304       yield %6;
305     };
306     return %5;
307 };
308 """, new Object[]{}
309                 },
310                 {
311                         // model must contain at least two operations
312                         """
313 func @"q" (%5 : java.type:"jdk.incubator.code.Quoted")java.type:"jdk.incubator.code.Quoted" -> {
314     return %5;
315 };
316 """, new Object[]{null}
317                 },
318                 // args length must be equal to params size
319                 {
320                         """
321 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
322     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
323       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
324             return %0;
325       };
326       yield %6;
327     };
328     return %5;
329 };
330 """, new Object[]{1, 2}
331                 }
332       };
333 }
334 
335 
336     @Test(dataProvider = "invalidCases")
337     void testInvalidCases(String model, Object[] args) {
338         CoreOp.FuncOp fop = ((CoreOp.FuncOp) OpParser.fromStringOfJavaCodeModel(model));
339         Assert.assertThrows(RuntimeException.class, () -> Quoted.extractOp(fop, args));
340     }
341 
342     @DataProvider
343     Object[][] validCases() {
344         return new Object[][] {
345                 {
346                         """
347 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
348     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
349       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
350           return;
351       };
352       yield %6;
353     };
354     return %5;
355 };
356 """, new Object[] {}
357                 },
358                 {
359                         """
360 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
361     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
362       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
363             return %0;
364       };
365       yield %6;
366     };
367     return %5;
368 };
369 """, new Object[] {1}
370                 },
371                 {
372                         """
373 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
374     %1 : Var<java.type:"int"> = var %0;
375     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
376       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
377             %7 : java.type:"int" = var.load %1;
378             return %7;
379       };
380       yield %6;
381     };
382     return %5;
383 };
384 """, new Object[] {2}
385                 },
386                 {
387                         """
388 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
389     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
390       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
391             %7 : java.type:"int" = add %0 %0;
392             %8 : java.type:"int" = mul %0 %0;
393             %9 : java.type:"int" = sub %8 %7;
394             return %9;
395       };
396       yield %6;
397     };
398     return %5;
399 };
400 """, new Object[] {3}
401                 },
402                 {
403                         """
404 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
405     %0 : java.type:"int" = constant @1;
406     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
407       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
408             return %0;
409       };
410       yield %6;
411     };
412     return %5;
413 };
414 """, new Object[] {}
415                 },
416                 {
417                         """
418 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
419     %0 : java.type:"int" = constant @1;
420     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
421       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
422             %7 : java.type:"int" = add %0 %0;
423             %8 : java.type:"int" = mul %0 %0;
424             %9 : java.type:"int" = sub %8 %7;
425             return %9;
426       };
427       yield %6;
428     };
429     return %5;
430 };
431 """, new Object[] {}
432                 },
433                 {
434                         """
435 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
436     %0 : java.type:"int" = constant @1;
437     %1 : Var<java.type:"int"> = var %0;
438     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
439       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
440             %7 : java.type:"int" = var.load %1;
441             %8 : java.type:"int" = mul %7 %7;
442             return %8;
443       };
444       yield %6;
445     };
446     return %5;
447 };
448 """, new Object[] {}
449                 },
450                 {
451                         """
452 func @"q" (%0 : java.type:"int", %2 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
453     %1 : Var<java.type:"int"> = var %0;
454     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
455       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
456             %7 : java.type:"int" = var.load %1;
457             %8 : java.type:"int" = add %7 %2;
458             return %8;
459       };
460       yield %6;
461     };
462     return %5;
463 };
464 """, new Object[]{8, 9}
465                 }
466         };
467     }
468 
469     @Test(dataProvider = "validCases")
470     void testValidCases(String model, Object[] args) {
471         CoreOp.FuncOp fop = ((CoreOp.FuncOp) OpParser.fromStringOfJavaCodeModel(model));
472         Quoted quoted = Quoted.extractOp(fop, args);
473 
474         for (Map.Entry<Value, Object> e : quoted.capturedValues().entrySet()) {
475             Value sv = e.getKey();
476             Object rv = e.getValue();
477             // assert only when captured value is block param, or result of VarOp initialized with block param
478             if (sv instanceof Op.Result opr && opr.op() instanceof CoreOp.VarOp vop
479                     && vop.initOperand() instanceof Block.Parameter p) {
480                 Assert.assertEquals(((CoreOp.Var) rv).value(), args[p.index()]);
481             } else if (sv instanceof Block.Parameter p) {
482                 Assert.assertEquals(rv, args[p.index()]);
483             }
484         }
485     }
486 
487     @DataProvider
488     Object[][] numParamsCases() {
489         return new Object[][]{
490                 {
491                         """
492                 func @"f" (%0 : java.type:"int", %1 : java.type:"int")java.type:"void" -> {
493                       %4 : java.type:"int" = add %0 %1;
494                       return;
495                   };
496                 """, 2
497                 },
498                 {
499                         """
500                 func @"f" (%0 : java.type:"int")java.type:"void" -> {
501                       %4 : java.type:"int" = add %0 %0;
502                       return;
503                   };
504                 """, 1
505                 },
506                 {
507                         """
508                 func @"f" (%0 : java.type:"int")java.type:"void" -> {
509                       %3 : java.type:"java.lang.String" = java.switch.expression %0
510                           ()java.type:"boolean" -> {
511                               %4 : java.type:"boolean" = constant @true;
512                               yield %4;
513                           }
514                           ()java.type:"java.lang.String" -> {
515                               %5 : java.type:"java.lang.String" = constant @"x = ";
516                               %7 : java.type:"java.lang.String" = concat %5 %0;
517                               yield %7;
518                           };
519                       return;
520                   };
521                 """, 1
522                 },
523                 {
524                         """
525                 func @"f" (%0 : java.type:"int", %1 : java.type:"java.lang.String")java.type:"void" -> {
526                       %3 : java.type:"java.lang.String" = java.switch.expression %0
527                           ()java.type:"boolean" -> {
528                               %4 : java.type:"boolean" = constant @true;
529                               yield %4;
530                           }
531                           ()java.type:"java.lang.String" -> {
532                               %5 : java.type:"java.lang.String" = constant @"x = ";
533                               %7 : java.type:"java.lang.String" = concat %5 %1;
534                               yield %7;
535                           };
536                       return;
537                   };
538                 """, 2
539                 }
540         };
541     }
542 
543     @Test(dataProvider = "numParamsCases")
544     void testNumAndOrderOfParams(String model, int expectedNumParams) {
545         CoreOp.FuncOp funcOp = (CoreOp.FuncOp) OpParser.fromString(JavaOp.JAVA_DIALECT_FACTORY, model).get(0);
546         CoreOp.FuncOp qm = Quoted.embedOp(funcOp.body().entryBlock().ops().getFirst());
547         Assert.assertEquals(qm.parameters().size(), expectedNumParams);
548 
549         // test that qm parameters are the sequence set of op 's operands + captured values
550         CoreOp.QuotedOp qop = ((CoreOp.QuotedOp) qm.body().entryBlock().ops().get(qm.body().entryBlock().ops().size() - 2));
551         Op op = qop.quotedOp();
552         SequencedSet<Value> expectedParams = new LinkedHashSet<>();
553         expectedParams.addAll(op.operands());
554         expectedParams.addAll(op.capturedValues());
555         Assert.assertEquals(qm.parameters(), expectedParams);
556 
557         // test that validation in Quoted constructor are correct
558         SequencedMap<Value, Object> m = new LinkedHashMap<>();
559         for (Value p : expectedParams) {
560             m.put(p, new Object());
561         }
562         new Quoted(op, m);
563     }
564 }