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