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