1 /*
  2  * Copyright (c) 2023, 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 /*
 25  * @test
 26  * @library ../
 27  * @run testng/othervm
 28  *   --enable-native-access=ALL-UNNAMED
 29  *   -Xbatch
 30  *   -XX:CompileCommand=dontinline,TestNormalize::doCall*
 31  *   TestNormalize
 32  */
 33 
 34 import org.testng.annotations.DataProvider;
 35 import org.testng.annotations.Test;
 36 
 37 import java.lang.foreign.*;
 38 import java.lang.invoke.MethodHandle;
 39 import java.lang.invoke.MethodHandles;
 40 import java.lang.invoke.MethodType;
 41 
 42 import static java.lang.foreign.ValueLayout.ADDRESS;
 43 import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
 44 import static java.lang.foreign.ValueLayout.JAVA_BYTE;
 45 import static java.lang.foreign.ValueLayout.JAVA_CHAR;
 46 import static java.lang.foreign.ValueLayout.JAVA_INT;
 47 import static java.lang.foreign.ValueLayout.JAVA_SHORT;
 48 import static org.testng.Assert.assertEquals;
 49 
 50 // test normalization of smaller than int primitive types
 51 public class TestNormalize extends NativeTestHelper {
 52 
 53     private static final Linker LINKER = Linker.nativeLinker();
 54     private static final MethodHandle SAVE_BOOLEAN_AS_INT;
 55     private static final MethodHandle SAVE_BYTE_AS_INT;
 56     private static final MethodHandle SAVE_SHORT_AS_INT;
 57     private static final MethodHandle SAVE_CHAR_AS_INT;
 58 
 59     private static final MethodHandle BOOLEAN_TO_INT;
 60     private static final MethodHandle BYTE_TO_INT;
 61     private static final MethodHandle SHORT_TO_INT;
 62     private static final MethodHandle CHAR_TO_INT;
 63 
 64     private static final MethodHandle NATIVE_BOOLEAN_TO_INT;
 65 
 66     private static final int BOOLEAN_HOB_MASK = ~0b1;
 67     private static final int BYTE_HOB_MASK    = ~0xFF;
 68     private static final int SHORT_HOB_MASK   = ~0xFFFF;
 69     private static final int CHAR_HOB_MASK    = ~0xFFFF;
 70 
 71     private static final MethodHandle SAVE_BOOLEAN;
 72 
 73     static {
 74         System.loadLibrary("Normalize");
 75 
 76         try {
 77             MethodHandles.Lookup lookup = MethodHandles.lookup();
 78             SAVE_BOOLEAN_AS_INT = lookup.findStatic(TestNormalize.class, "saveBooleanAsInt", MethodType.methodType(void.class, boolean.class, int[].class));
 79             SAVE_BYTE_AS_INT = lookup.findStatic(TestNormalize.class, "saveByteAsInt", MethodType.methodType(void.class, byte.class, int[].class));
 80             SAVE_SHORT_AS_INT = lookup.findStatic(TestNormalize.class, "saveShortAsInt", MethodType.methodType(void.class, short.class, int[].class));
 81             SAVE_CHAR_AS_INT = lookup.findStatic(TestNormalize.class, "saveCharAsInt", MethodType.methodType(void.class, char.class, int[].class));
 82 
 83             BOOLEAN_TO_INT = lookup.findStatic(TestNormalize.class, "booleanToInt", MethodType.methodType(int.class, boolean.class));
 84             BYTE_TO_INT = lookup.findStatic(TestNormalize.class, "byteToInt", MethodType.methodType(int.class, byte.class));
 85             SHORT_TO_INT = lookup.findStatic(TestNormalize.class, "shortToInt", MethodType.methodType(int.class, short.class));
 86             CHAR_TO_INT = lookup.findStatic(TestNormalize.class, "charToInt", MethodType.methodType(int.class, char.class));
 87 
 88             NATIVE_BOOLEAN_TO_INT = LINKER.downcallHandle(findNativeOrThrow("int_identity"), FunctionDescriptor.of(JAVA_INT, JAVA_BOOLEAN));
 89 
 90             SAVE_BOOLEAN = lookup.findStatic(TestNormalize.class, "saveBoolean", MethodType.methodType(void.class, boolean.class, boolean[].class));
 91         } catch (ReflectiveOperationException e) {
 92             throw new ExceptionInInitializerError(e);
 93         }
 94     }
 95 
 96     // The idea of this test is that we pass a 'dirty' int value down to native code, and then receive it back
 97     // as the argument to an upcall, as well as the result of the downcall, but with a sub-int type (boolean, byte, short, char).
 98     // When we do either of those, argument normalization should take place, so that the resulting value is sane (1).
 99     // After that we convert the value back to int again, the JVM can/will skip value normalization here.
100     // We then check the high order bits of the resulting int. If argument normalization took place at (1), they should all be 0.
101     @Test(dataProvider = "cases")
102     public void testNormalize(ValueLayout layout, int testValue, int hobMask, MethodHandle toInt, MethodHandle saver) throws Throwable {
103         // use actual type as parameter type to test upcall arg normalization
104         FunctionDescriptor upcallDesc = FunctionDescriptor.ofVoid(layout);
105         // use actual type as return type to test downcall return normalization
106         FunctionDescriptor downcallDesc = FunctionDescriptor.of(layout, ADDRESS, JAVA_INT);
107 
108         MemorySegment target = findNativeOrThrow("test");
109         MethodHandle downcallHandle = LINKER.downcallHandle(target, downcallDesc);
110         downcallHandle = MethodHandles.filterReturnValue(downcallHandle, toInt);
111 
112         try (Arena arena = Arena.ofConfined()) {
113             int[] box = new int[1];
114             saver = MethodHandles.insertArguments(saver, 1, box);
115             MemorySegment upcallStub = LINKER.upcallStub(saver, upcallDesc, arena);
116             int dirtyValue = testValue | hobMask; // set all bits that should not be set
117 
118             // test after JIT as well
119             for (int i = 0; i < 20_000; i++) {
120                 doCall(downcallHandle, upcallStub, box, dirtyValue, hobMask);
121             }
122         }
123     }
124 
125     private static void doCall(MethodHandle downcallHandle, MemorySegment upcallStub,
126                                int[] box, int dirtyValue, int hobMask) throws Throwable {
127         int result = (int) downcallHandle.invokeExact(upcallStub, dirtyValue);
128         assertEquals(box[0] & hobMask, 0); // check normalized upcall arg
129         assertEquals(result & hobMask, 0); // check normalized downcall return value
130     }
131 
132     public static void saveBooleanAsInt(boolean b, int[] box) {
133         box[0] = booleanToInt(b);
134     }
135     public static void saveByteAsInt(byte b, int[] box) {
136         box[0] = byteToInt(b);
137     }
138     public static void saveShortAsInt(short s, int[] box) {
139         box[0] = shortToInt(s);
140     }
141     public static void saveCharAsInt(char c, int[] box) {
142         box[0] = charToInt(c);
143     }
144 
145     public static int booleanToInt(boolean b) {
146         try {
147             return (int) NATIVE_BOOLEAN_TO_INT.invokeExact(b); // FIXME do in pure Java?
148         } catch (Throwable e) {
149             throw new RuntimeException(e);
150         }
151     }
152     public static int byteToInt(byte b) {
153         return b;
154     }
155     public static int charToInt(char c) {
156         return c;
157     }
158     public static int shortToInt(short s) {
159         return s;
160     }
161 
162     @DataProvider
163     public static Object[][] cases() {
164         return new Object[][] {
165             { JAVA_BOOLEAN, booleanToInt(true),     BOOLEAN_HOB_MASK, BOOLEAN_TO_INT, SAVE_BOOLEAN_AS_INT },
166             { JAVA_BYTE,    byteToInt((byte) 42),   BYTE_HOB_MASK,    BYTE_TO_INT,    SAVE_BYTE_AS_INT    },
167             { JAVA_SHORT,   shortToInt((short) 42), SHORT_HOB_MASK,   SHORT_TO_INT,   SAVE_SHORT_AS_INT   },
168             { JAVA_CHAR,    charToInt('a'),         CHAR_HOB_MASK,    CHAR_TO_INT,    SAVE_CHAR_AS_INT    }
169         };
170     }
171 
172     // test which int values are considered true and false
173     // we currently convert any int with a non-zero first byte to true, otherwise false.
174     @Test(dataProvider = "bools")
175     public void testBool(int testValue, boolean expected) throws Throwable {
176         MemorySegment addr = findNativeOrThrow("test");
177         MethodHandle target = LINKER.downcallHandle(addr, FunctionDescriptor.of(JAVA_BOOLEAN, ADDRESS, JAVA_INT));
178 
179         boolean[] box = new boolean[1];
180         MethodHandle upcallTarget = MethodHandles.insertArguments(SAVE_BOOLEAN, 1, box);
181 
182         try (Arena arena = Arena.ofConfined()) {
183             MemorySegment callback = LINKER.upcallStub(upcallTarget, FunctionDescriptor.ofVoid(JAVA_BOOLEAN), arena);
184             boolean result = (boolean) target.invokeExact(callback, testValue);
185             assertEquals(box[0], expected);
186             assertEquals(result, expected);
187         }
188     }
189 
190     private static void saveBoolean(boolean b, boolean[] box) {
191         box[0] = b;
192     }
193 
194     @DataProvider
195     public static Object[][] bools() {
196         return new Object[][]{
197             { 0b10,          true  }, // zero least significant bit, but non-zero first byte
198             { 0b1_0000_0000, false }  // zero first byte
199         };
200     }
201 }