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 }