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