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