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 }