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