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