File: TestNormalize.java

package info (click to toggle)
openjdk-21 21.0.8%2B9-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 823,976 kB
  • sloc: java: 5,613,338; xml: 1,643,607; cpp: 1,296,296; ansic: 420,291; asm: 404,850; objc: 20,994; sh: 15,271; javascript: 11,245; python: 6,895; makefile: 2,362; perl: 357; awk: 351; sed: 172; jsp: 24; csh: 3
file content (203 lines) | stat: -rw-r--r-- 9,420 bytes parent folder | download
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
        };
    }
}