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