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 }