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