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