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