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