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 }