jdk-24/test/jdk/java/foreign/NativeTestHelper.java
Jorn Vernee e96e191952 8318586: Explicitly handle upcall stub allocation failure
8318653: UpcallTestHelper::runInNewProcess waits for forked process without timeout

Reviewed-by: shade, mcimadamore
2023-11-30 19:26:54 +00:00

334 lines
15 KiB
Java

/*
* Copyright (c) 2020, 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.
*/
import java.lang.foreign.AddressLayout;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.GroupLayout;
import java.lang.foreign.Linker;
import java.lang.foreign.MemoryLayout;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.PaddingLayout;
import java.lang.foreign.SegmentAllocator;
import java.lang.foreign.SequenceLayout;
import java.lang.foreign.StructLayout;
import java.lang.foreign.SymbolLookup;
import java.lang.foreign.UnionLayout;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;
import java.util.random.RandomGenerator;
import static java.lang.foreign.MemoryLayout.PathElement.groupElement;
import static java.lang.foreign.MemoryLayout.PathElement.sequenceElement;
public class NativeTestHelper {
public static final boolean IS_WINDOWS = System.getProperty("os.name").startsWith("Windows");
private static final MethodHandle MH_SAVER;
private static final RandomGenerator DEFAULT_RANDOM;
static {
int seed = Integer.getInteger("NativeTestHelper.DEFAULT_RANDOM.seed", ThreadLocalRandom.current().nextInt());
System.out.println("NativeTestHelper::DEFAULT_RANDOM.seed = " + seed);
System.out.println("Re-run with '-DNativeTestHelper.DEFAULT_RANDOM.seed=" + seed + "' to reproduce");
DEFAULT_RANDOM = new Random(seed);
try {
MH_SAVER = MethodHandles.lookup().findStatic(NativeTestHelper.class, "saver",
MethodType.methodType(Object.class, Object[].class, List.class, AtomicReference.class, SegmentAllocator.class, int.class));
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
}
public static boolean isIntegral(MemoryLayout layout) {
return layout instanceof ValueLayout valueLayout && isIntegral(valueLayout.carrier());
}
static boolean isIntegral(Class<?> clazz) {
return clazz == byte.class || clazz == char.class || clazz == short.class
|| clazz == int.class || clazz == long.class;
}
public static boolean isPointer(MemoryLayout layout) {
return layout instanceof ValueLayout valueLayout && valueLayout.carrier() == MemorySegment.class;
}
public static final Linker LINKER = Linker.nativeLinker();
// the constants below are useful aliases for C types. The type/carrier association is only valid for 64-bit platforms.
/**
* The layout for the {@code bool} C type
*/
public static final ValueLayout.OfBoolean C_BOOL = (ValueLayout.OfBoolean) LINKER.canonicalLayouts().get("bool");
/**
* The layout for the {@code char} C type
*/
public static final ValueLayout.OfByte C_CHAR = (ValueLayout.OfByte) LINKER.canonicalLayouts().get("char");
/**
* The layout for the {@code short} C type
*/
public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) LINKER.canonicalLayouts().get("short");
/**
* The layout for the {@code int} C type
*/
public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) LINKER.canonicalLayouts().get("int");
/**
* The layout for the {@code long long} C type.
*/
public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) LINKER.canonicalLayouts().get("long long");
/**
* The layout for the {@code float} C type
*/
public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) LINKER.canonicalLayouts().get("float");
/**
* The layout for the {@code double} C type
*/
public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) LINKER.canonicalLayouts().get("double");
/**
* The {@code T*} native type.
*/
public static final AddressLayout C_POINTER = ((AddressLayout) LINKER.canonicalLayouts().get("void*"))
.withTargetLayout(MemoryLayout.sequenceLayout(Long.MAX_VALUE, C_CHAR));
/**
* The layout for the {@code size_t} C type
*/
public static final ValueLayout C_SIZE_T = (ValueLayout) LINKER.canonicalLayouts().get("size_t");
// Common layout shared by some tests
// struct S_PDI { void* p0; double p1; int p2; };
public static final MemoryLayout S_PDI_LAYOUT = switch ((int) ValueLayout.ADDRESS.byteSize()) {
case 8 -> MemoryLayout.structLayout(
C_POINTER.withName("p0"),
C_DOUBLE.withName("p1"),
C_INT.withName("p2"),
MemoryLayout.paddingLayout(4));
case 4 -> MemoryLayout.structLayout(
C_POINTER.withName("p0"),
C_DOUBLE.withName("p1"),
C_INT.withName("p2"));
default -> throw new UnsupportedOperationException("Unsupported address size");
};
private static final MethodHandle FREE = LINKER.downcallHandle(
LINKER.defaultLookup().find("free").get(), FunctionDescriptor.ofVoid(C_POINTER));
private static final MethodHandle MALLOC = LINKER.downcallHandle(
LINKER.defaultLookup().find("malloc").get(), FunctionDescriptor.of(C_POINTER, C_LONG_LONG));
public static void freeMemory(MemorySegment address) {
try {
FREE.invokeExact(address);
} catch (Throwable ex) {
throw new IllegalStateException(ex);
}
}
public static MemorySegment allocateMemory(long size) {
try {
return (MemorySegment) MALLOC.invokeExact(size);
} catch (Throwable ex) {
throw new IllegalStateException(ex);
}
}
public static MemorySegment findNativeOrThrow(String name) {
return SymbolLookup.loaderLookup().find(name).orElseThrow();
}
public static MethodHandle downcallHandle(String symbol, FunctionDescriptor desc, Linker.Option... options) {
return LINKER.downcallHandle(findNativeOrThrow(symbol), desc, options);
}
public static MemorySegment upcallStub(Class<?> holder, String name, FunctionDescriptor descriptor) {
return upcallStub(holder, name, descriptor, Arena.ofAuto());
}
public static MemorySegment upcallStub(Class<?> holder, String name, FunctionDescriptor descriptor, Arena arena) {
try {
MethodHandle target = MethodHandles.lookup().findStatic(holder, name, descriptor.toMethodType());
return LINKER.upcallStub(target, descriptor, arena);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static TestValue[] genTestArgs(FunctionDescriptor descriptor, SegmentAllocator allocator) {
return genTestArgs(DEFAULT_RANDOM, descriptor, allocator);
}
public static TestValue[] genTestArgs(RandomGenerator random, FunctionDescriptor descriptor, SegmentAllocator allocator) {
TestValue[] result = new TestValue[descriptor.argumentLayouts().size()];
for (int i = 0; i < result.length; i++) {
result[i] = genTestValue(random, descriptor.argumentLayouts().get(i), allocator);
}
return result;
}
public record TestValue (Object value, Consumer<Object> check) {
public void check(Object actual) { check.accept(actual); }
}
public static TestValue genTestValue(MemoryLayout layout, SegmentAllocator allocator) {
return genTestValue(DEFAULT_RANDOM, layout, allocator);
}
public static TestValue genTestValue(RandomGenerator random, MemoryLayout layout, SegmentAllocator allocator) {
if (layout instanceof StructLayout struct) {
MemorySegment segment = allocator.allocate(struct);
List<Consumer<Object>> fieldChecks = new ArrayList<>();
for (MemoryLayout fieldLayout : struct.memberLayouts()) {
if (fieldLayout instanceof PaddingLayout) continue;
MemoryLayout.PathElement fieldPath = groupElement(fieldLayout.name().orElseThrow());
fieldChecks.add(initField(random, segment, struct, fieldLayout, fieldPath, allocator));
}
return new TestValue(segment, actual -> fieldChecks.forEach(check -> check.accept(actual)));
} else if (layout instanceof UnionLayout union) {
MemorySegment segment = allocator.allocate(union);
List<MemoryLayout> filteredFields = union.memberLayouts().stream()
.filter(l -> !(l instanceof PaddingLayout))
.toList();
int fieldIdx = random.nextInt(filteredFields.size());
MemoryLayout fieldLayout = filteredFields.get(fieldIdx);
MemoryLayout.PathElement fieldPath = groupElement(fieldLayout.name().orElseThrow());
Consumer<Object> check = initField(random, segment, union, fieldLayout, fieldPath, allocator);
return new TestValue(segment, check);
} else if (layout instanceof SequenceLayout array) {
MemorySegment segment = allocator.allocate(array);
List<Consumer<Object>> elementChecks = new ArrayList<>();
for (int i = 0; i < array.elementCount(); i++) {
elementChecks.add(initField(random, segment, array, array.elementLayout(), sequenceElement(i), allocator));
}
return new TestValue(segment, actual -> elementChecks.forEach(check -> check.accept(actual)));
} else if (layout instanceof AddressLayout) {
MemorySegment value = MemorySegment.ofAddress(random.nextLong());
return new TestValue(value, actual -> assertEquals(actual, value));
}else if (layout instanceof ValueLayout.OfByte) {
byte value = (byte) random.nextInt();
return new TestValue(value, actual -> assertEquals(actual, value));
} else if (layout instanceof ValueLayout.OfShort) {
short value = (short) random.nextInt();
return new TestValue(value, actual -> assertEquals(actual, value));
} else if (layout instanceof ValueLayout.OfChar) {
char value = (char) random.nextInt();
return new TestValue(value, actual -> assertEquals(actual, value));
} else if (layout instanceof ValueLayout.OfInt) {
int value = random.nextInt();
return new TestValue(value, actual -> assertEquals(actual, value));
} else if (layout instanceof ValueLayout.OfLong) {
long value = random.nextLong();
return new TestValue(value, actual -> assertEquals(actual, value));
} else if (layout instanceof ValueLayout.OfFloat) {
float value = random.nextFloat();
return new TestValue(value, actual -> assertEquals(actual, value));
} else if (layout instanceof ValueLayout.OfDouble) {
double value = random.nextDouble();
return new TestValue(value, actual -> assertEquals(actual, value));
}
throw new IllegalStateException("Unexpected layout: " + layout);
}
private static Consumer<Object> initField(RandomGenerator random, MemorySegment container, MemoryLayout containerLayout,
MemoryLayout fieldLayout, MemoryLayout.PathElement fieldPath,
SegmentAllocator allocator) {
TestValue fieldValue = genTestValue(random, fieldLayout, allocator);
Consumer<Object> fieldCheck = fieldValue.check();
if (fieldLayout instanceof GroupLayout || fieldLayout instanceof SequenceLayout) {
UnaryOperator<MemorySegment> slicer = slicer(containerLayout, fieldPath);
MemorySegment slice = slicer.apply(container);
slice.copyFrom((MemorySegment) fieldValue.value());
return actual -> fieldCheck.accept(slicer.apply((MemorySegment) actual));
} else {
VarHandle accessor = containerLayout.varHandle(fieldPath);
//set value
accessor.set(container, 0L, fieldValue.value());
return actual -> fieldCheck.accept(accessor.get((MemorySegment) actual, 0L));
}
}
private static UnaryOperator<MemorySegment> slicer(MemoryLayout containerLayout, MemoryLayout.PathElement fieldPath) {
MethodHandle slicer = containerLayout.sliceHandle(fieldPath);
return container -> {
try {
return (MemorySegment) slicer.invokeExact(container, 0L);
} catch (Throwable e) {
throw new IllegalStateException(e);
}
};
}
private static void assertEquals(Object actual, Object expected) {
if (actual.getClass() != expected.getClass()) {
throw new AssertionError("Type mismatch: " + actual.getClass() + " != " + expected.getClass());
}
if (!actual.equals(expected)) {
throw new AssertionError("Not equal: " + actual + " != " + expected);
}
}
/**
* Make an upcall stub that saves its arguments into the given 'ref' array
*
* @param fd function descriptor for the upcall stub
* @param capturedArgs box to save arguments in
* @param arena allocator for making copies of by-value structs
* @param retIdx the index of the argument to return
* @return return the upcall stub
*/
public static MemorySegment makeArgSaverCB(FunctionDescriptor fd, Arena arena,
AtomicReference<Object[]> capturedArgs, int retIdx) {
MethodHandle target = MethodHandles.insertArguments(MH_SAVER, 1, fd.argumentLayouts(), capturedArgs, arena, retIdx);
target = target.asCollector(Object[].class, fd.argumentLayouts().size());
target = target.asType(fd.toMethodType());
return LINKER.upcallStub(target, fd, arena);
}
private static Object saver(Object[] o, List<MemoryLayout> argLayouts, AtomicReference<Object[]> ref, SegmentAllocator allocator, int retArg) {
for (int i = 0; i < o.length; i++) {
if (argLayouts.get(i) instanceof GroupLayout gl) {
MemorySegment ms = (MemorySegment) o[i];
MemorySegment copy = allocator.allocate(gl);
copy.copyFrom(ms);
o[i] = copy;
}
}
ref.set(o);
return retArg != -1 ? o[retArg] : null;
}
}