1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
|
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @enablePreview
* @library ../
* @requires jdk.foreign.linker != "UNSUPPORTED"
* @run testng/othervm
* --enable-native-access=ALL-UNNAMED
* -Xbatch
* -XX:CompileCommand=dontinline,TestNormalize::doCall*
* TestNormalize
*/
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_BOOLEAN;
import static java.lang.foreign.ValueLayout.JAVA_BYTE;
import static java.lang.foreign.ValueLayout.JAVA_CHAR;
import static java.lang.foreign.ValueLayout.JAVA_INT;
import static java.lang.foreign.ValueLayout.JAVA_SHORT;
import static org.testng.Assert.assertEquals;
// test normalization of smaller than int primitive types
public class TestNormalize extends NativeTestHelper {
private static final Linker LINKER = Linker.nativeLinker();
private static final MethodHandle SAVE_BOOLEAN_AS_INT;
private static final MethodHandle SAVE_BYTE_AS_INT;
private static final MethodHandle SAVE_SHORT_AS_INT;
private static final MethodHandle SAVE_CHAR_AS_INT;
private static final MethodHandle BOOLEAN_TO_INT;
private static final MethodHandle BYTE_TO_INT;
private static final MethodHandle SHORT_TO_INT;
private static final MethodHandle CHAR_TO_INT;
private static final MethodHandle NATIVE_BOOLEAN_TO_INT;
private static final int BOOLEAN_HOB_MASK = ~0b1;
private static final int BYTE_HOB_MASK = ~0xFF;
private static final int SHORT_HOB_MASK = ~0xFFFF;
private static final int CHAR_HOB_MASK = ~0xFFFF;
private static final MethodHandle SAVE_BOOLEAN;
static {
System.loadLibrary("Normalize");
try {
MethodHandles.Lookup lookup = MethodHandles.lookup();
SAVE_BOOLEAN_AS_INT = lookup.findStatic(TestNormalize.class, "saveBooleanAsInt", MethodType.methodType(void.class, boolean.class, int[].class));
SAVE_BYTE_AS_INT = lookup.findStatic(TestNormalize.class, "saveByteAsInt", MethodType.methodType(void.class, byte.class, int[].class));
SAVE_SHORT_AS_INT = lookup.findStatic(TestNormalize.class, "saveShortAsInt", MethodType.methodType(void.class, short.class, int[].class));
SAVE_CHAR_AS_INT = lookup.findStatic(TestNormalize.class, "saveCharAsInt", MethodType.methodType(void.class, char.class, int[].class));
BOOLEAN_TO_INT = lookup.findStatic(TestNormalize.class, "booleanToInt", MethodType.methodType(int.class, boolean.class));
BYTE_TO_INT = lookup.findStatic(TestNormalize.class, "byteToInt", MethodType.methodType(int.class, byte.class));
SHORT_TO_INT = lookup.findStatic(TestNormalize.class, "shortToInt", MethodType.methodType(int.class, short.class));
CHAR_TO_INT = lookup.findStatic(TestNormalize.class, "charToInt", MethodType.methodType(int.class, char.class));
NATIVE_BOOLEAN_TO_INT = LINKER.downcallHandle(findNativeOrThrow("int_identity"), FunctionDescriptor.of(JAVA_INT, JAVA_BOOLEAN));
SAVE_BOOLEAN = lookup.findStatic(TestNormalize.class, "saveBoolean", MethodType.methodType(void.class, boolean.class, boolean[].class));
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
// The idea of this test is that we pass a 'dirty' int value down to native code, and then receive it back
// as the argument to an upcall, as well as the result of the downcall, but with a sub-int type (boolean, byte, short, char).
// When we do either of those, argument normalization should take place, so that the resulting value is sane (1).
// After that we convert the value back to int again, the JVM can/will skip value normalization here.
// We then check the high order bits of the resulting int. If argument normalization took place at (1), they should all be 0.
@Test(dataProvider = "cases")
public void testNormalize(ValueLayout layout, int testValue, int hobMask, MethodHandle toInt, MethodHandle saver) throws Throwable {
// use actual type as parameter type to test upcall arg normalization
FunctionDescriptor upcallDesc = FunctionDescriptor.ofVoid(layout);
// use actual type as return type to test downcall return normalization
FunctionDescriptor downcallDesc = FunctionDescriptor.of(layout, ADDRESS, JAVA_INT);
MemorySegment target = findNativeOrThrow("test");
MethodHandle downcallHandle = LINKER.downcallHandle(target, downcallDesc);
downcallHandle = MethodHandles.filterReturnValue(downcallHandle, toInt);
try (Arena arena = Arena.ofConfined()) {
int[] box = new int[1];
saver = MethodHandles.insertArguments(saver, 1, box);
MemorySegment upcallStub = LINKER.upcallStub(saver, upcallDesc, arena);
int dirtyValue = testValue | hobMask; // set all bits that should not be set
// test after JIT as well
for (int i = 0; i < 20_000; i++) {
doCall(downcallHandle, upcallStub, box, dirtyValue, hobMask);
}
}
}
private static void doCall(MethodHandle downcallHandle, MemorySegment upcallStub,
int[] box, int dirtyValue, int hobMask) throws Throwable {
int result = (int) downcallHandle.invokeExact(upcallStub, dirtyValue);
assertEquals(box[0] & hobMask, 0); // check normalized upcall arg
assertEquals(result & hobMask, 0); // check normalized downcall return value
}
public static void saveBooleanAsInt(boolean b, int[] box) {
box[0] = booleanToInt(b);
}
public static void saveByteAsInt(byte b, int[] box) {
box[0] = byteToInt(b);
}
public static void saveShortAsInt(short s, int[] box) {
box[0] = shortToInt(s);
}
public static void saveCharAsInt(char c, int[] box) {
box[0] = charToInt(c);
}
public static int booleanToInt(boolean b) {
try {
return (int) NATIVE_BOOLEAN_TO_INT.invokeExact(b); // FIXME do in pure Java?
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
public static int byteToInt(byte b) {
return b;
}
public static int charToInt(char c) {
return c;
}
public static int shortToInt(short s) {
return s;
}
@DataProvider
public static Object[][] cases() {
return new Object[][] {
{ JAVA_BOOLEAN, booleanToInt(true), BOOLEAN_HOB_MASK, BOOLEAN_TO_INT, SAVE_BOOLEAN_AS_INT },
{ JAVA_BYTE, byteToInt((byte) 42), BYTE_HOB_MASK, BYTE_TO_INT, SAVE_BYTE_AS_INT },
{ JAVA_SHORT, shortToInt((short) 42), SHORT_HOB_MASK, SHORT_TO_INT, SAVE_SHORT_AS_INT },
{ JAVA_CHAR, charToInt('a'), CHAR_HOB_MASK, CHAR_TO_INT, SAVE_CHAR_AS_INT }
};
}
// test which int values are considered true and false
// we currently convert any int with a non-zero first byte to true, otherwise false.
@Test(dataProvider = "bools")
public void testBool(int testValue, boolean expected) throws Throwable {
MemorySegment addr = findNativeOrThrow("test");
MethodHandle target = LINKER.downcallHandle(addr, FunctionDescriptor.of(JAVA_BOOLEAN, ADDRESS, JAVA_INT));
boolean[] box = new boolean[1];
MethodHandle upcallTarget = MethodHandles.insertArguments(SAVE_BOOLEAN, 1, box);
try (Arena arena = Arena.ofConfined()) {
MemorySegment callback = LINKER.upcallStub(upcallTarget, FunctionDescriptor.ofVoid(JAVA_BOOLEAN), arena);
boolean result = (boolean) target.invokeExact(callback, testValue);
assertEquals(box[0], expected);
assertEquals(result, expected);
}
}
private static void saveBoolean(boolean b, boolean[] box) {
box[0] = b;
}
@DataProvider
public static Object[][] bools() {
return new Object[][]{
{ 0b10, true }, // zero least significant bit, but non-zero first byte
{ 0b1_0000_0000, false } // zero first byte
};
}
}
|