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