1 /* 2 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.incubator.code.interpreter; 27 28 import java.lang.invoke.*; 29 import java.lang.reflect.Array; 30 import java.lang.reflect.InvocationHandler; 31 import java.lang.reflect.Method; 32 import java.lang.reflect.Proxy; 33 import jdk.incubator.code.*; 34 import jdk.incubator.code.op.CoreOp; 35 import jdk.incubator.code.type.ArrayType; 36 import jdk.incubator.code.type.FieldRef; 37 import jdk.incubator.code.type.MethodRef; 38 import jdk.incubator.code.type.FunctionType; 39 import jdk.incubator.code.type.JavaType; 40 import jdk.incubator.code.TypeElement; 41 import jdk.incubator.code.type.VarType; 42 import java.util.*; 43 import java.util.concurrent.locks.ReentrantLock; 44 import java.util.stream.Collectors; 45 import java.util.stream.Stream; 46 47 import static java.util.stream.Collectors.toMap; 48 49 public final class Interpreter { 50 private Interpreter() { 51 } 52 53 /** 54 * Invokes an invokable operation by interpreting the code elements within 55 * the operations body. 56 * <p> 57 * The sequence of arguments must consists of objects corresponding, in order, 58 * to the invokable operation's {@link Op.Invokable#parameters() parameters}. 59 * If the invokable operation {@link Op.Invokable#capturedValues() captures values} 60 * then the sequence of arguments must be appended with objects corresponding, 61 * in order, to the captured values. 62 * 63 * @param l the lookup to use for interpreting reflective operations. 64 * @param op the invokeable operation to interpret. 65 * @param args the invokeable's arguments appended with captured arguments, if any. 66 * @return the interpreter result of invokable operation. 67 * @param <T> the type of Invokable. 68 * @throws InterpreterException if there is a failure to interpret 69 * @throws Throwable if interpretation results in the throwing of an uncaught exception 70 */ 71 public static <T extends Op & Op.Invokable> 72 Object invoke(MethodHandles.Lookup l, T op, 73 Object... args) { 74 // Arguments can contain null values so we cannot use List.of 75 return invoke(l, op, Arrays.asList(args)); 76 } 77 78 /** 79 * Invokes an invokable operation by interpreting the code elements within 80 * the operations body. 81 * <p> 82 * The list of arguments must consists of objects corresponding, in order, 83 * to the invokable operation's {@link Op.Invokable#parameters() parameters}. 84 * If the invokable operation {@link Op.Invokable#capturedValues() captures values} 85 * then the list of arguments must be appended with objects corresponding, 86 * in order, to the captured values. 87 * 88 * @param l the lookup to use for interpreting reflective operations. 89 * @param op the invokeable operation to interpret. 90 * @param args the invokeable's arguments appended with captured arguments, if any. 91 * @return the interpreter result of invokable operation. 92 * @param <T> the type of Invokable. 93 * @throws InterpreterException if there is a failure to interpret 94 * @throws Throwable if interpretation results in the throwing of an uncaught exception 95 */ 96 public static <T extends Op & Op.Invokable> 97 Object invoke(MethodHandles.Lookup l, T op, 98 List<Object> args) { 99 List<Block.Parameter> parameters = op.parameters(); 100 List<Value> capturedValues = op.capturedValues(); 101 if (parameters.size() + capturedValues.size() != args.size()) { 102 throw interpreterException(new IllegalArgumentException( 103 String.format("Actual #arguments (%d) differs from #parameters (%d) plus #captured arguments (%d)", 104 args.size(), parameters.size(), capturedValues.size()))); 105 } 106 107 // Map symbolic parameters to runtime arguments 108 Map<Value, Object> valuesAndArguments = new HashMap<>(); 109 for (int i = 0; i < parameters.size(); i++) { 110 valuesAndArguments.put(parameters.get(i), args.get(i)); 111 } 112 // Map symbolic captured values to the additional runtime arguments 113 for (int i = 0; i < capturedValues.size(); i++) { 114 valuesAndArguments.put(capturedValues.get(i), args.get(parameters.size() + i)); 115 } 116 117 return interpretEntryBlock(l, op.body().entryBlock(), new OpContext(), valuesAndArguments); 118 } 119 120 121 @SuppressWarnings("serial") 122 public static final class InterpreterException extends RuntimeException { 123 private InterpreterException(Throwable cause) { 124 super(cause); 125 } 126 } 127 128 static InterpreterException interpreterException(Throwable cause) { 129 return new InterpreterException(cause); 130 } 131 132 record BlockContext(Block b, Map<Value, Object> valuesAndArguments) { 133 } 134 135 static final class OpContext { 136 final Map<Object, ReentrantLock> locks = new HashMap<>(); 137 final Deque<BlockContext> stack = new ArrayDeque<>(); 138 final Deque<ExceptionRegionRecord> erStack = new ArrayDeque<>(); 139 140 Object getValue(Value v) { 141 // @@@ Only dominating values are accessible 142 BlockContext bc = findContext(v); 143 if (bc != null) { 144 return bc.valuesAndArguments.get(v); 145 } else { 146 throw interpreterException(new IllegalArgumentException("Undefined value: " + v)); 147 } 148 } 149 150 Object setValue(Value v, Object o) { 151 BlockContext bc = findContext(v); 152 if (bc != null) { 153 throw interpreterException(new IllegalArgumentException("Value already defined: " + v)); 154 } 155 stack.peek().valuesAndArguments.put(v, o); 156 return o; 157 } 158 159 BlockContext findContext(Value v) { 160 Optional<BlockContext> ob = stack.stream().filter(b -> b.valuesAndArguments.containsKey(v)).findFirst(); 161 return ob.orElse(null); 162 } 163 164 boolean contains(Block.Reference s) { 165 Block sb = s.targetBlock(); 166 return stack.stream().anyMatch(bc -> bc.b.equals(sb)); 167 } 168 169 void successor(Block.Reference sb) { 170 List<Object> sbValues = sb.arguments().stream().map(this::getValue).toList(); 171 172 Block b = sb.targetBlock(); 173 Map<Value, Object> bValues = new HashMap<>(); 174 for (int i = 0; i < sbValues.size(); i++) { 175 bValues.put(b.parameters().get(i), sbValues.get(i)); 176 } 177 178 if (contains(sb)) { 179 // if block is already dominating pop back up from the back branch to the block 180 // before the successor block 181 while (!stack.peek().b.equals(sb.targetBlock())) { 182 stack.pop(); 183 } 184 stack.pop(); 185 } 186 stack.push(new BlockContext(b, bValues)); 187 } 188 189 void successor(Block b, Map<Value, Object> bValues) { 190 stack.push(new BlockContext(b, bValues)); 191 } 192 193 void popTo(BlockContext bc) { 194 while (!stack.peek().equals(bc)) { 195 stack.pop(); 196 } 197 } 198 199 void pushExceptionRegion(ExceptionRegionRecord erb) { 200 erStack.push(erb); 201 } 202 203 void popExceptionRegion(CoreOp.ExceptionRegionExit ere) { 204 ere.catchBlocks().forEach(catchBlock -> { 205 if (erStack.peek().catchBlock != catchBlock.targetBlock()) { 206 // @@@ Use internal exception type 207 throw interpreterException(new IllegalStateException("Mismatched exception regions")); 208 } 209 erStack.pop(); 210 }); 211 } 212 213 Block exception(MethodHandles.Lookup l, Throwable e) { 214 // Find the first matching exception region 215 // with a catch block whose argument type is assignable-compatible to the throwable 216 ExceptionRegionRecord er; 217 Block cb = null; 218 while ((er = erStack.poll()) != null && 219 (cb = er.match(l, e)) == null) { 220 } 221 222 if (er == null) { 223 return null; 224 } 225 226 // Pop the block context to the block defining the start of the exception region 227 popTo(er.mark); 228 while (erStack.size() > er.erStackDepth()) { 229 erStack.pop(); 230 } 231 return cb; 232 } 233 } 234 235 static final class VarBox 236 implements CoreOp.Var<Object> { 237 Object value; 238 239 public Object value() { 240 return value; 241 } 242 243 VarBox(Object value) { 244 this.value = value; 245 } 246 247 static final Object UINITIALIZED = new Object(); 248 } 249 250 record ClosureRecord(CoreOp.ClosureOp op, 251 List<Object> capturedArguments) { 252 } 253 254 record TupleRecord(List<Object> components) { 255 Object getComponent(int index) { 256 return components.get(index); 257 } 258 259 TupleRecord with(int index, Object value) { 260 List<Object> copy = new ArrayList<>(components); 261 copy.set(index, value); 262 return new TupleRecord(copy); 263 } 264 } 265 266 record ExceptionRegionRecord(BlockContext mark, int erStackDepth, Block catchBlock) { 267 Block match(MethodHandles.Lookup l, Throwable e) { 268 List<Block.Parameter> args = catchBlock.parameters(); 269 if (args.size() != 1) { 270 throw interpreterException(new IllegalStateException("Catch block must have one argument")); 271 } 272 TypeElement et = args.get(0).type(); 273 if (et instanceof VarType vt) { 274 et = vt.valueType(); 275 } 276 if (resolveToClass(l, et).isInstance(e)) { 277 return catchBlock; 278 } 279 return null; 280 } 281 } 282 283 static Object interpretBody(MethodHandles.Lookup l, Body body, 284 OpContext oc, 285 List<Object> args) { 286 List<Block.Parameter> parameters = body.entryBlock().parameters(); 287 if (parameters.size() != args.size()) { 288 throw interpreterException(new IllegalArgumentException( 289 "Incorrect number of arguments arguments")); 290 } 291 292 // Map symbolic parameters to runtime arguments 293 Map<Value, Object> arguments = new HashMap<>(); 294 for (int i = 0; i < parameters.size(); i++) { 295 arguments.put(parameters.get(i), args.get(i)); 296 } 297 298 return interpretEntryBlock(l, body.entryBlock(), oc, arguments); 299 } 300 301 static Object interpretEntryBlock(MethodHandles.Lookup l, Block entry, 302 OpContext oc, 303 Map<Value, Object> valuesAndArguments) { 304 assert entry.isEntryBlock(); 305 306 // If the stack is not empty it means we are interpreting 307 // an entry block with a parent body whose nearest ancestor body 308 // is the current context block's parent body 309 BlockContext yieldContext = oc.stack.peek(); 310 assert yieldContext == null || 311 yieldContext.b().parentBody() == entry.parentBody().parentOp().ancestorBody(); 312 313 // Note that first block cannot have any successors so the queue will have at least one entry 314 oc.stack.push(new BlockContext(entry, valuesAndArguments)); 315 while (true) { 316 BlockContext bc = oc.stack.peek(); 317 318 // Execute all but the terminating operation 319 int nops = bc.b.ops().size(); 320 try { 321 for (int i = 0; i < nops - 1; i++) { 322 Op op = bc.b.ops().get(i); 323 assert !(op instanceof Op.Terminating) : op.opName(); 324 325 Object result = interpretOp(l, oc, op); 326 oc.setValue(op.result(), result); 327 } 328 } catch (InterpreterException e) { 329 throw e; 330 } catch (Throwable t) { 331 processThrowable(oc, l, t); 332 continue; 333 } 334 335 // Execute the terminating operation 336 Op to = bc.b.terminatingOp(); 337 if (to instanceof CoreOp.ConditionalBranchOp cb) { 338 boolean p; 339 Object bop = oc.getValue(cb.predicate()); 340 if (bop instanceof Boolean bp) { 341 p = bp; 342 } else if (bop instanceof Integer ip) { 343 // @@@ This is required when lifting up from bytecode, since boolean values 344 // are erased to int values, abd the bytecode lifting implementation is not currently 345 // sophisticated enough to recover the type information 346 p = ip != 0; 347 } else { 348 throw interpreterException( 349 new UnsupportedOperationException("Unsupported type input to operation: " + cb)); 350 } 351 Block.Reference sb = p ? cb.trueBranch() : cb.falseBranch(); 352 oc.successor(sb); 353 } else if (to instanceof CoreOp.BranchOp b) { 354 Block.Reference sb = b.branch(); 355 356 oc.successor(sb); 357 } else if (to instanceof CoreOp.ThrowOp _throw) { 358 Throwable t = (Throwable) oc.getValue(_throw.argument()); 359 processThrowable(oc, l, t); 360 } else if (to instanceof CoreOp.ReturnOp ret) { 361 Value rv = ret.returnValue(); 362 return rv == null ? null : oc.getValue(rv); 363 } else if (to instanceof CoreOp.YieldOp yop) { 364 if (yieldContext == null) { 365 throw interpreterException( 366 new IllegalStateException("Yielding to no parent body")); 367 } 368 Value yv = yop.yieldValue(); 369 Object yr = yv == null ? null : oc.getValue(yv); 370 oc.popTo(yieldContext); 371 return yr; 372 } else if (to instanceof CoreOp.ExceptionRegionEnter ers) { 373 int erStackDepth = oc.erStack.size(); 374 ers.catchBlocks().forEach(catchBlock -> { 375 var er = new ExceptionRegionRecord(oc.stack.peek(), erStackDepth, catchBlock.targetBlock()); 376 oc.pushExceptionRegion(er); 377 }); 378 379 oc.successor(ers.start()); 380 } else if (to instanceof CoreOp.ExceptionRegionExit ere) { 381 oc.popExceptionRegion(ere); 382 383 oc.successor(ere.end()); 384 } else { 385 throw interpreterException( 386 new UnsupportedOperationException("Unsupported terminating operation: " + to.opName())); 387 } 388 } 389 } 390 391 static void processThrowable(OpContext oc, MethodHandles.Lookup l, Throwable t) { 392 // Find a matching catch block 393 Block cb = oc.exception(l, t); 394 if (cb == null) { 395 // If there is no matching catch bock then rethrow back to the caller 396 eraseAndThrow(t); 397 throw new InternalError("should not reach here"); 398 } 399 400 // Add a new block context to the catch block with the exception as the argument 401 Map<Value, Object> bValues = new HashMap<>(); 402 Block.Parameter eArg = cb.parameters().get(0); 403 if (eArg.type() instanceof VarType) { 404 bValues.put(eArg, new VarBox(t)); 405 } else { 406 bValues.put(eArg, t); 407 } 408 oc.successor(cb, bValues); 409 } 410 411 412 413 @SuppressWarnings("unchecked") 414 public static <E extends Throwable> void eraseAndThrow(Throwable e) throws E { 415 throw (E) e; 416 } 417 418 static Object interpretOp(MethodHandles.Lookup l, OpContext oc, Op o) { 419 if (o instanceof CoreOp.ConstantOp co) { 420 if (co.resultType().equals(JavaType.J_L_CLASS)) { 421 return resolveToClass(l, (JavaType) co.value()); 422 } else { 423 return co.value(); 424 } 425 } else if (o instanceof CoreOp.FuncCallOp fco) { 426 String name = fco.funcName(); 427 428 // Find top-level op 429 Op top = fco; 430 while (top.ancestorBody() != null) { 431 top = top.ancestorBody().parentOp(); 432 } 433 434 // Ensure top-level op is a module and function name 435 // is in the module's function table 436 if (top instanceof CoreOp.ModuleOp mop) { 437 CoreOp.FuncOp funcOp = mop.functionTable().get(name); 438 if (funcOp == null) { 439 throw interpreterException( 440 new IllegalStateException 441 ("Function " + name + " cannot be resolved: not in module's function table")); 442 } 443 444 List<Object> values = o.operands().stream().map(oc::getValue).toList(); 445 return Interpreter.invoke(l, funcOp, values); 446 } else { 447 throw interpreterException( 448 new IllegalStateException( 449 "Function " + name + " cannot be resolved: top level op is not a module")); 450 } 451 } else if (o instanceof CoreOp.InvokeOp co) { 452 MethodType target = resolveToMethodType(l, o.opType()); 453 MethodHandles.Lookup il = switch (co.invokeKind()) { 454 case STATIC, INSTANCE -> l; 455 case SUPER -> l.in(target.parameterType(0)); 456 }; 457 MethodHandle mh = resolveToMethodHandle(il, co.invokeDescriptor(), co.invokeKind()); 458 459 mh = mh.asType(target).asFixedArity(); 460 Object[] values = o.operands().stream().map(oc::getValue).toArray(); 461 return invoke(mh, values); 462 } else if (o instanceof CoreOp.NewOp no) { 463 Object[] values = o.operands().stream().map(oc::getValue).toArray(); 464 JavaType nType = (JavaType) no.constructorType().returnType(); 465 if (nType instanceof ArrayType at) { 466 if (values.length > at.dimensions()) { 467 throw interpreterException(new IllegalArgumentException("Bad constructor NewOp: " + no)); 468 } 469 int[] lengths = Stream.of(values).mapToInt(v -> (int) v).toArray(); 470 for (int length : lengths) { 471 nType = ((ArrayType)nType).componentType(); 472 } 473 return Array.newInstance(resolveToClass(l, nType), lengths); 474 } else { 475 MethodHandle mh = constructorHandle(l, no.constructorType()); 476 return invoke(mh, values); 477 } 478 } else if (o instanceof CoreOp.QuotedOp qo) { 479 SequencedMap<Value, Object> capturedValues = qo.capturedValues().stream() 480 .collect(toMap(v -> v, oc::getValue, (v, _) -> v, LinkedHashMap::new)); 481 return new Quoted(qo.quotedOp(), capturedValues); 482 } else if (o instanceof CoreOp.LambdaOp lo) { 483 SequencedMap<Value, Object> capturedValuesAndArguments = lo.capturedValues().stream() 484 .collect(toMap(v -> v, oc::getValue, (v, _) -> v, LinkedHashMap::new)); 485 Class<?> fi = resolveToClass(l, lo.functionalInterface()); 486 487 Object[] capturedArguments = capturedValuesAndArguments.sequencedValues().toArray(Object[]::new); 488 MethodHandle fProxy = INVOKE_LAMBDA_MH.bindTo(l).bindTo(lo).bindTo(capturedArguments) 489 .asCollector(Object[].class, lo.parameters().size()); 490 Object fiInstance = MethodHandleProxies.asInterfaceInstance(fi, fProxy); 491 492 // If a quotable lambda proxy again to add method Quoted quoted() 493 if (Quotable.class.isAssignableFrom(fi)) { 494 return Proxy.newProxyInstance(l.lookupClass().getClassLoader(), new Class<?>[]{fi}, 495 new InvocationHandler() { 496 private final Quoted quoted = new Quoted(lo, capturedValuesAndArguments); 497 @Override 498 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 499 if (Objects.equals(method.getName(), "quoted") && method.getParameterCount() == 0) { 500 return __internal_quoted(); 501 } else { 502 // Delegate to FI instance 503 return method.invoke(fiInstance, args); 504 } 505 } 506 507 public Quoted __internal_quoted() { 508 return quoted; 509 } 510 }); 511 } else { 512 return fiInstance; 513 } 514 } else if (o instanceof CoreOp.ClosureOp co) { 515 List<Object> capturedArguments = co.capturedValues().stream() 516 .map(oc::getValue).toList(); 517 return new ClosureRecord(co, capturedArguments); 518 } else if (o instanceof CoreOp.ClosureCallOp cco) { 519 List<Object> values = o.operands().stream().map(oc::getValue).toList(); 520 ClosureRecord cr = (ClosureRecord) values.get(0); 521 522 List<Object> arguments = new ArrayList<>(values.subList(1, values.size())); 523 arguments.addAll(cr.capturedArguments); 524 return Interpreter.invoke(l, cr.op(), arguments); 525 } else if (o instanceof CoreOp.VarOp vo) { 526 Object v = vo.isUninitialized() 527 ? VarBox.UINITIALIZED 528 : oc.getValue(o.operands().get(0)); 529 return new VarBox(v); 530 } else if (o instanceof CoreOp.VarAccessOp.VarLoadOp vlo) { 531 // Cast to CoreOp.Var, since the instance may have originated as an external instance 532 // via a captured value map 533 CoreOp.Var<?> vb = (CoreOp.Var<?>) oc.getValue(o.operands().get(0)); 534 Object value = vb.value(); 535 if (value == VarBox.UINITIALIZED) { 536 throw interpreterException(new IllegalStateException("Loading from uninitialized variable")); 537 } 538 return value; 539 } else if (o instanceof CoreOp.VarAccessOp.VarStoreOp vso) { 540 VarBox vb = (VarBox) oc.getValue(o.operands().get(0)); 541 vb.value = oc.getValue(o.operands().get(1)); 542 return null; 543 } else if (o instanceof CoreOp.TupleOp to) { 544 List<Object> values = o.operands().stream().map(oc::getValue).toList(); 545 return new TupleRecord(values); 546 } else if (o instanceof CoreOp.TupleLoadOp tlo) { 547 TupleRecord tb = (TupleRecord) oc.getValue(o.operands().get(0)); 548 return tb.getComponent(tlo.index()); 549 } else if (o instanceof CoreOp.TupleWithOp two) { 550 TupleRecord tb = (TupleRecord) oc.getValue(o.operands().get(0)); 551 return tb.with(two.index(), oc.getValue(o.operands().get(1))); 552 } else if (o instanceof CoreOp.FieldAccessOp.FieldLoadOp fo) { 553 if (fo.operands().isEmpty()) { 554 VarHandle vh = fieldStaticHandle(l, fo.fieldDescriptor()); 555 return vh.get(); 556 } else { 557 Object v = oc.getValue(o.operands().get(0)); 558 VarHandle vh = fieldHandle(l, fo.fieldDescriptor()); 559 return vh.get(v); 560 } 561 } else if (o instanceof CoreOp.FieldAccessOp.FieldStoreOp fo) { 562 if (fo.operands().size() == 1) { 563 Object v = oc.getValue(o.operands().get(0)); 564 VarHandle vh = fieldStaticHandle(l, fo.fieldDescriptor()); 565 vh.set(v); 566 } else { 567 Object r = oc.getValue(o.operands().get(0)); 568 Object v = oc.getValue(o.operands().get(1)); 569 VarHandle vh = fieldHandle(l, fo.fieldDescriptor()); 570 vh.set(r, v); 571 } 572 return null; 573 } else if (o instanceof CoreOp.InstanceOfOp io) { 574 Object v = oc.getValue(o.operands().get(0)); 575 return isInstance(l, io.type(), v); 576 } else if (o instanceof CoreOp.CastOp co) { 577 Object v = oc.getValue(o.operands().get(0)); 578 return cast(l, co.type(), v); 579 } else if (o instanceof CoreOp.ArrayLengthOp) { 580 Object a = oc.getValue(o.operands().get(0)); 581 return Array.getLength(a); 582 } else if (o instanceof CoreOp.ArrayAccessOp.ArrayLoadOp) { 583 Object a = oc.getValue(o.operands().get(0)); 584 Object index = oc.getValue(o.operands().get(1)); 585 return Array.get(a, (int) index); 586 } else if (o instanceof CoreOp.ArrayAccessOp.ArrayStoreOp) { 587 Object a = oc.getValue(o.operands().get(0)); 588 Object index = oc.getValue(o.operands().get(1)); 589 Object v = oc.getValue(o.operands().get(2)); 590 Array.set(a, (int) index, v); 591 return null; 592 } else if (o instanceof CoreOp.ArithmeticOperation || o instanceof CoreOp.TestOperation) { 593 MethodHandle mh = opHandle(l, o.opName(), o.opType()); 594 Object[] values = o.operands().stream().map(oc::getValue).toArray(); 595 return invoke(mh, values); 596 } else if (o instanceof CoreOp.ConvOp) { 597 MethodHandle mh = opHandle(l, o.opName() + "_" + o.opType().returnType(), o.opType()); 598 Object[] values = o.operands().stream().map(oc::getValue).toArray(); 599 return invoke(mh, values); 600 } else if (o instanceof CoreOp.AssertOp _assert) { 601 Body testBody = _assert.bodies.get(0); 602 boolean testResult = (boolean) interpretBody(l, testBody, oc, List.of()); 603 if (!testResult) { 604 if (_assert.bodies.size() > 1) { 605 Body messageBody = _assert.bodies.get(1); 606 String message = String.valueOf(interpretBody(l, messageBody, oc, List.of())); 607 throw new AssertionError(message); 608 } else { 609 throw new AssertionError(); 610 } 611 } 612 return null; 613 } else if (o instanceof CoreOp.ConcatOp) { 614 return o.operands().stream() 615 .map(oc::getValue) 616 .map(String::valueOf) 617 .collect(Collectors.joining()); 618 } else if (o instanceof CoreOp.MonitorOp.MonitorEnterOp) { 619 Object monitorTarget = oc.getValue(o.operands().get(0)); 620 if (monitorTarget == null) { 621 throw new NullPointerException(); 622 } 623 ReentrantLock lock = oc.locks.computeIfAbsent(monitorTarget, _ -> new ReentrantLock()); 624 lock.lock(); 625 return null; 626 } else if (o instanceof CoreOp.MonitorOp.MonitorExitOp) { 627 Object monitorTarget = oc.getValue(o.operands().get(0)); 628 if (monitorTarget == null) { 629 throw new NullPointerException(); 630 } 631 ReentrantLock lock = oc.locks.get(monitorTarget); 632 if (lock == null) { 633 throw new IllegalMonitorStateException(); 634 } 635 lock.unlock(); 636 return null; 637 } else { 638 throw interpreterException( 639 new UnsupportedOperationException("Unsupported operation: " + o.opName())); 640 } 641 } 642 643 static final MethodHandle INVOKE_LAMBDA_MH; 644 static { 645 try { 646 INVOKE_LAMBDA_MH = MethodHandles.lookup().findStatic(Interpreter.class, "invokeLambda", 647 MethodType.methodType(Object.class, MethodHandles.Lookup.class, 648 CoreOp.LambdaOp.class, Object[].class, Object[].class)); 649 } catch (Throwable t) { 650 throw new InternalError(t); 651 } 652 } 653 654 static Object invokeLambda(MethodHandles.Lookup l, CoreOp.LambdaOp op, Object[] capturedArgs, Object[] args) { 655 List<Object> arguments = new ArrayList<>(Arrays.asList(args)); 656 arguments.addAll(Arrays.asList(capturedArgs)); 657 return invoke(l, op, arguments); 658 } 659 660 static MethodHandle opHandle(MethodHandles.Lookup l, String opName, FunctionType ft) { 661 MethodType mt = resolveToMethodType(l, ft).erase(); 662 try { 663 return MethodHandles.lookup().findStatic(InvokableLeafOps.class, opName, mt); 664 } catch (NoSuchMethodException | IllegalAccessException e) { 665 throw interpreterException(e); 666 } 667 } 668 669 static MethodHandle constructorHandle(MethodHandles.Lookup l, FunctionType ft) { 670 MethodType mt = resolveToMethodType(l, ft); 671 672 if (mt.returnType().isArray()) { 673 if (mt.parameterCount() != 1 || mt.parameterType(0) != int.class) { 674 throw interpreterException(new IllegalArgumentException("Bad constructor descriptor: " + ft)); 675 } 676 return MethodHandles.arrayConstructor(mt.returnType()); 677 } else { 678 try { 679 return l.findConstructor(mt.returnType(), mt.changeReturnType(void.class)); 680 } catch (NoSuchMethodException | IllegalAccessException e) { 681 throw interpreterException(e); 682 } 683 } 684 } 685 686 static VarHandle fieldStaticHandle(MethodHandles.Lookup l, FieldRef d) { 687 return resolveToVarHandle(l, d); 688 } 689 690 static VarHandle fieldHandle(MethodHandles.Lookup l, FieldRef d) { 691 return resolveToVarHandle(l, d); 692 } 693 694 static Object isInstance(MethodHandles.Lookup l, TypeElement d, Object v) { 695 Class<?> c = resolveToClass(l, d); 696 return c.isInstance(v); 697 } 698 699 static Object cast(MethodHandles.Lookup l, TypeElement d, Object v) { 700 Class<?> c = resolveToClass(l, d); 701 return c.cast(v); 702 } 703 704 static MethodHandle resolveToMethodHandle(MethodHandles.Lookup l, MethodRef d, CoreOp.InvokeOp.InvokeKind kind) { 705 try { 706 return d.resolveToHandle(l, kind); 707 } catch (ReflectiveOperationException e) { 708 throw interpreterException(e); 709 } 710 } 711 712 static VarHandle resolveToVarHandle(MethodHandles.Lookup l, FieldRef d) { 713 try { 714 return d.resolveToHandle(l); 715 } catch (ReflectiveOperationException e) { 716 throw interpreterException(e); 717 } 718 } 719 720 public static MethodType resolveToMethodType(MethodHandles.Lookup l, FunctionType ft) { 721 try { 722 return MethodRef.toNominalDescriptor(ft).resolveConstantDesc(l); 723 } catch (ReflectiveOperationException e) { 724 throw interpreterException(e); 725 } 726 } 727 728 public static Class<?> resolveToClass(MethodHandles.Lookup l, TypeElement d) { 729 try { 730 if (d instanceof JavaType jt) { 731 return (Class<?>)jt.erasure().resolve(l); 732 } else { 733 throw new ReflectiveOperationException(); 734 } 735 } catch (ReflectiveOperationException e) { 736 throw interpreterException(e); 737 } 738 } 739 740 static Object invoke(MethodHandle m, Object... args) { 741 try { 742 return m.invokeWithArguments(args); 743 } catch (RuntimeException | Error e) { 744 throw e; 745 } catch (Throwable e) { 746 eraseAndThrow(e); 747 throw new InternalError("should not reach here"); 748 } 749 } 750 }