1 /*
2 * Copyright (c) 2024, 2025, 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.dialect.core.CoreOp;
25 import jdk.incubator.code.interpreter.Interpreter;
26 import org.junit.jupiter.api.Assertions;
27 import org.junit.jupiter.api.Test;
28
29 import java.lang.classfile.ClassFile;
30 import java.lang.classfile.Label;
31 import java.lang.constant.*;
32 import java.lang.invoke.MethodHandles;
33 import java.lang.invoke.StringConcatFactory;
34
35 /*
36 * @test
37 * @modules jdk.incubator.code/jdk.incubator.code.internal
38 * @enablePreview
39 * @run junit TestLiftCustomBytecode
40 */
41
42 public class TestLiftCustomBytecode {
43
44 @Test
45 public void testBackJumps() throws Throwable {
46 CoreOp.FuncOp f = getFuncOp(ClassFile.of().build(ClassDesc.of("BackJumps"), clb ->
47 clb.withMethodBody("backJumps", MethodTypeDesc.of(ConstantDescs.CD_int, ConstantDescs.CD_int), ClassFile.ACC_STATIC, cob -> {
48 Label l1 = cob.newLabel();
49 Label l2 = cob.newLabel();
50 Label l3 = cob.newLabel();
51 Label l4 = cob.newLabel();
52 // Code wrapped in back jumps requires multiple passes and block skipping
53 cob.goto_(l1)
54 .labelBinding(l2)
55 .goto_(l3)
56 .labelBinding(l4)
57 .iload(0)
58 .ireturn()
59 .labelBinding(l1)
60 .goto_(l2)
61 .labelBinding(l3)
62 .goto_(l4);
63 })), "backJumps");
64
65 Assertions.assertEquals(42, (int) Interpreter.invoke(MethodHandles.lookup(), f, 42));
66 }
67
68 @Test
69 public void testDeepStackJump() throws Throwable {
70 CoreOp.FuncOp f = getFuncOp(ClassFile.of().build(ClassDesc.of("DeepStackJump"), clb ->
71 clb.withMethodBody("deepStackJump", MethodTypeDesc.of(ConstantDescs.CD_long), ClassFile.ACC_STATIC, cob -> {
72 Label l = cob.newLabel();
73 cob.lconst_1().iconst_1().iconst_2()
74 .goto_(l)
75 .labelBinding(l)
76 .iadd().i2l().ladd()
77 .lreturn();
78 })), "deepStackJump");
79
80 Assertions.assertEquals(4, (long) Interpreter.invoke(MethodHandles.lookup(), f));
81 }
82
83 public record TestRecord(int i, String s) {
84 }
85
86 @Test
87 public void testObjectMethodsIndy() throws Throwable {
88 byte[] testRecord = TestRecord.class.getResourceAsStream("TestLiftCustomBytecode$TestRecord.class").readAllBytes();
89 CoreOp.FuncOp toString = getFuncOp(testRecord, "toString");
90 CoreOp.FuncOp hashCode = getFuncOp(testRecord, "hashCode");
91 CoreOp.FuncOp equals = getFuncOp(testRecord, "equals");
92
93 TestRecord tr1 = new TestRecord(1, "hi"), tr2 = new TestRecord(2, "bye"), tr3 = new TestRecord(1, "hi");
94 MethodHandles.Lookup lookup = MethodHandles.lookup();
95 Assertions.assertEquals(tr1.toString(), (String)Interpreter.invoke(lookup, toString, tr1));
96 Assertions.assertEquals(tr2.toString(), (String)Interpreter.invoke(lookup, toString, tr2));
97 Assertions.assertEquals(tr1.hashCode(), (int)Interpreter.invoke(lookup, hashCode, tr1));
98 Assertions.assertEquals(tr2.hashCode(), (int)Interpreter.invoke(lookup, hashCode, tr2));
99 Assertions.assertTrue((boolean)Interpreter.invoke(lookup, equals, tr1, tr1));
100 Assertions.assertFalse((boolean)Interpreter.invoke(lookup, equals, tr1, tr2));
101 Assertions.assertTrue((boolean)Interpreter.invoke(lookup, equals, tr1, tr3));
102 Assertions.assertFalse((boolean)Interpreter.invoke(lookup, equals, tr1, "hello"));
103 }
104
105 @Test
106 public void testConstantBootstrapsCondy() throws Throwable {
107 byte[] testCondy = ClassFile.of().build(ClassDesc.of("TestCondy"), clb ->
108 clb.withMethodBody("condyMethod", MethodTypeDesc.of(ConstantDescs.CD_Class), ClassFile.ACC_STATIC, cob ->
109 cob.ldc(DynamicConstantDesc.ofNamed(
110 ConstantDescs.ofConstantBootstrap(ConstantDescs.CD_ConstantBootstraps, "primitiveClass", ConstantDescs.CD_Class),
111 int.class.descriptorString(),
112 ConstantDescs.CD_Class))
113 .areturn()));
114
115 CoreOp.FuncOp primitiveInteger = getFuncOp(testCondy, "condyMethod");
116
117 MethodHandles.Lookup lookup = MethodHandles.lookup();
118 Assertions.assertEquals(int.class, (Class)Interpreter.invoke(lookup, primitiveInteger));
119 }
120
121 @Test
122 public void testStringMakeConcat() throws Throwable {
123 byte[] testStringMakeConcat = ClassFile.of().build(ClassDesc.of("TestStringMakeConcat"), clb ->
124 clb.withMethodBody("concatMethod", MethodTypeDesc.of(ConstantDescs.CD_String), ClassFile.ACC_STATIC, cob ->
125 cob.ldc("A").ldc("B").ldc("C")
126 .invokedynamic(DynamicCallSiteDesc.of(
127 ConstantDescs.ofCallsiteBootstrap(StringConcatFactory.class.describeConstable().get(), "makeConcat", ConstantDescs.CD_CallSite),
128 MethodTypeDesc.of(ConstantDescs.CD_String, ConstantDescs.CD_String, ConstantDescs.CD_String, ConstantDescs.CD_String)))
129 .areturn()));
130
131 CoreOp.FuncOp concatMethod = getFuncOp(testStringMakeConcat, "concatMethod");
132
133 MethodHandles.Lookup lookup = MethodHandles.lookup();
134 Assertions.assertEquals("ABC", (String)Interpreter.invoke(lookup, concatMethod));
135 }
136
137 static CoreOp.FuncOp getFuncOp(byte[] classdata, String method) {
138 CoreOp.FuncOp flift = BytecodeLift.lift(classdata, method);
139 System.out.println(flift.toText());
140 return flift;
141 }
142 }