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