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 }