1 /*
2 * Copyright (c) 2025, 2026, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 import jdk.incubator.code.*;
25 import jdk.incubator.code.Reflect;
26 import jdk.incubator.code.bytecode.BytecodeGenerator;
27 import jdk.incubator.code.dialect.core.CoreOp;
28 import jdk.incubator.code.dialect.java.JavaOp;
29 import jdk.incubator.code.dialect.java.JavaType;
30 import jdk.internal.classfile.components.ClassPrinter;
31 import org.junit.jupiter.api.Assertions;
32 import org.junit.jupiter.api.BeforeAll;
33 import org.junit.jupiter.params.ParameterizedTest;
34 import org.junit.jupiter.params.provider.MethodSource;
35
36 import java.io.Closeable;
37 import java.io.IOException;
38 import java.lang.classfile.ClassFile;
39 import java.lang.classfile.ClassModel;
40 import java.lang.invoke.MethodHandles;
41 import java.lang.reflect.AccessFlag;
42 import java.lang.reflect.Method;
43 import java.util.*;
44 import java.util.function.Consumer;
45 import java.util.function.Function;
46 import java.util.function.IntUnaryOperator;
47 import java.util.stream.Collectors;
48 import java.util.stream.Stream;
49
50 /*
51 * @test
52 * @modules jdk.incubator.code/jdk.incubator.code.internal
53 * @modules java.base/jdk.internal.classfile.components
54 * @library ../../lib
55 * @enablePreview
56 * @run junit/othervm -Djdk.invoke.MethodHandle.dumpClassFiles=true TestBytecodeLift
57 */
58
59 public class TestBytecodeLift {
60
61 @Reflect
62 static int intNumOps(int i, int j, int k) {
63 k++;
64 i = (i + j) / k - i % j;
65 i--;
66 return i;
67 }
68
69 @Reflect
70 static byte byteNumOps(byte i, byte j, byte k) {
71 k++;
72 i = (byte) ((i + j) / k - i % j);
73 i--;
74 return i;
75 }
76
77 @Reflect
78 static short shortNumOps(short i, short j, short k) {
79 k++;
80 i = (short) ((i + j) / k - i % j);
81 i--;
82 return i;
83 }
84
85 @Reflect
86 static char charNumOps(char i, char j, char k) {
87 k++;
88 i = (char) ((i + j) / k - i % j);
89 i--;
90 return i;
91 }
92
93 @Reflect
94 static long longNumOps(long i, long j, long k) {
95 k++;
96 i = (i + j) / k - i % j;
97 i--;
98 return i;
99 }
100
101 @Reflect
102 static float floatNumOps(float i, float j, float k) {
103 k++;
104 i = (i + j) / k - i % j;
105 i--;
106 return i;
107 }
108
109 @Reflect
110 static double doubleNumOps(double i, double j, double k) {
111 k++;
112 i = (i + j) / k - i % j;
113 i--;
114 return i;
115 }
116
117 @Reflect
118 static int intBitOps(int i, int j, int k) {
119 return ~(i & j | k ^ j);
120 }
121
122 @Reflect
123 static byte byteBitOps(byte i, byte j, byte k) {
124 return (byte) ~(i & j | k ^ j);
125 }
126
127 @Reflect
128 static short shortBitOps(short i, short j, short k) {
129 return (short) ~(i & j | k ^ j);
130 }
131
132 @Reflect
133 static char charBitOps(char i, char j, char k) {
134 return (char) ~(i & j | k ^ j);
135 }
136
137 @Reflect
138 static long longBitOps(long i, long j, long k) {
139 return ~(i & j | k ^ j);
140 }
141
142 @Reflect
143 static boolean boolBitOps(boolean i, boolean j, boolean k) {
144 return i & j | k ^ j;
145 }
146
147 @Reflect
148 static int intShiftOps(int i, int j, int k) {
149 return ((-1 >> i) << (j << k)) >>> (k - j);
150 }
151
152 @Reflect
153 static byte byteShiftOps(byte i, byte j, byte k) {
154 return (byte) (((-1 >> i) << (j << k)) >>> (k - j));
155 }
156
157 @Reflect
158 static short shortShiftOps(short i, short j, short k) {
159 return (short) (((-1 >> i) << (j << k)) >>> (k - j));
160 }
161
162 @Reflect
163 static char charShiftOps(char i, char j, char k) {
164 return (char) (((-1 >> i) << (j << k)) >>> (k - j));
165 }
166
167 @Reflect
168 static long longShiftOps(long i, long j, long k) {
169 return ((-1 >> i) << (j << k)) >>> (k - j);
170 }
171
172 @Reflect
173 static Object[] boxingAndUnboxing(int i, byte b, short s, char c, Integer ii, Byte bb, Short ss, Character cc) {
174 ii += i; ii += b; ii += s; ii += c;
175 i += ii; i += bb; i += ss; i += cc;
176 b += ii; b += bb; b += ss; b += cc;
177 s += ii; s += bb; s += ss; s += cc;
178 c += ii; c += bb; c += ss; c += cc;
179 return new Object[]{i, b, s, c};
180 }
181
182 @Reflect
183 static String constructor(String s, int i, int j) {
184 return new String(s.getBytes(), i, j);
185 }
186
187 @Reflect
188 static Class<?> classArray(int i, int j) {
189 Class<?>[] ifaces = new Class[1 + i + j];
190 ifaces[0] = Function.class;
191 return ifaces[0];
192 }
193
194 @Reflect
195 static String[] stringArray(int i, int j) {
196 return new String[i];
197 }
198
199 @Reflect
200 static String[][] stringArray2(int i, int j) {
201 return new String[i][];
202 }
203
204 @Reflect
205 static String[][] stringArrayMulti(int i, int j) {
206 return new String[i][j];
207 }
208
209 @Reflect
210 static int[][] initializedIntArray(int i, int j) {
211 return new int[][]{{i, j}, {i + j}};
212 }
213
214 @Reflect
215 static int ifElseCompare(int i, int j) {
216 if (i < 3) {
217 i += 1;
218 } else {
219 j += 2;
220 }
221 return i + j;
222 }
223
224 @Reflect
225 static int ifElseEquality(int i, int j) {
226 if (j != 0) {
227 if (i != 0) {
228 i += 1;
229 } else {
230 i += 2;
231 }
232 } else {
233 if (j != 0) {
234 i += 3;
235 } else {
236 i += 4;
237 }
238 }
239 return i;
240 }
241
242 @Reflect
243 static int objectsCompare(Boolean b1, Boolean b2, Boolean b3) {
244 Object a = b1;
245 Object b = b2;
246 Object c = b3;
247 return a == b ? (a != c ? 1 : 2) : (b != c ? 3 : 4);
248 }
249
250 @Reflect
251 static int conditionalExpr(int i, int j) {
252 return ((i - 1 >= 0) ? i - 1 : j - 1);
253 }
254
255 @Reflect
256 static int nestedConditionalExpr(int i, int j) {
257 return (i < 2) ? (j < 3) ? i : j : i + j;
258 }
259
260 static final int[] MAP = {0, 1, 2, 3, 4};
261
262 @Reflect
263 static int deepStackBranches(boolean a, boolean b) {
264 return MAP[a ? MAP[b ? 1 : 2] : MAP[b ? 3 : 4]];
265 }
266
267 @Reflect
268 static int tryFinally(int i, int j) {
269 try {
270 i = i + j;
271 } finally {
272 i = i + j;
273 }
274 return i;
275 }
276
277 public record A(String s) {}
278
279 @Reflect
280 static A newWithArgs(int i, int j) {
281 return new A("hello world".substring(i, i + j));
282 }
283
284 @Reflect
285 static int loop(int n, int j) {
286 int sum = 0;
287 for (int i = 0; i < n; i++) {
288 sum = sum + j;
289 }
290 return sum;
291 }
292
293
294 @Reflect
295 static int ifElseNested(int a, int b) {
296 int c = a + b;
297 int d = 10 - a + b;
298 if (b < 3) {
299 if (a < 3) {
300 a += 1;
301 } else {
302 b += 2;
303 }
304 c += 3;
305 } else {
306 if (a > 2) {
307 a += 4;
308 } else {
309 b += 5;
310 }
311 d += 6;
312 }
313 return a + b + c + d;
314 }
315
316 @Reflect
317 static int nestedLoop(int m, int n) {
318 int sum = 0;
319 for (int i = 0; i < m; i++) {
320 for (int j = 0; j < n; j++) {
321 sum = sum + i + j;
322 }
323 }
324 return sum;
325 }
326
327 @Reflect
328 static int methodCall(int a, int b) {
329 int i = Math.max(a, b);
330 return Math.negateExact(i);
331 }
332
333 @Reflect
334 static int[] primitiveArray(int i, int j) {
335 int[] ia = new int[i + 1];
336 ia[0] = j;
337 return ia;
338 }
339
340 @Reflect
341 static boolean not(boolean b) {
342 return !b;
343 }
344
345 @Reflect
346 static boolean notCompare(int i, int j) {
347 boolean b = i < j;
348 return !b;
349 }
350
351 @Reflect
352 static int mod(int i, int j) {
353 return i % (j + 1);
354 }
355
356 @Reflect
357 static int xor(int i, int j) {
358 return i ^ j;
359 }
360
361 @Reflect
362 static int whileLoop(int i, int n) { int
363 counter = 0;
364 while (i < n && counter < 3) {
365 counter++;
366 if (counter == 4) {
367 break;
368 }
369 i++;
370 }
371 return counter;
372 }
373
374 static int consumeLambda(int i, IntUnaryOperator f) {
375 Assertions.assertNotNull(Op.ofLambda(f).get());
376 Assertions.assertNotNull(Op.ofLambda(f).get().op());
377 Assertions.assertTrue(Op.ofLambda(f).get().op() instanceof JavaOp.LambdaOp);
378 return f.applyAsInt(i + 1);
379 }
380
381 @Reflect
382 static int lambda(int i) {
383 return consumeLambda(i, a -> -a);
384 }
385
386 @Reflect
387 static int lambdaWithCapture(int i, String s) {
388 return consumeLambda(i, a -> a + s.length());
389 }
390
391 @Reflect
392 static int nestedLambdasWithCaptures(int i, int j, String s) {
393 return consumeLambda(i, a -> consumeLambda(a, b -> a + b + j - s.length()) + s.length());
394 }
395
396 @Reflect
397 static int methodHandle(int i) {
398 return consumeLambda(i, Math::negateExact);
399 }
400
401 int instanceMethod(int i) {
402 return -i + 13;
403 }
404
405 @Reflect
406 int instanceMethodHandle(int i) {
407 return consumeLambda(i, this::instanceMethod);
408 }
409
410 static void consume(boolean b, Consumer<Object> requireNonNull) {
411 if (b) {
412 requireNonNull.accept(new Object());
413 } else try {
414 requireNonNull.accept(null);
415 throw new AssertionError("Expectend NPE");
416 } catch (NullPointerException expected) {
417 }
418 }
419
420 @Reflect
421 static void nullReturningMethodHandle(boolean b) {
422 consume(b, Objects::requireNonNull);
423 }
424
425 @Reflect
426 static boolean compareLong(long i, long j) {
427 return i > j;
428 }
429
430 @Reflect
431 static boolean compareFloat(float i, float j) {
432 return i > j;
433 }
434
435 @Reflect
436 static boolean compareDouble(double i, double j) {
437 return i > j;
438 }
439
440 @Reflect
441 static int lookupSwitch(int i) {
442 return switch (1000 * i) {
443 case 1000 -> 1;
444 case 2000 -> 2;
445 case 3000 -> 3;
446 default -> 0;
447 };
448 }
449
450 @Reflect
451 static int tableSwitch(int i) {
452 return switch (i) {
453 case 1 -> 1;
454 case 2 -> 2;
455 case 3 -> 3;
456 default -> 0;
457 };
458 }
459
460 int instanceField = -1;
461
462 @Reflect
463 int instanceFieldAccess(int i) {
464 int ret = instanceField;
465 instanceField = i;
466 return ret;
467 }
468
469 @Reflect
470 static String stringConcat(String a, String b) {
471 return "a"+ a +"\u0001" + a + "b\u0002c" + b + "\u0001\u0002" + b + "dd";
472 }
473
474 @Reflect
475 static String multiTypeConcat(int i, Boolean b, char c, Short s, float f, Double d) {
476 return "i:"+ i +" b:" + b + " c:" + c + " f:" + f + " d:" + d;
477 }
478
479 @Reflect
480 static int ifTrue(int i) {
481 if (true) {
482 return i;
483 }
484 return -i;
485 }
486
487 @Reflect
488 static int excHandlerFollowingSplitTable(boolean b) {
489 try {
490 if (b) return 1;
491 else throw new Exception();
492 } catch (Exception ex) {}
493 return 2;
494 }
495
496 @Reflect
497 static int varModifiedInTryBlock(boolean b) {
498 int i = 0;
499 try {
500 i++;
501 if (b) throw new Exception();
502 i++;
503 throw new Exception();
504 } catch (Exception ex) {
505 return i;
506 }
507 }
508
509 @Reflect
510 static boolean finallyWithLoop(boolean b) {
511 try {
512 while (b) {
513 if (b)
514 return false;
515 b = !b;
516 }
517 return true;
518 } finally {
519 b = false;
520 }
521 }
522
523 @Reflect
524 static long doubleUseOfOperand(int x) {
525 long piece = x;
526 return piece * piece;
527 }
528
529 @Reflect
530 static int branchToExceptionBoundary(boolean b) {
531 int result = 0;
532 try (Closeable _ = () -> {}) {
533 for (Object _ : List.of(new Object())) {
534 try (Closeable _ = () -> {}) {
535 result++;
536 } catch (IOException _) {
537 throw new RuntimeException();
538 }
539 }
540 } catch (IOException _) {
541 throw new RuntimeException();
542 }
543 return b ? result : -result;
544 }
545
546 @Reflect
547 static int nestedFinallyLoopTryCatchAndExpression(int x) {
548 try {
549 for (int i = 0; ; ) {
550 try {
551 if ((x & (1 << i++)) != 0) {
552 return x + i;
553 }
554 throw new RuntimeException();
555 } catch (RuntimeException e) {
556 if (i > 4) {
557 return -1;
558 }
559 }
560 }
561 } finally {
562 if (x < 0) {
563 throw new RuntimeException();
564 }
565 }
566 }
567
568 record TestData(Method testMethod) {
569 @Override
570 public String toString() {
571 String s = testMethod.getName() + Arrays.stream(testMethod.getParameterTypes())
572 .map(Class::getSimpleName).collect(Collectors.joining(",", "(", ")"));
573 if (s.length() > 30) s = s.substring(0, 27) + "...";
574 return s;
575 }
576 }
577
578 public static Stream<TestData> testMethods() {
579 return Stream.of(TestBytecodeLift.class.getDeclaredMethods())
580 .filter(m -> m.isAnnotationPresent(Reflect.class))
581 .map(TestData::new);
582 }
583
584 private static byte[] CLASS_DATA;
585 private static ClassModel CLASS_MODEL;
586
587 @BeforeAll
588 public static void setup() throws Exception {
589 CLASS_DATA = TestBytecodeLift.class.getResourceAsStream("TestBytecodeLift.class").readAllBytes();
590 CLASS_MODEL = ClassFile.of().parse(CLASS_DATA);
591 }
592
593 private static final Map<Class<?>, Object[]> TEST_ARGS = new IdentityHashMap<>();
594 private static Object[] values(Object... values) {
595 return values;
596 }
597 private static void initTestArgs(Object[] values, Class<?>... argTypes) {
598 for (var argType : argTypes) TEST_ARGS.put(argType, values);
599 }
600 static {
601 initTestArgs(values(1, 2, 4), int.class, Integer.class);
602 initTestArgs(values((byte)1, (byte)3, (byte)4), byte.class, Byte.class);
603 initTestArgs(values((short)1, (short)2, (short)3), short.class, Short.class);
604 initTestArgs(values((char)2, (char)3, (char)4), char.class, Character.class);
605 initTestArgs(values(false, true), boolean.class, Boolean.class);
606 initTestArgs(values("Hello World"), String.class);
607 initTestArgs(values(1l, 2l, 4l), long.class, Long.class);
608 initTestArgs(values(1f, 3f, 4f), float.class, Float.class);
609 initTestArgs(values(1d, 2d, 3d), double.class, Double.class);
610 }
611
612 interface Executor {
613 void execute(Object[] args) throws Throwable;
614 }
615
616 private static void permutateAllArgs(Class<?>[] argTypes, Executor executor) throws Throwable {
617 final int argn = argTypes.length;
618 Object[][] argValues = new Object[argn][];
619 for (int i = 0; i < argn; i++) {
620 argValues[i] = TEST_ARGS.get(argTypes[i]);
621 }
622 int[] argIndexes = new int[argn];
623 Object[] args = new Object[argn];
624 while (true) {
625 for (int i = 0; i < argn; i++) {
626 args[i] = argValues[i][argIndexes[i]];
627 }
628 executor.execute(args);
629 int i = argn - 1;
630 while (i >= 0 && argIndexes[i] == argValues[i].length - 1) i--;
631 if (i < 0) return;
632 argIndexes[i++]++;
633 while (i < argn) argIndexes[i++] = 0;
634 }
635 }
636
637 @ParameterizedTest
638 @MethodSource("testMethods")
639 public void testLift(TestData d) throws Throwable {
640 CoreOp.FuncOp flift = lift(CLASS_DATA, d);
641 var errors = Verifier.verify(MethodHandles.lookup(), flift);
642 Assertions.assertTrue(errors.isEmpty(), errors.toString());
643 CoreOp.FuncOp secondRound = lift(BytecodeGenerator.generateClassData(MethodHandles.lookup(), flift), d);
644 errors = Verifier.verify(MethodHandles.lookup(), secondRound);
645 Assertions.assertTrue(errors.isEmpty(), errors.toString());
646 try {
647 Object receiver1, receiver2, receiver3;
648 if (d.testMethod.accessFlags().contains(AccessFlag.STATIC)) {
649 receiver1 = null;
650 receiver2 = null;
651 receiver3 = null;
652 } else {
653 receiver1 = new TestBytecodeLift();
654 receiver2 = new TestBytecodeLift();
655 receiver3 = new TestBytecodeLift();
656 }
657 permutateAllArgs(d.testMethod.getParameterTypes(), args -> {
658 var expected = d.testMethod.invoke(receiver1, args);
659 assertEquals(expected, invokeAndConvert(flift, receiver2, args));
660 assertEquals(expected, invokeAndConvert(secondRound, receiver3, args));
661 });
662 } catch (Throwable e) {
663 System.out.println("Compiled model:");
664 Op.ofMethod(d.testMethod).ifPresent(f -> System.out.println(f.toText()));
665 System.out.println("Lifted model:");
666 System.out.println(flift.toText());
667 System.out.println("Second round:");
668 System.out.println(secondRound.toText());
669 throw e;
670 }
671 }
672
673 static CoreOp.FuncOp lift(byte[] classData, TestData d) throws Exception {
674 try {
675 return BytecodeLift.lift(classData, d.testMethod.getName());
676 } catch (Throwable e) {
677 ClassPrinter.toYaml(ClassFile.of().parse(classData)
678 .methods().stream().filter(m -> m.methodName().equalsString(d.testMethod().getName())).findAny().get(),
679 ClassPrinter.Verbosity.CRITICAL_ATTRIBUTES, System.err::print);
680 System.err.println("Lift failed, compiled model:");
681 Op.ofMethod(d.testMethod).ifPresent(f -> System.err.println(f.toText()));
682 throw e;
683 }
684 }
685
686 private static Object invokeAndConvert(CoreOp.FuncOp func, Object receiver, Object... args) {
687 List argl = new ArrayList(args.length + 1);
688 if (receiver != null) argl.add(receiver);
689 argl.addAll(Arrays.asList(args));
690 Object ret = Interpreter.invoke(MethodHandles.lookup(), func, argl);
691 if (ret instanceof Integer i) {
692 CodeType rt = func.invokableSignature().returnType();
693 if (rt.equals(JavaType.BOOLEAN)) {
694 return i != 0;
695 } else if (rt.equals(JavaType.BYTE)) {
696 return i.byteValue();
697 } else if (rt.equals(JavaType.CHAR)) {
698 return (short)i.intValue();
699 } else if (rt.equals(JavaType.SHORT)) {
700 return i.shortValue();
701 }
702 }
703 return ret;
704 }
705
706 private static void assertEquals(Object expected, Object actual) {
707 switch (expected) {
708 case int[] expArr when actual instanceof int[] actArr -> Assertions.assertArrayEquals(expArr, actArr);
709 case Object[] expArr when actual instanceof Object[] actArr -> Assertions.assertArrayEquals(expArr, actArr);
710 case null, default -> Assertions.assertEquals(expected, actual);
711 }
712 }
713 }