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 }