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;
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));
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 (lo.isQuotable()) {
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 // @@@ avoid use of opName
603 MethodHandle mh = opHandle(l, o.externalizeOpName(), o.opType());
604 Object[] values = o.operands().stream().map(oc::getValue).toArray();
605 return invoke(mh, values);
606 } else if (o instanceof JavaOp.ConvOp) {
607 // @@@ avoid use of opName
608 MethodHandle mh = opHandle(l, o.externalizeOpName() + "_" + o.opType().returnType(), o.opType());
609 Object[] values = o.operands().stream().map(oc::getValue).toArray();
610 return invoke(mh, values);
611 } else if (o instanceof JavaOp.AssertOp _assert) {
612 Body testBody = _assert.bodies.get(0);
613 boolean testResult = (boolean) interpretBody(l, testBody, oc, List.of());
614 if (!testResult) {
615 if (_assert.bodies.size() > 1) {
616 Body messageBody = _assert.bodies.get(1);
617 String message = String.valueOf(interpretBody(l, messageBody, oc, List.of()));
618 throw new AssertionError(message);
619 } else {
620 throw new AssertionError();
621 }
622 }
623 return null;
624 } else if (o instanceof JavaOp.ConcatOp) {
625 return o.operands().stream()
626 .map(oc::getValue)
627 .map(String::valueOf)
628 .collect(Collectors.joining());
629 } else if (o instanceof JavaOp.MonitorOp.MonitorEnterOp) {
630 Object monitorTarget = oc.getValue(o.operands().get(0));
631 if (monitorTarget == null) {
632 throw new NullPointerException();
633 }
634 ReentrantLock lock = oc.locks.computeIfAbsent(monitorTarget, _ -> new ReentrantLock());
635 lock.lock();
636 return null;
637 } else if (o instanceof JavaOp.MonitorOp.MonitorExitOp) {
638 Object monitorTarget = oc.getValue(o.operands().get(0));
639 if (monitorTarget == null) {
640 throw new NullPointerException();
641 }
642 ReentrantLock lock = oc.locks.get(monitorTarget);
643 if (lock == null) {
644 throw new IllegalMonitorStateException();
645 }
646 lock.unlock();
647 return null;
648 } else {
649 throw interpreterException(
650 new UnsupportedOperationException("Unsupported operation: " + o));
651 }
652 }
653
654 static final MethodHandle INVOKE_LAMBDA_MH;
655 static {
656 try {
657 INVOKE_LAMBDA_MH = MethodHandles.lookup().findStatic(Interpreter.class, "invokeLambda",
658 MethodType.methodType(Object.class, MethodHandles.Lookup.class,
659 JavaOp.LambdaOp.class, Object[].class, Object[].class));
660 } catch (Throwable t) {
661 throw new InternalError(t);
662 }
663 }
664
665 static Object invokeLambda(MethodHandles.Lookup l, JavaOp.LambdaOp op, Object[] capturedArgs, Object[] args) {
666 List<Object> arguments = new ArrayList<>(Arrays.asList(args));
667 arguments.addAll(Arrays.asList(capturedArgs));
668 return invoke(l, op, arguments);
669 }
670
671 static MethodHandle opHandle(MethodHandles.Lookup l, String opName, FunctionType ft) {
672 MethodType mt = resolveToMethodType(l, ft).erase();
673 try {
674 return MethodHandles.lookup().findStatic(InvokableLeafOps.class, opName, mt);
675 } catch (NoSuchMethodException | IllegalAccessException e) {
676 throw interpreterException(e);
677 }
678 }
679
680 static VarHandle fieldStaticHandle(MethodHandles.Lookup l, FieldRef d) {
681 return resolveToVarHandle(l, d);
682 }
683
684 static VarHandle fieldHandle(MethodHandles.Lookup l, FieldRef d) {
685 return resolveToVarHandle(l, d);
686 }
687
688 static Object isInstance(MethodHandles.Lookup l, TypeElement d, Object v) {
689 Class<?> c = resolveToClass(l, d);
690 return c.isInstance(v);
691 }
692
693 static Object cast(MethodHandles.Lookup l, TypeElement d, Object v) {
694 Class<?> c = resolveToClass(l, d);
695 return c.cast(v);
696 }
697
698 static MethodHandle resolveToMethodHandle(MethodHandles.Lookup l, MethodRef d, JavaOp.InvokeOp.InvokeKind kind) {
699 try {
700 return d.resolveToHandle(l, kind);
701 } catch (ReflectiveOperationException e) {
702 throw interpreterException(e);
703 }
704 }
705
706 static MethodHandle resolveToConstructorHandle(MethodHandles.Lookup l, ConstructorRef d) {
707 try {
708 return d.resolveToHandle(l);
709 } catch (ReflectiveOperationException e) {
710 throw interpreterException(e);
711 }
712 }
713
714 static VarHandle resolveToVarHandle(MethodHandles.Lookup l, FieldRef d) {
715 try {
716 return d.resolveToHandle(l);
717 } catch (ReflectiveOperationException e) {
718 throw interpreterException(e);
719 }
720 }
721
722 static MethodType resolveToMethodType(MethodHandles.Lookup l, FunctionType ft) {
723 try {
724 return MethodRef.toNominalDescriptor(ft).resolveConstantDesc(l);
725 } catch (ReflectiveOperationException e) {
726 throw interpreterException(e);
727 }
728 }
729
730 static Class<?> resolveToClass(MethodHandles.Lookup l, TypeElement d) {
731 try {
732 if (d instanceof JavaType jt) {
733 return (Class<?>)jt.erasure().resolve(l);
734 } else {
735 throw new ReflectiveOperationException();
736 }
737 } catch (ReflectiveOperationException e) {
738 throw interpreterException(e);
739 }
740 }
741
742 static Object invoke(MethodHandle m, Object... args) {
743 try {
744 return m.invokeWithArguments(args);
745 } catch (RuntimeException | Error e) {
746 throw e;
747 } catch (Throwable e) {
748 eraseAndThrow(e);
749 throw new InternalError("should not reach here");
750 }
751 }
752 }