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 }