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