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