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.Map;
 17 import java.util.function.IntUnaryOperator;
 18 
 19 /*
 20  * @test
 21  * @modules jdk.incubator.code
 22  * @run testng TestQuoteOp
 23  */
 24 public class TestQuoteOp {
 25 
 26     @CodeReflection
 27     public void f(int i) {
 28         String s = "abc";
 29         Runnable r = () -> {
 30             System.out.println(i + s + hashCode());
 31         };
 32     }
 33 
 34     @Test
 35     void testQuoteOpThatHasCaptures() throws NoSuchMethodException {
 36         Method f = getClass().getDeclaredMethod("f", int.class);
 37         CoreOp.FuncOp fm = Op.ofMethod(f).orElseThrow();
 38         Op lop = fm.body().entryBlock().ops().stream().filter(op -> op instanceof JavaOp.LambdaOp).findFirst().orElseThrow();
 39 
 40         CoreOp.FuncOp funcOp = Quoted.quoteOp(lop);
 41 
 42         Object[] args = new Object[]{1, "a", this};
 43         Quoted quoted = Quoted.quotedOp(funcOp, args);
 44         // op must have the same structure as lop
 45         // for the moment, we don't have utility to check that
 46 
 47         Assert.assertTrue(lop.getClass().isInstance(quoted.op()));
 48 
 49         Iterator<Object> iterator = quoted.capturedValues().values().iterator();
 50 
 51         Assert.assertEquals(((CoreOp.Var) iterator.next()).value(), args[0]);
 52         Assert.assertEquals(((CoreOp.Var) iterator.next()).value(), args[1]);
 53         Assert.assertEquals(iterator.next(), args[2]);
 54     }
 55 
 56     @CodeReflection
 57     static void g(String s) {
 58         boolean b = s.startsWith("a");
 59     }
 60 
 61     @Test
 62     void testQuoteOpThatHasOperands() throws NoSuchMethodException { // op with operands
 63         Method g = getClass().getDeclaredMethod("g", String.class);
 64         CoreOp.FuncOp gm = Op.ofMethod(g).orElseThrow();
 65         Op invOp = gm.body().entryBlock().ops().stream().filter(o -> o instanceof JavaOp.InvokeOp).findFirst().orElseThrow();
 66 
 67         CoreOp.FuncOp funcOp = Quoted.quoteOp(invOp);
 68 
 69         Object[] args = {"abc", "b"};
 70         Quoted quoted = Quoted.quotedOp(funcOp, args);
 71 
 72         Assert.assertTrue(invOp.getClass().isInstance(quoted.op()));
 73 
 74         Iterator<Object> iterator = quoted.capturedValues().values().iterator();
 75 
 76         Assert.assertEquals(iterator.next(), args[0]);
 77         Assert.assertEquals(iterator.next(), args[1]);
 78     }
 79 
 80     @Test
 81     void testWithJavacModel() {
 82         final int y = 88;
 83         int z = 99;
 84         Quotable q = (IntUnaryOperator & Quotable) x -> x + y + z + hashCode();
 85 
 86         // access FuncOp created by javac
 87         Quoted quoted = Op.ofQuotable(q).orElseThrow();
 88         Op op = quoted.op();
 89         CoreOp.QuotedOp qop = ((CoreOp.QuotedOp) op.ancestorBody().parentOp());
 90         CoreOp.FuncOp fop = ((CoreOp.FuncOp) qop.ancestorBody().parentOp());
 91 
 92         Object[] args = {this, 111};
 93         Quoted quoted2 = Quoted.quotedOp(fop, args);
 94 
 95         Iterator<Object> iterator = quoted2.capturedValues().values().iterator();
 96 
 97         Assert.assertEquals(((CoreOp.Var) iterator.next()).value(), y);
 98         Assert.assertEquals(((CoreOp.Var) iterator.next()).value(), args[1]);
 99         Assert.assertEquals(iterator.next(), args[0]);
100     }
101 
102     @DataProvider
103     Object[][] invalidCases() {
104         return new Object[][]{
105               // TODO describe error in a comment
106                 {
107                         // func op must have one block
108                         """
109 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
110     branch ^block_1;
111 
112   ^block_1:
113     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
114         %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
115             return;
116         };
117         yield %6;
118     };
119     return %5;
120 };
121 """, new Object[]{}
122                 },
123                 {
124               // before last op must be QuotedOp
125               """
126 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
127     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
128       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
129           return;
130       };
131       yield %6;
132     };
133     %0 : java.type:"boolean" = constant @false;
134     return %5;
135 };
136 """, new Object[]{}
137                 },
138                 {
139                         // last op must be ReturnOp
140                         """
141 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
142     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
143       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
144           return;
145       };
146       yield %6;
147     };
148     yield %5;
149 };
150 """, new Object[]{}
151                 },
152                 {
153                         // the result of QuotedOp must be returned
154                         """
155 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
156     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
157       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
158           return;
159       };
160       yield %6;
161     };
162     return;
163 };
164 """, new Object[]{}
165                 },
166                 {
167                         // the result of QuotedOp must be returned
168                         """
169 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
170     %0 : java.type:"int" = constant @1;
171     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
172       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
173           return;
174       };
175       yield %6;
176     };
177     return %0;
178 };
179 """, new Object[]{}
180                 },
181                 {
182                         // param must be used
183                         """
184 func @"q" (%0 : java.type:"Object")java.type:"jdk.incubator.code.Quoted" -> {
185     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
186       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
187           return;
188       };
189       yield %6;
190     };
191     return %5;
192 };
193 """, new Object[]{"s"}
194                 },
195                 {
196                         // param used more than once, all uses must be as operand or capture of quoted op
197                         """
198 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
199     %2 : Var<java.type:"int"> = var %0 @"y";
200     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
201         %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
202             return %0;
203         };
204         yield %6;
205     };
206     return %5;
207 };
208 """, new Object[]{1}
209                 },
210                 {
211                         // operations before quoted op must be ConstantOp or VarOp
212                         """
213 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
214     %0 : java.type:"java.lang.String" = new @java.ref:"java.lang.String::()";
215     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
216       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
217            %7 : java.type:"int" = invoke %0 @java.ref:"java.lang.String::length():int";
218            return %7;
219       };
220       yield %6;
221     };
222     return %5;
223 };
224 """, new Object[]{}
225                 },
226                 {
227                         // constant op must be used
228                         """
229 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
230     %0 : java.type:"int" = constant @1;
231     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
232       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
233           return;
234       };
235       yield %6;
236     };
237     return %5;
238 };
239 """, new Object[]{}
240                 },
241                 {
242                         // constant used more than once, all its uses must be as operand or capture of quoted op
243                         """
244 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
245     %0 : java.type:"int" = constant @1;
246     %1 : Var<java.type:"int"> = var %0 @"y";
247     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
248       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
249             %7 : java.type:"int" = var.load %1;
250             %8 : java.type:"int" = add %0 %7;
251             return %8;
252       };
253       yield %6;
254     };
255     return %5;
256 };
257 """, new Object[]{}
258                 },
259                 {
260                         // var op must be initialized with param or result of constant op
261                         """
262 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
263     %1 : Var<java.type:"int"> = var @"y";
264     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
265       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
266             %7 : java.type:"int" = var.load %1;
267             return %7;
268       };
269       yield %6;
270     };
271     return %5;
272 };
273 """, new Object[]{}
274                 },
275                 {
276                         // model must contain at least two operations
277                         """
278 func @"q" (%5 : java.type:"jdk.incubator.code.Quoted")java.type:"jdk.incubator.code.Quoted" -> {
279     return %5;
280 };
281 """, new Object[]{null}
282                 },
283                 // args length must be equal to params size
284                 {
285                         """
286 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
287     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
288       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
289             return %0;
290       };
291       yield %6;
292     };
293     return %5;
294 };
295 """, new Object[]{1, 2}
296                 }
297       };
298 }
299 
300 
301     @Test(dataProvider = "invalidCases")
302     void testInvalidCases(String model, Object[] args) {
303         CoreOp.FuncOp fop = ((CoreOp.FuncOp) OpParser.fromStringOfJavaCodeModel(model));
304         Assert.assertThrows(RuntimeException.class, () -> Quoted.quotedOp(fop, args));
305     }
306 
307     @DataProvider
308     Object[][] validCases() {
309         return new Object[][] {
310                 {
311                         """
312 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
313     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
314       %6 : java.type:"java.lang.Runnable" = lambda ()java.type:"void" -> {
315           return;
316       };
317       yield %6;
318     };
319     return %5;
320 };
321 """, new Object[] {}
322                 },
323                 {
324                         """
325 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
326     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
327       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
328             return %0;
329       };
330       yield %6;
331     };
332     return %5;
333 };
334 """, new Object[] {1}
335                 },
336                 {
337                         """
338 func @"q" (%0 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
339     %1 : Var<java.type:"int"> = var %0;
340     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
341       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
342             %7 : java.type:"int" = var.load %1;
343             return %7;
344       };
345       yield %6;
346     };
347     return %5;
348 };
349 """, new Object[] {2}
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             %7 : java.type:"int" = add %0 %0;
357             %8 : java.type:"int" = mul %0 %0;
358             %9 : java.type:"int" = sub %8 %7;
359             return %9;
360       };
361       yield %6;
362     };
363     return %5;
364 };
365 """, new Object[] {3}
366                 },
367                 {
368                         """
369 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
370     %0 : java.type:"int" = constant @1;
371     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
372       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
373             return %0;
374       };
375       yield %6;
376     };
377     return %5;
378 };
379 """, new Object[] {}
380                 },
381                 {
382                         """
383 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
384     %0 : java.type:"int" = constant @1;
385     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
386       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
387             %7 : java.type:"int" = add %0 %0;
388             %8 : java.type:"int" = mul %0 %0;
389             %9 : java.type:"int" = sub %8 %7;
390             return %9;
391       };
392       yield %6;
393     };
394     return %5;
395 };
396 """, new Object[] {}
397                 },
398                 {
399                         """
400 func @"q" ()java.type:"jdk.incubator.code.Quoted" -> {
401     %0 : java.type:"int" = constant @1;
402     %1 : Var<java.type:"int"> = var %0;
403     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
404       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
405             %7 : java.type:"int" = var.load %1;
406             %8 : java.type:"int" = mul %7 %7;
407             return %8;
408       };
409       yield %6;
410     };
411     return %5;
412 };
413 """, new Object[] {}
414                 },
415                 {
416                         """
417 func @"q" (%0 : java.type:"int", %2 : java.type:"int")java.type:"jdk.incubator.code.Quoted" -> {
418     %1 : Var<java.type:"int"> = var %0;
419     %5 : java.type:"jdk.incubator.code.Quoted" = quoted ()java.type:"void" -> {
420       %6 : java.type:"java.util.function.IntSupplier" = lambda ()java.type:"int" -> {
421             %7 : java.type:"int" = var.load %1;
422             %8 : java.type:"int" = add %7 %2;
423             return %8;
424       };
425       yield %6;
426     };
427     return %5;
428 };
429 """, new Object[]{8, 9}
430                 }
431         };
432     }
433 
434     @Test(dataProvider = "validCases")
435     void testValidCases(String model, Object[] args) {
436         CoreOp.FuncOp fop = ((CoreOp.FuncOp) OpParser.fromStringOfJavaCodeModel(model));
437         Quoted quoted = Quoted.quotedOp(fop, args);
438 
439         for (Map.Entry<Value, Object> e : quoted.capturedValues().entrySet()) {
440             Value sv = e.getKey();
441             Object rv = e.getValue();
442             // assert only when captured value is block param, or result of VarOp initialized with block param
443             if (sv instanceof Op.Result opr && opr.op() instanceof CoreOp.VarOp vop
444                     && vop.initOperand() instanceof Block.Parameter p) {
445                 Assert.assertEquals(((CoreOp.Var) rv).value(), args[p.index()]);
446             } else if (sv instanceof Block.Parameter p) {
447                 Assert.assertEquals(rv, args[p.index()]);
448             }
449         }
450     }
451 }