JavaOpenCL64

This commit is contained in:
2026-08-07 19:33:10 +08:00
commit aedd643d35
278 changed files with 25848 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/graalvm-jdk-24+36.1">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="libs/bcel-6.10.0.jar"/>
<classpathentry kind="lib" path="libs/annotations-26.1.0.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+1
View File
@@ -0,0 +1 @@
/bin/
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>JavaOpenCL64</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8
+15
View File
@@ -0,0 +1,15 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=18
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=18
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=18
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
package club.doki7.ffm;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.FunctionDescriptor;
import java.lang.invoke.MethodHandle;
@FunctionalInterface
public interface FunctionLoader {
@Nullable MethodHandle apply(
@NotNull String name,
@NotNull FunctionDescriptor descriptor
);
}
+26
View File
@@ -0,0 +1,26 @@
package club.doki7.ffm;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import club.doki7.ffm.ptr.IntPtr;
import org.jetbrains.annotations.NotNull;
import java.lang.foreign.MemorySegment;
/// Represents a pointer to native memory.
@ValueBasedCandidate
public interface IPointer {
/// The implementation should always provide a not-null {@link MemorySegment}
/// ({@code segment() != null && !segment().equals(MemorySegment.NULL)}). The segment must be
/// properly aligned according to the pointee type. To represent null pointer, user
/// should use a Java {@code null} {@link IPointer}.
///
/// Generally speaking, the segment's size does not need to be multiple of the size of the
/// pointee type. For example, a {@link IntPtr} can point to a segment of
/// 7 bytes. The trailing bytes can be safely ignored by most algorithms and FFI functions.
///
/// If the memory segment is even not big enough to hold a single element of the pointee type,
/// the segment is simply considered "empty". This is just like allocating 0 bytes with C/C++
/// {@code malloc(0)} (on some implementation): the resulting pointer is not {@code NULL} and
/// valid, but you cannot read or write anything.
@NotNull MemorySegment segment();
}
+189
View File
@@ -0,0 +1,189 @@
package club.doki7.ffm;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.library.JavaSystemLibrary;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.util.Objects;
public enum LibcArena implements Arena {
INSTANCE;
/// Allocates memory using system libc {@code aligned_alloc}
///
/// Note that you're in charge of freeing the memory using {@link LibcArena#free}, otherwise
/// there will be a memory leak. Be extra careful when using {@link LibcArena} in combination
/// with `allocate` series methods.
///
/// @param byteSize The size of the memory to allocate
/// @param byteAlignment The alignment of the memory to allocate
/// @return A {@link MemorySegment} representing the allocated memory with all bytes zeroed
/// @throws IllegalArgumentException If the byte size or alignment is invalid
/// @throws OutOfMemoryError If the memory allocation fails
@Override
public @NotNull MemorySegment allocate(long byteSize, long byteAlignment) {
if (byteSize <= 0 || byteAlignment <= 0 || (byteAlignment & (byteAlignment - 1)) != 0) {
throw new IllegalArgumentException("Invalid byte size or alignment");
}
if (HANDLE$aligned_alloc == null) {
return allocateLegacy(byteSize, byteAlignment);
}
MemorySegment ms;
try {
ms = (MemorySegment) HANDLE$aligned_alloc.invokeExact(
MemorySegment.ofAddress(byteAlignment),
MemorySegment.ofAddress(byteSize)
);
} catch (Throwable e) {
throw new RuntimeException("Failed to allocate memory", e);
}
if (ms.equals(MemorySegment.NULL)) {
throw new OutOfMemoryError("Failed allocating memory with aligned_alloc");
}
ms = ms.reinterpret(byteSize);
ms.fill((byte) 0);
return ms;
}
/// Frees memory that was allocated by {@link LibcArena#allocate(long, long)}.
///
/// @param ms The memory segment to free
public void free(@NotNull MemorySegment ms) {
if (ms.equals(MemorySegment.NULL)) {
return;
}
if (HANDLE$aligned_alloc == null) {
freeLegacy(ms);
return;
}
try {
Objects.requireNonNull(HANDLE$free).invokeExact(ms);
} catch (Throwable e) {
throw new RuntimeException("Failed to free memory", e);
}
}
/// Frees memory that was allocated by libc allocator, but not via
/// {@link LibcArena#allocate(long, long)}.
///
/// Some libraries, like `stb_vorbis`, may allocate memory using libc allocators internally, and
/// require you to free that memory using `free` from the same libc implementation. The best way
/// to do this is to use the `free` method encapsulation provided by that library integration.
/// But if that is not available, this method can be used as an alternative with some risk.
///
/// This function is marked as {@link Unsafe}, because if the memory was not exactly allocated
/// by the same allocator {@link LibcArena} found, it will lead to undefined behavior. This
/// relates with platforms, build system and many other factors. Double check before really
/// doing this.
@Unsafe
public void freeNonAllocated(@NotNull MemorySegment ms) {
if (ms.equals(MemorySegment.NULL)) {
return;
}
try {
Objects.requireNonNull(HANDLE$free).invokeExact(ms);
} catch (Throwable e) {
throw new RuntimeException("Failed to free non-allocated memory", e);
}
}
@Override
public @Nullable MemorySegment.Scope scope() {
return null;
}
@Override
public void close() {
throw new UnsupportedOperationException("Cannot close LibcArena");
}
private static MemorySegment allocateLegacy(long byteSize, long byteAlignment) {
final long pointerSize = ValueLayout.ADDRESS.byteSize();
final long totalSize = byteSize + byteAlignment - 1 + pointerSize;
MemorySegment rawMS;
try {
MethodHandle hMalloc = Objects.requireNonNull(HANDLE$malloc);
rawMS = (MemorySegment) hMalloc.invokeExact(MemorySegment.ofAddress(totalSize));
} catch (Throwable e) {
throw new RuntimeException("Failed to allocate memory using malloc", e);
}
if (rawMS.equals(MemorySegment.NULL)) {
throw new OutOfMemoryError("Failed allocating memory with malloc");
}
final long rawAddress = rawMS.address();
final long alignedAddress = (rawAddress + pointerSize + byteAlignment - 1) & -byteAlignment;
final long metadataAddress = alignedAddress - pointerSize;
MemorySegment ms = MemorySegment.ofAddress(alignedAddress).reinterpret(byteSize);
MemorySegment metadata = MemorySegment.ofAddress(metadataAddress).reinterpret(pointerSize);
metadata.set(ValueLayout.ADDRESS, 0, rawMS);
ms.fill((byte) 0);
return ms;
}
private static void freeLegacy(@NotNull MemorySegment ms) {
if (ms.equals(MemorySegment.NULL)) {
return;
}
final long pointerSize = ValueLayout.ADDRESS.byteSize();
final long metadataAddress = ms.address() - pointerSize;
MemorySegment metadata = MemorySegment.ofAddress(metadataAddress).reinterpret(pointerSize);
MemorySegment rawMS = metadata.get(ValueLayout.ADDRESS, 0);
if (rawMS.equals(MemorySegment.NULL)) {
throw new IllegalStateException("Memory segment does not have allocated memory");
}
try {
Objects.requireNonNull(HANDLE$free).invokeExact(rawMS);
} catch (Throwable e) {
throw new RuntimeException("Failed to free memory", e);
}
}
private static final FunctionDescriptor DESCRIPTOR$aligned_alloc = FunctionDescriptor.of(
ValueLayout.ADDRESS,
NativeLayout.C_SIZE_T,
NativeLayout.C_SIZE_T
);
private static final FunctionDescriptor DESCRIPTOR$malloc = FunctionDescriptor.of(
ValueLayout.ADDRESS,
NativeLayout.C_SIZE_T
);
private static final FunctionDescriptor DESCRIPTOR$free = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS
);
private static final @Nullable MethodHandle HANDLE$aligned_alloc = RawFunctionLoader.link(
JavaSystemLibrary.INSTANCE.load("aligned_alloc"),
DESCRIPTOR$aligned_alloc
);
private static final @Nullable MethodHandle HANDLE$malloc = RawFunctionLoader.link(
JavaSystemLibrary.INSTANCE.load("malloc"),
DESCRIPTOR$malloc
);
private static final MethodHandle HANDLE$free = RawFunctionLoader.link(
JavaSystemLibrary.INSTANCE.load("free"),
DESCRIPTOR$free
);
}
+46
View File
@@ -0,0 +1,46 @@
package club.doki7.ffm;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
@Deprecated(forRemoval = true, since = "0.2.4")
public final class Loader {
private static final Linker nativeLinker = Linker.nativeLinker();
private static final SymbolLookup stdlibLookup = nativeLinker.defaultLookup();
private static final SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
@Deprecated(forRemoval = true, since = "0.2.4")
public static @NotNull MethodHandle loadFunction(String name, FunctionDescriptor descriptor) {
return loaderLookup.find(name)
.or(() -> stdlibLookup.find(name))
.map(segment -> RawFunctionLoader.link(segment, descriptor))
.orElseThrow(() -> new RuntimeException("native function " + name + " not found"));
}
@Deprecated(forRemoval = true, since = "0.2.4")
public static @Nullable MethodHandle loadFunctionOrNull(String name, FunctionDescriptor descriptor) {
return loaderLookup.find(name)
.or(() -> stdlibLookup.find(name))
.map(segment -> RawFunctionLoader.link(segment, descriptor))
.orElse(null);
}
@Deprecated(forRemoval = true, since = "0.2.4")
public static @NotNull MemorySegment loadFunction(String name) {
return loaderLookup.find(name)
.or(() -> stdlibLookup.find(name))
.orElseThrow(() -> new RuntimeException("native function " + name + " not found"));
}
@Deprecated(forRemoval = true, since = "0.2.4")
public static @Nullable MemorySegment loadFunctionOrNull(String name) {
return loaderLookup.find(name)
.or(() -> stdlibLookup.find(name))
.orElse(null);
}
private Loader() {}
}
+161
View File
@@ -0,0 +1,161 @@
package club.doki7.ffm;
import club.doki7.ffm.annotation.Unsigned;
import org.jetbrains.annotations.NotNull;
import java.lang.foreign.*;
import java.util.ArrayList;
import java.util.List;
public final class NativeLayout {
/// Memory layout of current JVM platform C {@code size_t} type.
///
/// Currently, this field is set to {@link ValueLayout#ADDRESS}, whose JavaDoc claims that it
/// has the same size and alignment with a C {@code size_t} type.
public static final @NotNull ValueLayout C_SIZE_T = ValueLayout.ADDRESS;
public static final int POINTER_SIZE = (int) C_SIZE_T.byteSize();
/// Memory layout of current JVM platform C {@code long} type.
///
/// Currently, all 32bit platforms will use {@link ValueLayout#JAVA_INT}. For 64bit platforms,
/// Windows will use {@link ValueLayout#JAVA_INT}, while other platforms will use
/// {@link ValueLayout#JAVA_LONG}.
///
/// The detection algorithm came from LWJGL3.
/// @see <a href="https://github.com/LWJGL/lwjgl3/blob/813400f21ebfce5a9e1566cbf8ff96ca1d8f4921/modules/lwjgl/core/src/main/java/org/lwjgl/system/Pointer.java">lwjgl/core/src/main/java/org/lwgjl/system/Pointer.java</a>
public static final @NotNull ValueLayout C_LONG;
public static final int C_LONG_SIZE;
/// Memory layout of current JVM platform C {@code wchar_t} type.
///
/// Currently, on Windows it is {@link ValueLayout#JAVA_SHORT} (2 bytes), while on other
/// platforms it is {@link ValueLayout#JAVA_INT} (4 bytes).
public static final @NotNull ValueLayout WCHAR_T;
public static final int WCHAR_SIZE;
public static final @Unsigned int UINT32_MAX = (~0);
public static final @Unsigned long UINT64_MAX = (~0L);
// FIXME: move this to somewhere else
private static boolean isWindows() {
return System.getProperty("os.name").toLowerCase().contains("windows");
}
static {
if (POINTER_SIZE == 4) {
// On typical 32bit platforms, long is 4 bytes
C_LONG = ValueLayout.JAVA_INT;
}
else if (POINTER_SIZE == 8) {
if (isWindows()) {
// On 64bit Windows, long is 4 bytes
C_LONG = ValueLayout.JAVA_INT;
} else {
// Otherwise, long is 8 bytes
C_LONG = ValueLayout.JAVA_LONG;
}
}
else {
throw new RuntimeException(String.format("unsupported pointer size: %d", POINTER_SIZE));
}
C_LONG_SIZE = (int) C_LONG.byteSize();
WCHAR_T = isWindows()
? ValueLayout.JAVA_SHORT
: ValueLayout.JAVA_INT;
WCHAR_SIZE = (int) WCHAR_T.byteSize();
}
public static long readCLong(@NotNull MemorySegment segment, long offset) {
if (C_LONG == ValueLayout.JAVA_INT) {
return segment.get(ValueLayout.JAVA_INT, offset);
} else {
return segment.get(ValueLayout.JAVA_LONG, offset);
}
}
public static void writeCLong(@NotNull MemorySegment segment, long offset, long value) {
if (C_LONG == ValueLayout.JAVA_INT) {
segment.set(ValueLayout.JAVA_INT, offset, (int) value);
} else {
segment.set(ValueLayout.JAVA_LONG, offset, value);
}
}
public static @Unsigned long readCSizeT(@NotNull MemorySegment segment, long offset) {
if (C_SIZE_T == ValueLayout.JAVA_INT) {
return segment.get(ValueLayout.JAVA_INT, offset);
} else {
return segment.get(ValueLayout.JAVA_LONG, offset);
}
}
public static void writeCSizeT(@NotNull MemorySegment segment, long offset, @Unsigned long value) {
if (C_SIZE_T == ValueLayout.JAVA_INT) {
segment.set(ValueLayout.JAVA_INT, offset, (int) value);
} else {
segment.set(ValueLayout.JAVA_LONG, offset, value);
}
}
public static @Unsigned int readWCharT(@NotNull MemorySegment segment, long offset) {
if (WCHAR_T == ValueLayout.JAVA_INT) {
return segment.get(ValueLayout.JAVA_INT, offset);
} else {
return segment.get(ValueLayout.JAVA_SHORT, offset);
}
}
public static void writeWCharT(@NotNull MemorySegment segment, long offset, int value) {
if (WCHAR_T == ValueLayout.JAVA_INT) {
segment.set(ValueLayout.JAVA_INT, offset, value);
} else {
segment.set(ValueLayout.JAVA_SHORT, offset, (short) value);
}
}
/// Unlike {@link MemoryLayout#structLayout MemoryLayout.structLayout}, this function will
/// automatically compute and add padding to the layout to ensure that each element is properly
/// aligned. The resulting layout should be the same with a C struct layout.
///
/// @param elements the elements of the struct
/// @return the struct layout
public static @NotNull StructLayout structLayout(@NotNull MemoryLayout... elements) {
long currentSize = 0;
long maxAlignment = 0;
List<MemoryLayout> paddedElements = new ArrayList<>();
for (MemoryLayout element : elements) {
long alignment = element.byteAlignment();
if (alignment > maxAlignment) {
maxAlignment = alignment;
}
long padding = (alignment - (currentSize % alignment)) % alignment;
if (padding != 0) {
paddedElements.add(MemoryLayout.paddingLayout(padding));
currentSize += padding;
}
paddedElements.add(element);
currentSize += element.byteSize();
}
if (maxAlignment != 0) {
long padding = (maxAlignment - (currentSize % maxAlignment)) % maxAlignment;
if (padding != 0) {
paddedElements.add(MemoryLayout.paddingLayout(padding));
}
}
MemoryLayout[] paddedElementsArray = paddedElements.toArray(new MemoryLayout[0]);
return MemoryLayout.structLayout(paddedElementsArray);
}
/// Currently forwards to {@link MemoryLayout#unionLayout}.
public static @NotNull UnionLayout unionLayout(@NotNull MemoryLayout... elements) {
return MemoryLayout.unionLayout(elements);
}
private NativeLayout() {}
}
+41
View File
@@ -0,0 +1,41 @@
package club.doki7.ffm;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.invoke.MethodHandle;
import java.nio.file.LinkOption;
@FunctionalInterface
public interface RawFunctionLoader {
@NotNull MemorySegment apply(@NotNull String name);
default @NotNull MemorySegment load(@NotNull String name) {
return apply(name);
}
Linker nativeLinker = Linker.nativeLinker();
static @Nullable MethodHandle link(@NotNull MemorySegment segment, FunctionDescriptor descriptor) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
return nativeLinker.downcallHandle(segment, descriptor);
}
static @Nullable MethodHandle linkWithOptions(
@NotNull MemorySegment segment,
FunctionDescriptor descriptor,
Linker.Option ...options
) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
return nativeLinker.downcallHandle(segment, descriptor, options);
}
}
@@ -0,0 +1,9 @@
package club.doki7.ffm.annotation;
import java.lang.annotation.Documented;
/// Marker annotation, indicating that an integral value is a C bitmask type.
@Documented
public @interface Bitmask {
Class<?> value() default void.class;
}
@@ -0,0 +1,9 @@
package club.doki7.ffm.annotation;
import java.lang.annotation.Documented;
/// Marker annotation, indicating that an integral value is a C enumeration type.
@Documented
public @interface EnumType {
Class<?> value() default void.class;
}
@@ -0,0 +1,9 @@
package club.doki7.ffm.annotation;
import java.lang.annotation.Documented;
/// Marker annotation, indicating that a value is actually of an aliased native type.
@Documented
public @interface NativeType {
String value() default "";
}
@@ -0,0 +1,11 @@
package club.doki7.ffm.annotation;
import java.lang.annotation.Documented;
/// Marker annotation, indicating that a pointer value ({@link java.lang.foreign.MemorySegment} or
/// {@code long}) is a pointer to a specific type.
@Documented
public @interface Pointer {
Class<?> target() default Object.class;
String comment() default "";
}
+11
View File
@@ -0,0 +1,11 @@
package club.doki7.ffm.annotation;
import java.lang.annotation.Documented;
/// Marker annotation, indicating that the annotated method is Unsafe.
///
/// An {@link Unsafe} method, if misused, could cause undefined behaviour such as buffer overflow or
/// null pointer dereference, which could in turn cause data corruption or even crash the JVM.
@Documented
public @interface Unsafe {
}
@@ -0,0 +1,12 @@
package club.doki7.ffm.annotation;
import java.lang.annotation.Documented;
/// Marker annotation, indicating that the annotated record constructor is Unsafe
///
/// An {@link UnsafeConstructor} constructor, if misused, could cause undefined behaviour such as
/// buffer overflow or null pointer dereference, which could in turn cause data corruption or even
/// crash the JVM.
@Documented
public @interface UnsafeConstructor {
}
@@ -0,0 +1,10 @@
package club.doki7.ffm.annotation;
import java.lang.annotation.Documented;
/// Marker annotation, indicating that the annotated integral value should be treated as Unsigned.
///
/// For example, if an {@code int} is annotated with this annotation, you should use
/// {@link Integer#toUnsignedString} to get its string representation, and so on.
@Documented
public @interface Unsigned {}
@@ -0,0 +1,12 @@
package club.doki7.ffm.annotation;
import java.lang.annotation.Documented;
/// Marker annotation, indicating the annotated type will consequentially become {@code @ValueBased}
/// once Project Valhalla gets stabilized.
///
/// In order to maintain compatibility, it's better not to rely on object hash identity of these
/// annotated types.
@Documented
public @interface ValueBasedCandidate {
}
@@ -0,0 +1,2 @@
/// Auxiliary annotation.
package club.doki7.ffm.annotation;
+202
View File
@@ -0,0 +1,202 @@
package club.doki7.ffm.bits;
import club.doki7.ffm.annotation.Unsigned;
import org.jetbrains.annotations.NotNull;
import java.lang.foreign.AddressLayout;
import java.lang.foreign.MemorySegment;
import java.nio.ByteOrder;
public final class BitfieldUtil {
public static @Unsigned boolean readBit(
@NotNull MemorySegment segment,
@Unsigned int bit
) {
return switch ((int) segment.byteSize()) {
case 1 -> readBit(segment.get(AddressLayout.JAVA_BYTE, 0), bit);
case 2 -> readBit(segment.get(AddressLayout.JAVA_SHORT, 0), bit);
case 4 -> readBit(segment.get(AddressLayout.JAVA_INT, 0), bit);
default -> throw new IllegalArgumentException("Unsupported size: " + segment.byteSize());
};
}
public static @Unsigned int readBits(
@NotNull MemorySegment segment,
@Unsigned int startBit,
@Unsigned int endBit
) {
return switch ((int) segment.byteSize()) {
case 1 -> readBits(segment.get(AddressLayout.JAVA_BYTE, 0), startBit, endBit);
case 2 -> readBits(segment.get(AddressLayout.JAVA_SHORT, 0), startBit, endBit);
case 4 -> readBits(segment.get(AddressLayout.JAVA_INT, 0), startBit, endBit);
default -> throw new IllegalArgumentException("Unsupported size: " + segment.byteSize());
};
}
public static void writeBit(
@NotNull MemorySegment segment,
@Unsigned int bit,
boolean bitValue
) {
switch ((int) segment.byteSize()) {
case 1 -> segment.set(AddressLayout.JAVA_BYTE, 0, writeBit(segment.get(AddressLayout.JAVA_BYTE, 0), bit, bitValue));
case 2 -> segment.set(AddressLayout.JAVA_SHORT, 0, writeBit(segment.get(AddressLayout.JAVA_SHORT, 0), bit, bitValue));
case 4 -> segment.set(AddressLayout.JAVA_INT, 0, writeBit(segment.get(AddressLayout.JAVA_INT, 0), bit, bitValue));
default -> throw new IllegalArgumentException("Unsupported size: " + segment.byteSize());
}
}
public static void writeBits(
@NotNull MemorySegment segment,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned int bits
) {
switch ((int) segment.byteSize()) {
case 1 -> segment.set(AddressLayout.JAVA_BYTE, 0, writeBits(segment.get(AddressLayout.JAVA_BYTE, 0), startBit, endBit, (byte) bits));
case 2 -> segment.set(AddressLayout.JAVA_SHORT, 0, writeBits(segment.get(AddressLayout.JAVA_SHORT, 0), startBit, endBit, (short) bits));
case 4 -> segment.set(AddressLayout.JAVA_INT, 0, writeBits(segment.get(AddressLayout.JAVA_INT, 0), startBit, endBit, bits));
default -> throw new IllegalArgumentException("Unsupported size: " + segment.byteSize());
}
}
public static boolean readBit(
@Unsigned byte value,
@Unsigned int bit
) {
checkBitRange(bit, Byte.SIZE);
return impl.readBitUnchecked(value, bit);
}
public static boolean readBit(
@Unsigned short value,
@Unsigned int bit
) {
checkBitRange(bit, Short.SIZE);
return impl.readBitUnchecked(value, bit);
}
public static boolean readBit(
@Unsigned int value,
@Unsigned int bit
) {
checkBitRange(bit, Integer.SIZE);
return impl.readBitUnchecked(value, bit);
}
public static @Unsigned byte readBits(
@Unsigned byte value,
@Unsigned int startBit,
@Unsigned int endBit
) {
checkBitRange(startBit, endBit, Byte.SIZE);
return impl.readBitsUnchecked(value, startBit, endBit);
}
public static @Unsigned short readBits(
@Unsigned short value,
@Unsigned int startBit,
@Unsigned int endBit
) {
checkBitRange(startBit, endBit, Short.SIZE);
return impl.readBitsUnchecked(value, startBit, endBit);
}
public static @Unsigned int readBits(
@Unsigned int value,
@Unsigned int startBit,
@Unsigned int endBit
) {
checkBitRange(startBit, endBit, Integer.SIZE);
return impl.readBitsUnchecked(value, startBit, endBit);
}
public static @Unsigned byte writeBit(
@Unsigned byte value,
@Unsigned int bit,
boolean bitValue
) {
checkBitRange(bit, Byte.SIZE);
return impl.writeBitUnchecked(value, bit, bitValue);
}
public static @Unsigned short writeBit(
@Unsigned short value,
@Unsigned int bit,
boolean bitValue
) {
checkBitRange(bit, Short.SIZE);
return impl.writeBitUnchecked(value, bit, bitValue);
}
public static @Unsigned int writeBit(
@Unsigned int value,
@Unsigned int bit,
boolean bitValue
) {
checkBitRange(bit, Integer.SIZE);
return impl.writeBitUnchecked(value, bit, bitValue);
}
public static @Unsigned byte writeBits(
@Unsigned byte value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned byte bits
) {
checkBitRange(startBit, endBit, Byte.SIZE);
return impl.writeBitsUnchecked(value, startBit, endBit, bits);
}
public static @Unsigned short writeBits(
@Unsigned short value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned short bits
) {
checkBitRange(startBit, endBit, Short.SIZE);
return impl.writeBitsUnchecked(value, startBit, endBit, bits);
}
public static @Unsigned int writeBits(
@Unsigned int value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned int bits
) {
checkBitRange(startBit, endBit, Integer.SIZE);
return impl.writeBitsUnchecked(value, startBit, endBit, bits);
}
private static void checkBitRange(int bit, int size) {
if (bit < 0 || bit >= size) {
throw new IllegalArgumentException("bit must be no less than 0 and less than " + size);
}
}
private static void checkBitRange(int startBit, int endBit, int size) {
if (startBit < 0 || endBit > size) {
throw new IllegalArgumentException("startBit must be no less than 0 and endBit must be no more than " + size);
}
if (startBit >= endBit) {
throw new IllegalArgumentException("startBit must be less than endBit");
}
}
private static final @NotNull IBitfieldUtilImpl impl;
static {
// On most platforms, the bitfields are packed from right to left. However, on ARM platform,
// the bitfields packing depends on the endianness.
String arch = System.getProperty("os.arch").toLowerCase();
if (arch.contains("arm") || arch.contains("aarch")) {
if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
impl = new BitfieldUtilImplL2R();
} else {
impl = new BitfieldUtilImplR2L();
}
} else {
// May need future refinement, but adequate for now.
impl = new BitfieldUtilImplR2L();
}
}
}
@@ -0,0 +1,158 @@
package club.doki7.ffm.bits;
import club.doki7.ffm.annotation.Unsigned;
/// On certain platforms, bitfields are packed from left to right.
///
/// For example, for the following
/// C struct:
///
/// {@snippet lang=c :
/// typedef struct {
/// uint8_t r : 3; // startBit = 0, endBit = 3
/// uint8_t g : 3; // startBit = 3, endBit = 6
/// uint8_t b : 2; // startBit = 6, endBit = 8
/// } rgb332_t;
/// }
///
/// The bitfield is packed as such:
///
/// {@snippet :
/// bit 765 432 10
/// value RRR GGG BB
/// }
///
/// So to read the {@code G} field, we need to shift the value right by 2 ({@code = 8 - endBit})
/// bits and mask it with 0x07 ({@code = (1 << (endBit - startBit)) - 1}).
final class BitfieldUtilImplL2R implements IBitfieldUtilImpl {
@Override
public boolean readBitUnchecked(@Unsigned byte value, @Unsigned int bit) {
return ((value >>> (Byte.SIZE - bit - 1)) & 0x01) != 0;
}
@Override
public boolean readBitUnchecked(@Unsigned short value, @Unsigned int bit) {
return ((value >>> (Short.SIZE - bit - 1)) & 0x01) != 0;
}
@Override
public boolean readBitUnchecked(@Unsigned int value, @Unsigned int bit) {
return ((value >>> (Integer.SIZE - bit - 1)) & 0x01) != 0;
}
@Override
public @Unsigned byte readBitsUnchecked(
@Unsigned byte value,
@Unsigned int startBit,
@Unsigned int endBit
) {
int shiftRight = Byte.SIZE - endBit;
int mask = (1 << (endBit - startBit)) - 1;
return (byte) ((value >>> shiftRight) & mask);
}
@Override
public @Unsigned short readBitsUnchecked(
@Unsigned short value,
@Unsigned int startBit,
@Unsigned int endBit
) {
int shiftRight = Short.SIZE - endBit;
int mask = (1 << (endBit - startBit)) - 1;
return (short) ((value >>> shiftRight) & mask);
}
@Override
public int readBitsUnchecked(
@Unsigned int value,
@Unsigned int startBit,
@Unsigned int endBit
) {
int shiftRight = Integer.SIZE - endBit;
long mask = (1L << (endBit - startBit)) - 1;
return (int) ((value >>> shiftRight) & mask);
}
@Override
public @Unsigned byte writeBitUnchecked(
@Unsigned byte value,
@Unsigned int bit,
boolean bitValue
) {
if (bitValue) {
return (byte) (value | (1 << (Byte.SIZE - bit - 1)));
} else {
return (byte) (value & ~(1 << (Byte.SIZE - bit - 1)));
}
}
@Override
public @Unsigned short writeBitUnchecked(
@Unsigned short value,
@Unsigned int bit,
boolean bitValue
) {
if (bitValue) {
return (short) (value | (1 << (Short.SIZE - bit - 1)));
} else {
return (short) (value & ~(1 << (Short.SIZE - bit - 1)));
}
}
@Override
public @Unsigned int writeBitUnchecked(
@Unsigned int value,
@Unsigned int bit,
boolean bitValue
) {
if (bitValue) {
return (value | (1 << (Integer.SIZE - bit - 1)));
} else {
return (value & ~(1 << (Integer.SIZE - bit - 1)));
}
}
@Override
public @Unsigned byte writeBitsUnchecked(
@Unsigned byte value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned byte bits
) {
int shiftLeft = Byte.SIZE - endBit;
int mask = (1 << (endBit - startBit)) - 1;
int maskShifted = mask << shiftLeft;
int bitsShifted = (bits & mask) << shiftLeft;
return (byte) ((value & ~maskShifted) | bitsShifted);
}
@Override
public @Unsigned short writeBitsUnchecked(
@Unsigned short value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned short bits
) {
int shiftLeft = Short.SIZE - endBit;
int mask = (1 << (endBit - startBit)) - 1;
int maskShifted = mask << shiftLeft;
int bitsShifted = (bits & mask) << shiftLeft;
return (short) ((value & ~maskShifted) | bitsShifted);
}
@Override
public @Unsigned int writeBitsUnchecked(
@Unsigned int value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned int bits
) {
int shiftLeft = Integer.SIZE - endBit;
long mask = (1L << (endBit - startBit)) - 1;
long maskShifted = mask << shiftLeft;
long bitsShifted = (bits & mask) << shiftLeft;
return (int) ((value & ~maskShifted) | bitsShifted);
}
BitfieldUtilImplL2R() {}
}
@@ -0,0 +1,152 @@
package club.doki7.ffm.bits;
import club.doki7.ffm.annotation.Unsigned;
/// On certain platforms, bitfields are packed from right to left.
///
/// For example, for the following
/// C struct:
///
/// {@snippet lang=c :
/// typedef struct {
/// uint8_t r : 3; // startBit = 0, endBit = 3
/// uint8_t g : 3; // startBit = 3, endBit = 6
/// uint8_t b : 2; // startBit = 6, endBit = 8
/// } rgb332_t;
/// }
///
/// The bitfield is packed as such:
///
/// {@snippet :
/// bit 76 543 210
/// value BB GGG RRR
/// }
///
/// So to read the {@code G} field, we need to shift the value right by 3 ({@code = startBit}) bits
/// and mask it with 0x07 ({@code = (1 << (endBit - startBit)) - 1}).
final class BitfieldUtilImplR2L implements IBitfieldUtilImpl {
@Override
public @Unsigned boolean readBitUnchecked(@Unsigned byte value, @Unsigned int bit) {
return ((value >>> bit) & 0x01) != 0;
}
@Override
public @Unsigned boolean readBitUnchecked(@Unsigned short value, @Unsigned int bit) {
return ((value >>> bit) & 0x01) != 0;
}
@Override
public @Unsigned boolean readBitUnchecked(@Unsigned int value, @Unsigned int bit) {
return ((value >>> bit) & 0x01) != 0;
}
@Override
public @Unsigned byte readBitsUnchecked(
@Unsigned byte value,
@Unsigned int startBit,
@Unsigned int endBit
) {
int mask = (1 << (endBit - startBit)) - 1;
return (byte) ((value >>> startBit) & mask);
}
@Override
public @Unsigned short readBitsUnchecked(
@Unsigned short value,
@Unsigned int startBit,
@Unsigned int endBit
) {
int mask = (1 << (endBit - startBit)) - 1;
return (short) ((value >>> startBit) & mask);
}
@Override
public @Unsigned int readBitsUnchecked(
@Unsigned int value,
@Unsigned int startBit,
@Unsigned int endBit
) {
long mask = (1L << (endBit - startBit)) - 1;
return (int) ((value >>> startBit) & mask);
}
@Override
public @Unsigned byte writeBitUnchecked(
@Unsigned byte value,
@Unsigned int bit,
boolean bitValue
) {
if (bitValue) {
return (byte) (value | (1 << bit));
} else {
return (byte) (value & ~(1 << bit));
}
}
@Override
public @Unsigned short writeBitUnchecked(
@Unsigned short value,
@Unsigned int bit,
boolean bitValue
) {
if (bitValue) {
return (short) (value | (1 << bit));
} else {
return (short) (value & ~(1 << bit));
}
}
@Override
public @Unsigned int writeBitUnchecked(
@Unsigned int value,
@Unsigned int bit,
boolean bitValue
) {
if (bitValue) {
return value | (1 << bit);
} else {
return value & ~(1 << bit);
}
}
@Override
public @Unsigned byte writeBitsUnchecked(
@Unsigned byte value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned byte bits
) {
int mask = (1 << (endBit - startBit)) - 1;
int maskShifted = mask << startBit;
int bitsShifted = (bits & mask) << startBit;
return (byte) ((value & ~maskShifted) | bitsShifted);
}
@Override
public @Unsigned short writeBitsUnchecked(
@Unsigned short value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned short bits
) {
int mask = (1 << (endBit - startBit)) - 1;
int maskShifted = mask << startBit;
int bitsShifted = (bits & mask) << startBit;
return (short) ((value & ~maskShifted) | bitsShifted);
}
@Override
public @Unsigned int writeBitsUnchecked(
@Unsigned int value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned int bits
) {
long mask = (1L << (endBit - startBit)) - 1;
long maskShifted = mask << startBit;
long bitsShifted = (bits & mask) << startBit;
return (int) ((value & ~maskShifted) | bitsShifted);
}
BitfieldUtilImplR2L() {}
}
@@ -0,0 +1,45 @@
package club.doki7.ffm.bits;
import club.doki7.ffm.annotation.Unsigned;
sealed interface IBitfieldUtilImpl permits BitfieldUtilImplL2R, BitfieldUtilImplR2L {
boolean readBitUnchecked(@Unsigned byte value, @Unsigned int bit);
boolean readBitUnchecked(@Unsigned short value, @Unsigned int bit);
boolean readBitUnchecked(@Unsigned int value, @Unsigned int bit);
@Unsigned
byte readBitsUnchecked(@Unsigned byte value, @Unsigned int startBit, @Unsigned int endBit);
@Unsigned
short readBitsUnchecked(@Unsigned short value, @Unsigned int startBit, @Unsigned int endBit);
@Unsigned
int readBitsUnchecked(@Unsigned int value, @Unsigned int startBit, @Unsigned int endBit);
@Unsigned
byte writeBitUnchecked(@Unsigned byte value, @Unsigned int bit, boolean bitValue);
@Unsigned
short writeBitUnchecked(@Unsigned short value, @Unsigned int bit, boolean bitValue);
@Unsigned
int writeBitUnchecked(@Unsigned int value, @Unsigned int bit, boolean bitValue);
@Unsigned
byte writeBitsUnchecked(
@Unsigned byte value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned byte bits
);
@Unsigned
short writeBitsUnchecked(
@Unsigned short value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned short bits
);
@Unsigned
int writeBitsUnchecked(
@Unsigned int value,
@Unsigned int startBit,
@Unsigned int endBit,
@Unsigned int bits
);
}
@@ -0,0 +1,2 @@
/// Cross-platform binary bit manipulation utilities.
package club.doki7.ffm.bits;
@@ -0,0 +1,20 @@
package club.doki7.ffm.library;
import org.jetbrains.annotations.NotNull;
public sealed interface ILibraryLoader permits
WindowsLibraryLoader,
UnixLibraryLoader,
JavaSystemLibraryLoader
{
@NotNull ISharedLibrary loadLibrary(@NotNull String libName) throws UnsatisfiedLinkError;
static ILibraryLoader platformLoader() {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("windows")) {
return WindowsLibraryLoader.INSTANCE;
} else {
return UnixLibraryLoader.INSTANCE;
}
}
}
@@ -0,0 +1,11 @@
package club.doki7.ffm.library;
import club.doki7.ffm.RawFunctionLoader;
public sealed interface ISharedLibrary extends RawFunctionLoader, AutoCloseable permits
JavaSystemLibrary,
UnixLibrary,
WindowsLibrary
{
@Override void close();
}
@@ -0,0 +1,25 @@
package club.doki7.ffm.library;
import org.jetbrains.annotations.NotNull;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
public enum JavaSystemLibrary implements ISharedLibrary {
INSTANCE;
@Override
public @NotNull MemorySegment apply(@NotNull String name) {
return loaderLookup.find(name)
.or(() -> stdlibLookup.find(name))
.orElse(MemorySegment.NULL);
}
@Override
public void close() {}
private static final Linker nativeLinker = Linker.nativeLinker();
private static final SymbolLookup stdlibLookup = nativeLinker.defaultLookup();
private static final SymbolLookup loaderLookup = SymbolLookup.loaderLookup();
}
@@ -0,0 +1,17 @@
package club.doki7.ffm.library;
import org.jetbrains.annotations.NotNull;
public enum JavaSystemLibraryLoader implements ILibraryLoader {
INSTANCE;
@Override
public @NotNull ISharedLibrary loadLibrary(@NotNull String libName) throws UnsatisfiedLinkError {
try {
System.loadLibrary(libName);
} catch (Throwable e) {
System.load(libName);
}
return JavaSystemLibrary.INSTANCE;
}
}
@@ -0,0 +1,60 @@
package club.doki7.ffm.library;
import club.doki7.ffm.RawFunctionLoader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.util.Objects;
public final class UnixLibrary implements ISharedLibrary {
@Override
public @NotNull MemorySegment apply(@NotNull String libName) throws UnsatisfiedLinkError {
try (Arena arena = Arena.ofConfined()){
MethodHandle h = Objects.requireNonNull(hDlsym);
MemorySegment nameSegment = arena.allocateFrom(libName);
return (MemorySegment) h.invokeExact(library, nameSegment);
} catch (Throwable e) {
throw new UnsatisfiedLinkError("Failed to load symbol '" + libName + "': " + e.getMessage());
}
}
@Override
public void close() {
try {
MethodHandle h = Objects.requireNonNull(hDlclose);
h.invokeExact(library);
} catch (Throwable a) {}
}
UnixLibrary(MemorySegment library) {
this.library = library;
}
private final MemorySegment library;
private static final FunctionDescriptor DESCRIPTOR$dlsym =
FunctionDescriptor.of(
ValueLayout.ADDRESS, // returns void*
ValueLayout.ADDRESS, // void *handle
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_BYTE) // const char *symbol
);
private static final FunctionDescriptor DESCRIPTOR$dlclose =
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // returns int
ValueLayout.ADDRESS // void *handle
);
private static final @Nullable MethodHandle hDlsym;
private static final @Nullable MethodHandle hDlclose;
static {
MemorySegment pfnDlsym = JavaSystemLibrary.INSTANCE.load("dlsym");
MemorySegment pfnDlclose = JavaSystemLibrary.INSTANCE.load("dlclose");
hDlsym = RawFunctionLoader.link(pfnDlsym, DESCRIPTOR$dlsym);
hDlclose = RawFunctionLoader.link(pfnDlclose, DESCRIPTOR$dlclose);
}
}
@@ -0,0 +1,60 @@
package club.doki7.ffm.library;
import club.doki7.ffm.RawFunctionLoader;
import club.doki7.ffm.util.UnixUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.util.Objects;
public enum UnixLibraryLoader implements ILibraryLoader {
INSTANCE;
@Override
public @NotNull ISharedLibrary loadLibrary(@NotNull String libName) throws UnsatisfiedLinkError {
if (!libName.startsWith("/")) {
libName = "lib" + libName + ".so";
}
MemorySegment result;
try (Arena arena = Arena.ofConfined()) {
MethodHandle h = Objects.requireNonNull(hDlopen);
MemorySegment nameSegment = arena.allocateFrom(libName);
result = (MemorySegment) h.invokeExact(nameSegment, RTLD_LAZY | RTLD_LOCAL);
} catch (Throwable e) {
throw new UnsatisfiedLinkError(e.getMessage());
}
if (result.equals(MemorySegment.NULL)) {
String error = UnixUtil.dlerror();
if (error != null) {
throw new UnsatisfiedLinkError("dlopen error: " + error);
} else {
throw new UnsatisfiedLinkError("dlopen error: unknown error");
}
}
return new UnixLibrary(result);
}
private static final FunctionDescriptor DESCRIPTOR$dlopen = FunctionDescriptor.of(
ValueLayout.ADDRESS, // returns void*
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_BYTE), // const char* filename
ValueLayout.JAVA_INT // int flag
);
private static final @Nullable MethodHandle hDlopen;
static {
MemorySegment pfnDlopen = JavaSystemLibrary.INSTANCE.load("dlopen");
hDlopen = RawFunctionLoader.link(pfnDlopen, DESCRIPTOR$dlopen);
UnixUtil.forceLoad();
}
private static final int RTLD_LAZY = 0x1;
private static final int RTLD_LOCAL = 0;
}
@@ -0,0 +1,58 @@
package club.doki7.ffm.library;
import club.doki7.ffm.RawFunctionLoader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.util.Objects;
public final class WindowsLibrary implements ISharedLibrary {
@Override
public @NotNull MemorySegment apply(@NotNull String name) {
try (Arena arena = Arena.ofConfined()){
MethodHandle h = Objects.requireNonNull(hGetProcAddress);
return (MemorySegment) h.invokeExact(this.hModule, arena.allocateFrom(name));
} catch (Throwable e) {
throw new UnsatisfiedLinkError(e.getMessage());
}
}
@Override
public void close() {
try {
MethodHandle h = Objects.requireNonNull(hFreeLibrary);
h.invokeExact(this.hModule);
} catch (Throwable a) {}
}
WindowsLibrary(MemorySegment hModule) {
this.hModule = hModule;
}
private final MemorySegment hModule;
private static final FunctionDescriptor DESCRIPTOR$GetProcAddress =
FunctionDescriptor.of(
ValueLayout.ADDRESS, // returns FARPROC
ValueLayout.ADDRESS, // HMODULE hModule
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_BYTE) // LPCSTR lpProcName
);
public static final FunctionDescriptor DESCRIPTOR$FreeLibrary =
FunctionDescriptor.of(
ValueLayout.JAVA_INT, // returns BOOL (Windows BOOL, i.e., int32)
ValueLayout.ADDRESS // HMODULE hLibModule
);
private static final @Nullable MethodHandle hGetProcAddress;
private static final @Nullable MethodHandle hFreeLibrary;
static {
MemorySegment pfnGetProcAddress = JavaSystemLibrary.INSTANCE.load("GetProcAddress");
MemorySegment pfnFreeLibrary = JavaSystemLibrary.INSTANCE.load("FreeLibrary");
hGetProcAddress = RawFunctionLoader.link(pfnGetProcAddress, DESCRIPTOR$GetProcAddress);
hFreeLibrary = RawFunctionLoader.link(pfnFreeLibrary, DESCRIPTOR$FreeLibrary);
}
}
@@ -0,0 +1,74 @@
package club.doki7.ffm.library;
import club.doki7.ffm.RawFunctionLoader;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.VarHandle;
import java.util.Objects;
public enum WindowsLibraryLoader implements ILibraryLoader {
INSTANCE;
@Override
public @NotNull ISharedLibrary loadLibrary(
@NotNull String libName
) throws UnsatisfiedLinkError {
try (Arena arena = Arena.ofConfined()) {
MethodHandle h = Objects.requireNonNull(hLoadLibraryW);
StructLayout captureStateLayout = Linker.Option.captureStateLayout();
MemorySegment capturedState = arena.allocate(captureStateLayout);
char[] charArray = libName.toCharArray();
MemorySegment lpLibName = arena.allocate(ValueLayout.JAVA_SHORT, charArray.length + 1);
lpLibName.copyFrom(MemorySegment.ofArray(charArray));
MemorySegment result;
try {
result = (MemorySegment) h.invokeExact(capturedState, lpLibName);
} catch (Throwable e) {
throw new UnsatisfiedLinkError(e.getMessage());
}
if (result.equals(MemorySegment.NULL)) {
VarHandle vh = captureStateLayout.varHandle(
MemoryLayout.PathElement.groupElement("GetLastError")
);
int lastError = (int) vh.get(capturedState, 0L);
if (lastError < 0) {
throw new UnsatisfiedLinkError("LoadLibraryW error: unknown error");
} else {
throw new UnsatisfiedLinkError("LoadLibraryW error: " + lastError);
}
}
return new WindowsLibrary(result);
}
}
private static final FunctionDescriptor DESCRIPTOR$LoadLibraryW = FunctionDescriptor.of(
ValueLayout.ADDRESS, // returns HMODULE
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_SHORT) // LPCWSTR lpLibFileName
);
private static final @Nullable MethodHandle hLoadLibraryW;
static {
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
try {
System.loadLibrary("kernel32");
} catch (Throwable e) {
throw new RuntimeException(e);
}
MemorySegment pfnLoadLibraryW = JavaSystemLibrary.INSTANCE.load("LoadLibraryW");
hLoadLibraryW = RawFunctionLoader.linkWithOptions(
pfnLoadLibraryW,
DESCRIPTOR$LoadLibraryW,
Linker.Option.captureCallState("GetLastError")
);
} else {
hLoadLibraryW = null;
}
}
}
+2
View File
@@ -0,0 +1,2 @@
/// Utility library for Java 22 FFM (Project Panama) APIs.
package club.doki7.ffm;
+260
View File
@@ -0,0 +1,260 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/// Represents a pointer to byte(s) in native memory.
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}). To represent null pointer,
/// you may use a Java {@code null} instead. See the documentation of {@link IPointer#segment()}
/// for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record BytePtr(@NotNull MemorySegment segment) implements IPointer, Iterable<Byte> {
public long size() {
return segment.byteSize();
}
public byte read() {
return segment.get(ValueLayout.JAVA_BYTE, 0);
}
public void write(byte value) {
segment.set(ValueLayout.JAVA_BYTE, 0, value);
}
public byte read(long index) {
return segment.get(ValueLayout.JAVA_BYTE, index);
}
public void write(long index, byte value) {
segment.set(ValueLayout.JAVA_BYTE, index, value);
}
public void write(byte @NotNull [] bytes) {
segment.copyFrom(MemorySegment.ofArray(bytes));
}
public void writeV(byte value0, byte @NotNull ...values) {
write(value0);
offset(1).write(values);
}
public void writeString(@NotNull String s) {
segment.setString(0, s);
}
/// Assume the {@link BytePtr} is a null-terminated string, reads the string from the beginning
/// of the underlying memory segment, until the first NUL byte is encountered.
///
/// This function requires the size of the underlying memory segment to be set correctly. If the
/// size is not known in advance and correctly set (for example, the {@link BytePtr} or the
/// underlying {@link MemorySegment} is returned from some C API), you may use
/// {@link BytePtr#readString} (note that it is {@link Unsafe}) instead.
public @NotNull String readStringSafe() {
return segment.getString(0);
}
/// Assumes the {@link BytePtr} is a null-terminated string, reads the string from the beginning
/// of the underlying memory segment, until the first NUL byte is encountered.
///
/// This function is {@link Unsafe} because it does not check the size of the underlying memory
/// segment. This function is suitable for the cases that the size of the underlying memory
/// segment is not known in advance and correctly set (for example, the {@link BytePtr} or the
/// underlying {@link MemorySegment} is returned from some C API). If the size is correctly set,
/// you may use {@link BytePtr#readStringSafe} instead.
@Unsafe
public @NotNull String readString() {
MemorySegment reinterpreted = segment.reinterpret(Long.MAX_VALUE);
return reinterpreted.getString(0);
}
/// Assume the {@link BytePtr} is capable of holding at least {@code newSize} bytes, create a
/// new view {@link BytePtr} that uses the same backing storage as this {@link BytePtr}, but
/// with the new size. Since there is actually no way to really check whether the new size is
/// valid, while buffer overflow is undefined behavior, this method is marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull BytePtr reinterpret(long newSize) {
return new BytePtr(segment.reinterpret(newSize));
}
public @NotNull BytePtr offset(long offset) {
return new BytePtr(segment.asSlice(offset));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull BytePtr slice(long start, long end) {
return new BytePtr(segment.asSlice(start, end - start));
}
public @NotNull BytePtr slice(long end) {
return new BytePtr(segment.asSlice(0, end));
}
@Override
public @NotNull Iterator<Byte> iterator() {
return new Iter(segment);
}
/// Create a new {@link BytePtr} using {@code segment} as backing storage, with argument
/// validation.
///
/// If {@code segment} is not big enough to hold at least one byte, that segment is simply
/// considered "empty". See the documentation of {@link IPointer#segment()} for more details.
///
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} is {@link MemorySegment#NULL},
/// otherwise a new {@link BytePtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native
public static @Nullable BytePtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
return new BytePtr(segment);
}
/// Create a new {@link BytePtr} using the same backing storage as {@code buffer}, with argument
/// validation.
///
/// The main difference between this static method and the {@link #allocate(Arena, ByteBuffer)}
/// method is that this method does not copy the contents of {@code buffer} into a newly
/// allocated {@link MemorySegment}. Instead, the newly created {@link BytePtr} will use the
/// same backing storage as {@code buffer}. Thus, modifications from one side will be visible on
/// the other side.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be referred. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be referred, you may want to call {@link Buffer#rewind()}.
///
/// @param buffer the {@link ByteBuffer} to use as the backing storage
/// @return a new {@link BytePtr} that uses {@code buffer} as its backing storage
/// @throws IllegalArgumentException if {@code buffer} is not direct
public static @NotNull BytePtr checked(@NotNull ByteBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct");
}
return new BytePtr(MemorySegment.ofBuffer(buffer));
}
public static @NotNull BytePtr from(@NotNull IPointer ptr) {
return new BytePtr(ptr.segment());
}
public static @NotNull BytePtr allocate(@NotNull Arena arena) {
return new BytePtr(arena.allocate(1));
}
public static @NotNull BytePtr allocate(@NotNull Arena arena, long size) {
return new BytePtr(arena.allocate(size));
}
public static @NotNull BytePtr allocate(@NotNull Arena arena, byte @NotNull [] bytes) {
return new BytePtr(arena.allocateFrom(ValueLayout.JAVA_BYTE, bytes));
}
public static @NotNull BytePtr allocate(@NotNull Arena arena, Collection<Byte> bytes) {
BytePtr ret = allocate(arena, bytes.size());
int i = 0;
for (byte value : bytes) {
ret.write(i, value);
i += 1;
}
return ret;
}
public static @NotNull BytePtr allocateV(@NotNull Arena arena, byte value0, byte ...values) {
BytePtr ret = allocate(arena, values.length + 1);
ret.write(value0);
ret.offset(1).segment.copyFrom(MemorySegment.ofArray(values));
return ret;
}
/// Allocate a new {@link BytePtr} in {@code arena} and copy the contents of {@code buffer} into
/// the newly allocated {@link BytePtr}.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be copied. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be copied, you may want to call {@link Buffer#rewind()}.
///
///
/// @param arena the {@link Arena} to allocate the new {@link BytePtr} in
/// @param buffer the {@link ByteBuffer} to copy the contents from
/// @return a new {@link BytePtr} that contains the contents of {@code buffer}
public static @NotNull BytePtr allocate(@NotNull Arena arena, @NotNull ByteBuffer buffer) {
var s = arena.allocate(buffer.remaining());
s.copyFrom(MemorySegment.ofBuffer(buffer));
return new BytePtr(s);
}
public static @NotNull BytePtr allocateAligned(
@NotNull Arena arena,
long size,
long alignment
) {
return new BytePtr(arena.allocate(size, alignment));
}
public static @NotNull BytePtr allocateString(@NotNull Arena arena, @NotNull String s) {
return new BytePtr(arena.allocateFrom(s));
}
/// An iterator over the bytes.
private static final class Iter implements Iterator<Byte> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() != 0;
}
@Override
public Byte next() {
if (!hasNext()) {
throw new NoSuchElementException("No more bytes to read");
}
byte value = segment.get(ValueLayout.JAVA_BYTE, 0);
segment = segment.asSlice(1);
return value;
}
private @NotNull MemorySegment segment;
}
}
+148
View File
@@ -0,0 +1,148 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.NativeLayout;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
/// Represents a pointer to 32-bit integer(s) in native memory.
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code NativeLayout.C_LONG.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record CLongPtr(MemorySegment segment) implements IPointer, Iterable<Long> {
public long size() {
return segment.byteSize() / NativeLayout.C_LONG_SIZE;
}
public long read() {
return NativeLayout.readCLong(segment, 0);
}
public void write(long value) {
NativeLayout.writeCLong(segment, 0, value);
}
public long read(long index) {
return NativeLayout.readCLong(segment, index * NativeLayout.C_LONG_SIZE);
}
public void write(long index, long value) {
NativeLayout.writeCLong(segment, index * NativeLayout.C_LONG_SIZE, value);
}
/// Assume the {@link CLongPtr} is capable of holding at least {@code newSize} elements, create
/// a new view {@link CLongPtr} that uses the same backing storage as this {@link CLongPtr}, but
/// with the new size. Since there is actually no way to really check whether the new size is
/// valid, while buffer overflow is undefined behavior, this method is marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull CLongPtr reinterpret(long newSize) {
return new CLongPtr(segment.reinterpret(newSize * NativeLayout.C_LONG_SIZE));
}
public @NotNull CLongPtr offset(long offset) {
return new CLongPtr(segment.asSlice(offset * NativeLayout.C_LONG_SIZE));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull CLongPtr slice(long start, long end) {
return new CLongPtr(segment.asSlice(
start * NativeLayout.C_LONG_SIZE,
(end - start) * NativeLayout.C_LONG_SIZE
));
}
public @NotNull CLongPtr slice(long end) {
return new CLongPtr(segment.asSlice(0, end * NativeLayout.C_LONG_SIZE));
}
public @NotNull PrimitiveIterator.OfLong iterator() {
return new Iter(segment);
}
/// Create a new {@link CLongPtr} with the given {@link MemorySegment} as the backing storage,
/// with argument validation.
///
/// This function does not ensure {@code segment}'s size to be a multiple of
/// {@link NativeLayout#C_LONG_SIZE}, since that several trailing bytes could be automatically
/// ignored by {@link #size()} method, and usually these bytes does not interfere with FFI
/// operations. If {@code segment} is not big enough to hold at least one element, that segment
/// is simply considered "empty". See the documentation of {@link IPointer#segment()} for more
/// details.
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} is {@link MemorySegment#NULL},
/// otherwise a new {@link CLongPtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native or not properly aligned
public static @Nullable CLongPtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
if (segment.address() % NativeLayout.C_LONG.byteAlignment() != 0) {
throw new IllegalArgumentException("Segment address must be aligned to " + NativeLayout.C_LONG.byteAlignment() + " bytes");
}
return new CLongPtr(segment);
}
public static @NotNull CLongPtr allocate(@NotNull Arena arena) {
return new CLongPtr(arena.allocate(NativeLayout.C_LONG));
}
public static @NotNull CLongPtr allocate(@NotNull Arena arena, long size) {
return new CLongPtr(arena.allocate(NativeLayout.C_LONG, size));
}
/// An iterator over the integers.
private static final class Iter implements PrimitiveIterator.OfLong {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= NativeLayout.C_LONG_SIZE;
}
@Override
public long nextLong() {
if (!hasNext()) {
throw new NoSuchElementException("No more elements to read");
}
long value = NativeLayout.readCLong(segment, 0);
segment = segment.asSlice(NativeLayout.C_LONG_SIZE);
return value;
}
private @NotNull MemorySegment segment;
}
}
+238
View File
@@ -0,0 +1,238 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.Buffer;
import java.nio.DoubleBuffer;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
/// Represents a pointer to 64-bit double-precision float(s) in native memory
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@link ValueLayout.OfDouble#byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record DoublePtr(@NotNull MemorySegment segment) implements IPointer, Iterable<Double> {
public long size() {
return segment.byteSize() / Double.BYTES;
}
public double read() {
return segment.get(ValueLayout.JAVA_DOUBLE, 0);
}
public void write(double value) {
segment.set(ValueLayout.JAVA_DOUBLE, 0, value);
}
public double read(long index) {
return segment.get(ValueLayout.JAVA_DOUBLE, index * Double.BYTES);
}
public void write(long index, double value) {
segment.set(ValueLayout.JAVA_DOUBLE, index * Double.BYTES, value);
}
public void write(double @NotNull [] array) {
segment.copyFrom(MemorySegment.ofArray(array));
}
public void writeV(double value0, double @NotNull ...values) {
write(value0);
offset(1).write(values);
}
/// Assume the {@link DoublePtr} is capable of holding at least {@code newSize} doubles, create
/// a new view {@link DoublePtr} that uses the same backing storage as this {@link DoublePtr},
/// but with the new size. Since there is actually no way to really check whether the new size
/// is valid, while buffer overflow is undefined behavior, this method is marked as
/// {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull DoublePtr reinterpret(long newSize) {
return new DoublePtr(segment.reinterpret(newSize * Double.BYTES));
}
public @NotNull DoublePtr offset(long offset) {
return new DoublePtr(segment.asSlice(offset * Double.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull DoublePtr slice(long start, long end) {
return new DoublePtr(segment.asSlice(start * Double.BYTES, (end - start) * Double.BYTES));
}
public @NotNull DoublePtr slice(long end) {
return new DoublePtr(segment.asSlice(0, end * Double.BYTES));
}
/// Create a new {@link DoublePtr} using {@code segment} as backing storage, with argument
/// validation.
///
/// This function does not ensure {@code segment}'s size to be a multiple of
/// {@link Double#BYTES}, since that several trailing bytes could be automatically ignored by
/// {@link #size()} method, and usually these bytes does not interfere with FFI operations. If
/// {@code segment} is not big enough to hold at least one double, that segment is simply
/// considered "empty". See the documentation of {@link IPointer#segment()} for more details.
///
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} is {@link MemorySegment#NULL},
/// otherwise a new {@link DoublePtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native or not properly aligned
public static @Nullable DoublePtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
if (segment.address() % ValueLayout.JAVA_DOUBLE.byteAlignment() != 0) {
throw new IllegalArgumentException("Segment address must be aligned to " + ValueLayout.JAVA_DOUBLE.byteAlignment() + " bytes");
}
return new DoublePtr(segment);
}
@Override
public @NotNull PrimitiveIterator.OfDouble iterator() {
return new Iter(segment);
}
/// Create a new {@link DoublePtr} using the same backing storage as {@code buffer}, with
/// argument validation.
///
/// The main difference between this static method and the {@link #allocate(Arena, DoubleBuffer)}
/// method is that this method does not copy the contents of the {@code buffer} into a newly
/// allocated {@link MemorySegment}. Instead, the newly created {@link DoublePtr} will use the
/// same backing storage as {@code buffer}. Thus, modification from one side will be visible on
/// the other side.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be referred. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be referred, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link DoubleBuffer#order()} property. {@link DoublePtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param buffer the {@link DoubleBuffer} to use as the backing storage
/// @return a new {@link DoublePtr} that uses {@code buffer} as its backing storage
/// @throws IllegalArgumentException if {@code buffer} is not direct, or its backing storage is
/// not properly aligned
public static @NotNull DoublePtr checked(@NotNull DoubleBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct");
}
MemorySegment segment = MemorySegment.ofBuffer(buffer);
if (segment.address() % ValueLayout.JAVA_DOUBLE.byteAlignment() != 0) {
throw new IllegalArgumentException("Buffer address must be aligned to " + ValueLayout.JAVA_DOUBLE.byteAlignment() + " bytes");
}
return new DoublePtr(segment);
}
public static @NotNull DoublePtr allocate(@NotNull Arena arena) {
return new DoublePtr(arena.allocate(ValueLayout.JAVA_DOUBLE));
}
public static @NotNull DoublePtr allocate(@NotNull Arena arena, long size) {
return new DoublePtr(arena.allocate(ValueLayout.JAVA_DOUBLE, size));
}
public static @NotNull DoublePtr allocate(@NotNull Arena arena, double @NotNull [] array) {
return new DoublePtr(arena.allocateFrom(ValueLayout.JAVA_DOUBLE, array));
}
public static @NotNull DoublePtr allocate(@NotNull Arena arena, Collection<Double> doubles) {
DoublePtr ret = allocate(arena, doubles.size());
int i = 0;
for (double value : doubles) {
ret.write(i, value);
i += 1;
}
return ret;
}
public static @NotNull DoublePtr allocateV(@NotNull Arena arena, double value0, double ...values) {
DoublePtr ret = allocate(arena, values.length + 1);
ret.write(value0);
ret.offset(1).segment.copyFrom(MemorySegment.ofArray(values));
return ret;
}
/// Allocate a new {@link DoublePtr} in {@code arena} and copy the contents of {@code buffer} into
/// the newly allocated {@link DoublePtr}.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be copied. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be copied, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link DoubleBuffer#order()} property. {@link DoublePtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param arena the {@link Arena} to allocate the new {@link DoublePtr} in
/// @param buffer the {@link DoubleBuffer} to copy the contents from
/// @return a new {@link DoublePtr} that contains the contents of {@code buffer}
public static @NotNull DoublePtr allocate(@NotNull Arena arena, @NotNull DoubleBuffer buffer) {
var s = arena.allocate(ValueLayout.JAVA_DOUBLE, buffer.remaining());
s.copyFrom(MemorySegment.ofBuffer(buffer));
return new DoublePtr(s);
}
/// An iterator over the double precision float numbers.
private static final class Iter implements PrimitiveIterator.OfDouble {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= Double.BYTES;
}
@Override
public double nextDouble() {
if (!hasNext()) {
throw new NoSuchElementException("No more doubles to read");
}
double value = segment.get(ValueLayout.JAVA_DOUBLE, 0);
segment = segment.asSlice(Double.BYTES);
return value;
}
private @NotNull MemorySegment segment;
}
}
+237
View File
@@ -0,0 +1,237 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.Buffer;
import java.nio.FloatBuffer;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/// Represents a pointer to 32-bit float(s) in native memory
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@link ValueLayout.OfFloat#byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record FloatPtr(@NotNull MemorySegment segment) implements IPointer, Iterable<Float> {
public long size() {
return segment.byteSize() / Float.BYTES;
}
public float read() {
return segment.get(ValueLayout.JAVA_FLOAT, 0);
}
public void write(float value) {
segment.set(ValueLayout.JAVA_FLOAT, 0, value);
}
public float read(long index) {
return segment.get(ValueLayout.JAVA_FLOAT, index * Float.BYTES);
}
public void write(long index, float value) {
segment.set(ValueLayout.JAVA_FLOAT, index * Float.BYTES, value);
}
public void write(float @NotNull [] array) {
segment.copyFrom(MemorySegment.ofArray(array));
}
public void writeV(float value0, float @NotNull ...values) {
write(value0);
offset(1).write(values);
}
/// Assume the {@link FloatPtr} is capable of holding at least {@code newSize} floats, create
/// a new view {@link FloatPtr} that uses the same backing storage as this {@link FloatPtr}, but
/// with the new size. Since there is actually no way to really check whether the new size is
/// valid, while buffer overflow is undefined behavior, this method is marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull FloatPtr reinterpret(long newSize) {
return new FloatPtr(segment.reinterpret(newSize * Float.BYTES));
}
public @NotNull FloatPtr offset(long offset) {
return new FloatPtr(segment.asSlice(offset * Float.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull FloatPtr slice(long start, long end) {
return new FloatPtr(segment.asSlice(start * Float.BYTES, (end - start) * Float.BYTES));
}
public @NotNull FloatPtr slice(long end) {
return new FloatPtr(segment.asSlice(0, end * Float.BYTES));
}
@Override
public @NotNull Iterator<Float> iterator() {
return new Iter(segment);
}
/// Create a new {@link FloatPtr} using {@code segment} as backing storage, with argument
/// validation.
///
/// This function does not ensure {@code segment}'s size to be a multiple of
/// {@link Float#BYTES}, since that several trailing bytes could be automatically ignored by
/// {@link #size()} method, and usually these bytes does not interfere with FFI operations. If
/// {@code segment} is not big enough to hold at least one float, that segment is simply
/// considered "empty". See the documentation of {@link IPointer#segment()} for more details.
///
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} is {@link MemorySegment#NULL},
/// otherwise a new {@link FloatPtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native or not properly aligned
public static @Nullable FloatPtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
if (segment.address() % ValueLayout.JAVA_FLOAT.byteAlignment() != 0) {
throw new IllegalArgumentException("Segment address must be aligned to " + ValueLayout.JAVA_FLOAT.byteAlignment() + " bytes");
}
return new FloatPtr(segment);
}
/// Create a new {@link FloatPtr} using the same backing storage as {@code buffer}, with
/// argument validation.
///
/// The main difference between this static method and the {@link #allocate(Arena, FloatBuffer)}
/// method is that this method does not copy the contents of the {@code buffer} into a newly
/// allocated {@link MemorySegment}. Instead, the newly created {@link FloatPtr} will use the
/// same backing storage as {@code buffer}. Thus, modification from one side will be visible on
/// the other side.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be referred. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be referred, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link FloatBuffer#order()} property. {@link FloatPtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param buffer the {@link FloatBuffer} to use as the backing storage
/// @return a new {@link FloatPtr} that uses {@code buffer} as its backing storage
/// @throws IllegalArgumentException if {@code buffer} is not direct, or its backing storage is
/// not properly aligned
public static @NotNull FloatPtr checked(@NotNull FloatBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct");
}
MemorySegment segment = MemorySegment.ofBuffer(buffer);
if (segment.address() % ValueLayout.JAVA_FLOAT.byteAlignment() != 0) {
throw new IllegalArgumentException("Buffer address must be aligned to " + ValueLayout.JAVA_FLOAT.byteAlignment() + " bytes");
}
return new FloatPtr(segment);
}
public static @NotNull FloatPtr allocate(@NotNull Arena arena) {
return new FloatPtr(arena.allocate(ValueLayout.JAVA_FLOAT));
}
public static @NotNull FloatPtr allocate(@NotNull Arena arena, long size) {
return new FloatPtr(arena.allocate(ValueLayout.JAVA_FLOAT, size));
}
public static @NotNull FloatPtr allocate(@NotNull Arena arena, float @NotNull [] array) {
return new FloatPtr(arena.allocateFrom(ValueLayout.JAVA_FLOAT, array));
}
public static @NotNull FloatPtr allocate(@NotNull Arena arena, Collection<Float> floats) {
FloatPtr ret = allocate(arena, floats.size());
int i = 0;
for (Float f : floats) {
ret.write(i, f);
i += 1;
}
return ret;
}
public static @NotNull FloatPtr allocateV(@NotNull Arena arena, float value0, float ...values) {
FloatPtr ret = allocate(arena, values.length + 1);
ret.write(value0);
ret.offset(1).segment.copyFrom(MemorySegment.ofArray(values));
return ret;
}
/// Allocate a new {@link FloatPtr} in {@code arena} and copy the contents of {@code buffer} into
/// the newly allocated {@link FloatPtr}.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be copied. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be copied, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link FloatBuffer#order()} property. {@link FloatPtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param arena the {@link Arena} to allocate the new {@link FloatPtr} in
/// @param buffer the {@link FloatBuffer} to copy the contents from
/// @return a new {@link FloatPtr} that contains the contents of {@code buffer}
public static @NotNull FloatPtr allocate(@NotNull Arena arena, @NotNull FloatBuffer buffer) {
var s = arena.allocate(ValueLayout.JAVA_FLOAT, buffer.remaining());
s.copyFrom(MemorySegment.ofBuffer(buffer));
return new FloatPtr(s);
}
/// An iterator over the float numbers.
private static final class Iter implements Iterator<Float> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= Float.BYTES;
}
@Override
public Float next() {
if (!hasNext()) {
throw new NoSuchElementException("No more floats to read");
}
float value = segment.get(ValueLayout.JAVA_FLOAT, 0);
segment = segment.asSlice(Float.BYTES);
return value;
}
private @NotNull MemorySegment segment;
}
}
+253
View File
@@ -0,0 +1,253 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.Buffer;
import java.nio.IntBuffer;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
/// Represents a pointer to 32-bit integer(s) in native memory.
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@link ValueLayout.OfInt#byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record IntPtr(@NotNull MemorySegment segment) implements IPointer, Iterable<Integer> {
public long size() {
return segment.byteSize() / Integer.BYTES;
}
public int read() {
return segment.get(ValueLayout.JAVA_INT, 0);
}
public void write(int value) {
segment.set(ValueLayout.JAVA_INT, 0, value);
}
public int read(long index) {
return segment.get(ValueLayout.JAVA_INT, index * Integer.BYTES);
}
public void write(long index, int value) {
segment.set(ValueLayout.JAVA_INT, index * Integer.BYTES, value);
}
public void write(int @NotNull [] array) {
segment.copyFrom(MemorySegment.ofArray(array));
}
public void writeV(int value0, int @NotNull ...values) {
write(value0);
offset(1).write(values);
}
/// Assume the {@link IntPtr} is capable of holding at least {@code newSize} integers, create
/// a new view {@link IntPtr} that uses the same backing storage as this {@link IntPtr}, but
/// with the new size. Since there is actually no way to really check whether the new size is
/// valid, while buffer overflow is undefined behavior, this method is marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull IntPtr reinterpret(long newSize) {
return new IntPtr(segment.reinterpret(newSize * Integer.BYTES));
}
public @NotNull IntPtr offset(long offset) {
return new IntPtr(segment.asSlice(offset * Integer.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull IntPtr slice(long start, long end) {
return new IntPtr(segment.asSlice(start * Integer.BYTES, (end - start) * Integer.BYTES));
}
public @NotNull IntPtr slice(long end) {
return new IntPtr(segment.asSlice(0, end * Integer.BYTES));
}
@Override
public @NotNull PrimitiveIterator.OfInt iterator() {
return new Iter(segment);
}
/// Create a new {@link IntPtr} using {@code segment} as backing storage, with argument
/// validation.
///
/// This function does not ensure {@code segment}'s size to be a multiple of
/// {@link Integer#BYTES}, since that several trailing bytes could be automatically ignored by
/// {@link #size()} method, and usually these bytes does not interfere with FFI operations.
/// If {@code segment} is not big enough to hold at least one integer, that segment is simply
/// considered "empty". See the documentation of {@link IPointer#segment()} for more details.
///
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} {@link MemorySegment#NULL},
/// otherwise a new {@link IntPtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native or not properly aligned
public static @Nullable IntPtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
if (segment.address() % ValueLayout.JAVA_INT.byteAlignment() != 0) {
throw new IllegalArgumentException("Segment address must be aligned to " + ValueLayout.JAVA_INT.byteAlignment() + " bytes");
}
return new IntPtr(segment);
}
/// Create a new {@link IntPtr} using the same backing storage as {@code buffer}, with argument
/// validation.
///
/// The main difference between this static method and the {@link #allocate(Arena, IntBuffer)}
/// method is that this method does not copy the contents of {@code buffer} into a newly
/// allocated {@link MemorySegment}. Instead, the newly created {@link IntPtr} will use the same
/// backing storage as {@code buffer}. Thus, modifications from one side will be visible on the
/// other side.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be referred. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be referred, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link IntBuffer#order()} property. {@link IntPtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param buffer the {@link IntBuffer} to use as the backing storage
/// @return a new {@link IntPtr} that uses {@code buffer} as its backing storage
/// @throws IllegalArgumentException if {@code buffer} is not direct, or its backing storage is
/// not properly aligned
public static @NotNull IntPtr checked(@NotNull IntBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct");
}
MemorySegment segment = MemorySegment.ofBuffer(buffer);
if (segment.address() % ValueLayout.JAVA_INT.byteAlignment() != 0) {
throw new IllegalArgumentException("Buffer address must be aligned to " + ValueLayout.JAVA_INT.byteAlignment() + " bytes");
}
return new IntPtr(segment);
}
public static @NotNull IntPtr allocate(@NotNull Arena arena) {
return new IntPtr(arena.allocate(ValueLayout.JAVA_INT));
}
public static @NotNull IntPtr allocate(@NotNull Arena arena, long size) {
return new IntPtr(arena.allocate(ValueLayout.JAVA_INT, size));
}
public static @NotNull IntPtr allocate(@NotNull Arena arena, int @NotNull [] array) {
return new IntPtr(arena.allocateFrom(ValueLayout.JAVA_INT, array));
}
public static @NotNull IntPtr allocate(@NotNull Arena arena, Collection<Integer> ints) {
IntPtr ret = allocate(arena, ints.size());
int i = 0;
for (Integer value : ints) {
ret.write(i, value);
i += 1;
}
return ret;
}
public static @NotNull IntPtr allocateV(@NotNull Arena arena, int value0, int ...values) {
IntPtr ret = allocate(arena, values.length + 1);
ret.write(value0);
ret.offset(1).segment.copyFrom(MemorySegment.ofArray(values));
return ret;
}
/// Allocate a new {@link IntPtr} in {@code arena} and copy the contents of {@code array} into
/// the newly allocated {@link IntPtr}.
///
/// Be aware that if the length of {@code array} is not a multiple of {@link Integer#BYTES}, the
/// residual bytes will be simply discarded.
///
/// @param arena the {@link Arena} to allocate the new {@link IntPtr} in
/// @param array the {@code byte} array to copy the contents from
/// @return a new {@link IntPtr} that contains the contents of {@code array}
public static @NotNull IntPtr allocate(@NotNull Arena arena, byte @NotNull [] array) {
var segment = arena.allocate(ValueLayout.JAVA_INT, array.length / Integer.BYTES);
segment.copyFrom(MemorySegment.ofArray(array));
return new IntPtr(segment);
}
/// Allocate a new {@link IntPtr} in {@code arena} and copy the contents of {@code buffer} into
/// the newly allocated {@link IntPtr}.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be copied. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be copied, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link IntBuffer#order()} property. {@link IntPtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param arena the {@link Arena} to allocate the new {@link IntPtr} in
/// @param buffer the {@link IntBuffer} to copy the contents from
/// @return a new {@link IntPtr} that contains the contents of {@code buffer}
public static @NotNull IntPtr allocate(@NotNull Arena arena, @NotNull IntBuffer buffer) {
MemorySegment s = arena.allocate(ValueLayout.JAVA_INT, buffer.remaining());
s.copyFrom(MemorySegment.ofBuffer(buffer));
return new IntPtr(s);
}
/// An iterator over the integers.
private static final class Iter implements PrimitiveIterator.OfInt {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= Integer.BYTES;
}
@Override
public int nextInt() {
if (!hasNext()) {
throw new NoSuchElementException("No more integers to read");
}
int value = segment.get(ValueLayout.JAVA_INT, 0);
segment = segment.asSlice(Integer.BYTES);
return value;
}
private @NotNull MemorySegment segment;
}
}
+241
View File
@@ -0,0 +1,241 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.Buffer;
import java.nio.LongBuffer;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
/// Represents a pointer to 64-bit long integer(s) in native memory.
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@link ValueLayout.OfLong#byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record LongPtr(@NotNull MemorySegment segment) implements IPointer, Iterable<Long> {
public long size() {
return segment.byteSize() / Long.BYTES;
}
public long read() {
return segment.get(ValueLayout.JAVA_LONG, 0);
}
public void write(long value) {
segment.set(ValueLayout.JAVA_LONG, 0, value);
}
public long read(long index) {
return segment.get(ValueLayout.JAVA_LONG, index * Long.BYTES);
}
public void write(long index, long value) {
segment.set(ValueLayout.JAVA_LONG, index * Long.BYTES, value);
}
public void write(long @NotNull [] array) {
segment.copyFrom(MemorySegment.ofArray(array));
}
public void writeV(long value0, long @NotNull ...values) {
write(value0);
offset(1).write(values);
}
/// Assume the {@link LongPtr} is capable of holding at least {@code newSize} long integers,
/// create a new view {@link LongPtr} that uses the same backing storage as this
/// {@link LongPtr}, but with the new size. Since there is actually no way to really check
/// whether the new size is valid, while buffer overflow is undefined behavior, this method is
/// marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull LongPtr reinterpret(long newSize) {
return new LongPtr(segment.reinterpret(newSize * Long.BYTES));
}
public @NotNull LongPtr offset(long offset) {
return new LongPtr(segment.asSlice(offset * Long.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull LongPtr slice(long start, long end) {
return new LongPtr(segment.asSlice(start * Long.BYTES, (end - start) * Long.BYTES));
}
public @NotNull LongPtr slice(long end) {
return new LongPtr(segment.asSlice(0, end * Long.BYTES));
}
@Override
public @NotNull PrimitiveIterator.OfLong iterator() {
return new Iter(segment);
}
/// Create a new {@link LongPtr} using {@code segment} as backing storage, with argument
/// validation.
///
/// This function does not ensure {@code segment}'s size to be a multiple of
/// {@link Long#BYTES}, since that several trailing bytes could be automatically ignored by
/// {@link #size()} method, and usually these bytes does not interfere with FFI operations.
/// If {@code segment} is not big enough to hold at least one integer, that segment is simply
/// considered "empty". See the documentation of {@link IPointer#segment()} for more details.
///
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} is {@link MemorySegment#NULL},
/// otherwise a new {@link LongPtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native or not properly aligned
public static @Nullable LongPtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
if (segment.address() % ValueLayout.JAVA_LONG.byteAlignment() != 0) {
throw new IllegalArgumentException("Segment address must be aligned to " + ValueLayout.JAVA_LONG.byteAlignment() + " bytes");
}
return new LongPtr(segment);
}
/// Create a new {@link LongPtr} using the same backing storage as {@code buffer}, with argument
/// validation.
///
/// The main difference between this static method and the {@link #allocate(Arena, LongBuffer)}
/// method is that this method does not copy the contents of {@code buffer} into a newly
/// allocated {@link MemorySegment}. Instead, the newly created {@link LongPtr} will use the
/// same backing storage as {@code buffer}. Thus, modifications from one side will be visible on
/// the other side.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be referred. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be referred, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link LongBuffer#order()} property. {@link LongPtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param buffer the {@link LongBuffer} to use as the backing storage
/// @return a new {@link LongPtr} that uses {@code buffer} as its backing storage
/// @throws IllegalArgumentException if {@code buffer} is not direct, or its backing storage is
/// not properly aligned
public static @NotNull LongPtr checked(@NotNull LongBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct");
}
MemorySegment segment = MemorySegment.ofBuffer(buffer);
if (segment.address() % ValueLayout.JAVA_LONG.byteAlignment() != 0) {
throw new IllegalArgumentException("Buffer address must be aligned to " + ValueLayout.JAVA_LONG.byteAlignment() + " bytes");
}
return new LongPtr(segment);
}
public static @NotNull LongPtr allocate(@NotNull Arena arena) {
return new LongPtr(arena.allocate(ValueLayout.JAVA_LONG));
}
public static @NotNull LongPtr allocate(@NotNull Arena arena, long size) {
return new LongPtr(arena.allocate(ValueLayout.JAVA_LONG, size));
}
public static @NotNull LongPtr allocate(@NotNull Arena arena, long @NotNull [] array) {
return new LongPtr(arena.allocateFrom(ValueLayout.JAVA_LONG, array));
}
public static @NotNull LongPtr allocate(@NotNull Arena arena, Collection<Long> longs) {
LongPtr ret = allocate(arena, longs.size());
int i = 0;
for (long value : longs) {
ret.write(i, value);
i += 1;
}
return ret;
}
public static @NotNull LongPtr allocateV(@NotNull Arena arena, long value0, long ...values) {
LongPtr ret = allocate(arena, values.length + 1);
ret.write(value0);
ret.offset(1).segment.copyFrom(MemorySegment.ofArray(values));
return ret;
}
/// Allocate a new {@link LongPtr} in {@code arena} and copy the contents of {@code buffer} into
/// the newly allocated {@link LongPtr}.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be copied. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be copied, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link LongBuffer#order()} property. {@link LongPtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param arena the {@link Arena} to allocate the new {@link LongPtr} in
/// @param buffer the {@link LongBuffer} to copy the contents from
/// @return a new {@link LongPtr} that contains the contents of {@code buffer}
public static @NotNull LongPtr allocate(@NotNull Arena arena, @NotNull LongBuffer buffer) {
MemorySegment s = arena.allocate(
ValueLayout.JAVA_LONG,
(long) buffer.remaining() * Long.BYTES
);
s.copyFrom(MemorySegment.ofBuffer(buffer));
return new LongPtr(s);
}
/// An iterator over the long integers.
private static final class Iter implements PrimitiveIterator.OfLong {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= Long.BYTES;
}
@Override
public long nextLong() {
if (!hasNext()) {
throw new NoSuchElementException("No more long integers to read");
}
long value = segment.get(ValueLayout.JAVA_LONG, 0);
segment = segment.asSlice(Long.BYTES);
return value;
}
private @NotNull MemorySegment segment;
}
}
+281
View File
@@ -0,0 +1,281 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.AddressLayout;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/// Represents a pointer to pointer(s) in native memory.
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@link AddressLayout#byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record PointerPtr(@NotNull MemorySegment segment) implements IPointer, Iterable<MemorySegment> {
public long size() {
return segment.byteSize() / ValueLayout.ADDRESS.byteSize();
}
public @NotNull MemorySegment read() {
return segment.get(ValueLayout.ADDRESS, 0);
}
public void write(@NotNull MemorySegment value) {
segment.set(ValueLayout.ADDRESS, 0, value);
}
public void write(@Nullable IPointer pointer) {
if (pointer != null) {
write(pointer.segment());
} else {
write(MemorySegment.NULL);
}
}
public @NotNull MemorySegment read(long index) {
return segment.get(ValueLayout.ADDRESS, index * ValueLayout.ADDRESS.byteSize());
}
public void write(long index, @NotNull MemorySegment value) {
segment.set(ValueLayout.ADDRESS, index * ValueLayout.ADDRESS.byteSize(), value);
}
public void write(long index, @Nullable IPointer pointer) {
if (pointer != null) {
write(index, pointer.segment());
} else {
write(index, MemorySegment.NULL);
}
}
/// Assume the {@link PointerPtr} is capable of holding at least {@code newSize} pointers,
/// create a new view {@link PointerPtr} that uses the same backing storage as this
/// {@link PointerPtr}, but with the new size. Since there is actually no way to really check
/// whether the new size is valid, while buffer overflow is undefined behavior, this method is
/// marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull PointerPtr reinterpret(long newSize) {
return new PointerPtr(segment.reinterpret(newSize * ValueLayout.ADDRESS.byteSize()));
}
public @NotNull PointerPtr offset(long offset) {
return new PointerPtr(segment.asSlice(offset * ValueLayout.ADDRESS.byteSize()));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull PointerPtr slice(long start, long end) {
return new PointerPtr(segment.asSlice(
start * ValueLayout.ADDRESS.byteSize(),
(end - start) * ValueLayout.ADDRESS.byteSize()
));
}
public @NotNull PointerPtr slice(long end) {
return new PointerPtr(segment.asSlice(0, end * ValueLayout.ADDRESS.byteSize()));
}
@Override
public @NotNull Iterator<MemorySegment> iterator() {
return new Iter(segment);
}
/// Create a new {@link PointerPtr} using {@code segment} as backing storage, with argument
/// validation.
///
/// This function does not ensure {@code segment}'s size to be a multiple of
/// {@link AddressLayout#byteSize()}, since that several trailing bytes could be automatically
/// ignored by {@link #size()} method, and usually these bytes does not interfere with FFI
/// operations. If {@code segment} is not big enough to hold at least one pointer, that segment
/// is simply considered "empty". See the documentation of {@link IPointer#segment()} for more
/// details.
///
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} is {@link MemorySegment#NULL},
/// otherwise a new {@link PointerPtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native or not properly aligned
public static @Nullable PointerPtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
if (segment.byteSize() % ValueLayout.ADDRESS.byteSize() != 0) {
throw new IllegalArgumentException("Segment size must be a multiple of " + ValueLayout.ADDRESS.byteSize());
}
return new PointerPtr(segment);
}
public static @NotNull PointerPtr allocate(@NotNull Arena arena) {
return new PointerPtr(arena.allocate(ValueLayout.ADDRESS));
}
public static @NotNull PointerPtr allocate(@NotNull Arena arena, long size) {
return new PointerPtr(arena.allocate(ValueLayout.ADDRESS, size));
}
public static @NotNull PointerPtr allocate(
@NotNull Arena arena,
Collection<@Nullable IPointer> pointers
) {
PointerPtr ret = allocate(arena, pointers.size());
int i = 0;
for (IPointer pointer : pointers) {
if (pointer != null) {
ret.write(i, pointer.segment());
} else {
ret.write(i, MemorySegment.NULL);
}
i += 1;
}
return ret;
}
public static @NotNull PointerPtr allocateR(
@NotNull Arena arena,
Collection<@NotNull MemorySegment> segments
) {
PointerPtr ret = allocate(arena, segments.size());
int i = 0;
for (MemorySegment segment : segments) {
ret.write(i, segment);
i += 1;
}
return ret;
}
public static @NotNull PointerPtr allocateV(
@NotNull Arena arena,
@Nullable IPointer pointer0,
@Nullable IPointer ...pointers
) {
PointerPtr ret = allocate(arena, pointers.length + 1);
ret.write(pointer0 != null ? pointer0.segment() : MemorySegment.NULL);
for (int i = 0; i < pointers.length; i++) {
if (pointers[i] != null) {
//noinspection DataFlowIssue
ret.write(i + 1, pointers[i].segment());
}
}
return ret;
}
public static @NotNull PointerPtr allocateV(
@NotNull Arena arena,
@NotNull MemorySegment segment0,
@NotNull MemorySegment ...segments
) {
PointerPtr ret = allocate(arena, segments.length + 1);
ret.write(segment0);
for (int i = 0; i < segments.length; i++) {
ret.write(i + 1, segments[i]);
}
return ret;
}
public static @NotNull PointerPtr allocateStrings(
@NotNull Arena arena,
@Nullable String string0,
@Nullable String @NotNull ...strings
) {
PointerPtr ret = allocate(arena, strings.length + 1);
if (string0 != null) {
ret.write(0, arena.allocateFrom(string0));
} else {
ret.write(0, MemorySegment.NULL);
}
for (int i = 0; i < strings.length; i++) {
if (strings[i] != null) {
ret.write(i + 1, arena.allocateFrom(strings[i]));
} else {
ret.write(i + 1, MemorySegment.NULL);
}
}
return ret;
}
public static @NotNull PointerPtr allocateStrings(
@NotNull Arena arena,
@Nullable String @NotNull [] strings
) {
PointerPtr ret = allocate(arena, strings.length);
for (int i = 0; i < strings.length; i++) {
if (strings[i] != null) {
ret.write(i, arena.allocateFrom(strings[i]));
} else {
ret.write(i, MemorySegment.NULL);
}
}
return ret;
}
public static @NotNull PointerPtr allocateStrings(
@NotNull Arena arena,
@NotNull Collection<@Nullable String> strings
) {
PointerPtr ret = allocate(arena, strings.size());
int i = 0;
for (String string : strings) {
if (string != null) {
ret.write(i, arena.allocateFrom(string));
} else {
ret.write(i, MemorySegment.NULL);
}
i += 1;
}
return ret;
}
/// An iterator over the pointers.
private static final class Iter implements Iterator<MemorySegment> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= ValueLayout.ADDRESS.byteSize();
}
@Override
public @NotNull MemorySegment next() {
if (!hasNext()) {
throw new NoSuchElementException("No more pointers to read");
}
MemorySegment value = segment.get(ValueLayout.ADDRESS, 0);
segment = segment.asSlice(ValueLayout.ADDRESS.byteSize());
return value;
}
private @NotNull MemorySegment segment;
}
}
+241
View File
@@ -0,0 +1,241 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.nio.Buffer;
import java.nio.ShortBuffer;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
/// Represents a pointer to 16-bit short integer(s) in native memory.
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@link ValueLayout.OfShort#byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record ShortPtr(@NotNull MemorySegment segment) implements IPointer, Iterable<Short> {
public long size() {
return segment.byteSize() / Short.BYTES;
}
public short read() {
return segment.get(ValueLayout.JAVA_SHORT, 0);
}
public void write(short value) {
segment.set(ValueLayout.JAVA_SHORT, 0, value);
}
public short read(long index) {
return segment.get(ValueLayout.JAVA_SHORT, index * Short.BYTES);
}
public void write(long index, short value) {
segment.set(ValueLayout.JAVA_SHORT, index * Short.BYTES, value);
}
public void write(short @NotNull [] values) {
segment.copyFrom(MemorySegment.ofArray(values));
}
public void writeV(short value0, short @NotNull ...values) {
write(value0);
offset(1).write(values);
}
/// Assume the {@link ShortPtr} is capable of holding at least {@code newSize} short integers,
/// create a new view {@link ShortPtr} that uses the same backing storage as this
/// {@link ShortPtr}, but with the new size. Since there is actually no way to really check
/// whether the new size is valid, while buffer overflow is undefined behavior, this method is
/// marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull ShortPtr reinterpret(long newSize) {
return new ShortPtr(segment.reinterpret(newSize * Short.BYTES));
}
public @NotNull ShortPtr offset(long offset) {
return new ShortPtr(segment.asSlice(offset * Short.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull ShortPtr slice(long start, long end) {
return new ShortPtr(segment.asSlice(start * Short.BYTES, (end - start) * Short.BYTES));
}
public @NotNull ShortPtr slice(long end) {
return new ShortPtr(segment.asSlice(0, end * Short.BYTES));
}
@Override
public @NotNull Iterator<Short> iterator() {
return new Iter(segment);
}
/// Create a new {@link ShortPtr} using {@code segment} as backing storage, with argument
/// validation.
///
/// This function does not ensure {@code segment}'s size to be a multiple of
/// {@link Short#BYTES}, since that several trailing bytes could be automatically ignored by
/// {@link #size()} method, and usually these bytes does not interfere with FFI operations.
/// If {@code segment} is not big enough to hold at least one short integer, that segment is
/// simply considered "empty". See the documentation of {@link IPointer#segment()} for more
/// details.
///
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} is {@link MemorySegment#NULL},
/// otherwise a new {@link ShortPtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native or not properly aligned
public static @Nullable ShortPtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
if (segment.address() % ValueLayout.JAVA_SHORT.byteAlignment() != 0) {
throw new IllegalArgumentException("Segment address must be aligned to " + ValueLayout.JAVA_SHORT.byteAlignment() + " bytes");
}
return new ShortPtr(segment);
}
/// Create a new {@link ShortPtr} using the same backing storage as {@code buffer}, with
/// argument validation.
///
/// The main difference between this static method and the {@link #allocate(Arena, ShortBuffer)}
/// method is that this method does not copy the contents of {@code buffer} into a newly
/// allocated {@link MemorySegment}. Instead, the newly created {@link ShortPtr} will use the
/// same backing storage as {@code buffer}. Thus, modifications from one side will be visible on
/// the other side.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be referred. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be referred, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link ShortBuffer#order()} property. {@link ShortPtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param buffer the {@link ShortBuffer} to use as the backing storage
/// @return a new {@link ShortPtr} that uses {@code buffer} as its backing storage
/// @throws IllegalArgumentException if {@code buffer} is not direct, or its backing storage is
/// not properly aligned
public static @NotNull ShortPtr checked(@NotNull ShortBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalArgumentException("Buffer must be direct");
}
MemorySegment segment = MemorySegment.ofBuffer(buffer);
if (segment.address() % ValueLayout.JAVA_SHORT.byteAlignment() != 0) {
throw new IllegalArgumentException("Buffer address must be aligned to " + ValueLayout.JAVA_SHORT.byteAlignment() + " bytes");
}
return new ShortPtr(segment);
}
public static @NotNull ShortPtr allocate(@NotNull Arena arena) {
return new ShortPtr(arena.allocate(ValueLayout.JAVA_SHORT));
}
public static @NotNull ShortPtr allocate(@NotNull Arena arena, long size) {
return new ShortPtr(arena.allocate(ValueLayout.JAVA_SHORT, size));
}
public static @NotNull ShortPtr allocate(@NotNull Arena arena, short @NotNull [] array) {
return new ShortPtr(arena.allocateFrom(ValueLayout.JAVA_SHORT, array));
}
public static @NotNull ShortPtr allocate(@NotNull Arena arena, Collection<Short> shorts) {
ShortPtr ret = allocate(arena, shorts.size());
int i = 0;
for (Short value : shorts) {
ret.write(i, value);
i += 1;
}
return ret;
}
public static @NotNull ShortPtr allocateV(@NotNull Arena arena, short value0, short ...values) {
ShortPtr ret = allocate(arena, values.length + 1);
ret.write(value0);
ret.offset(1).segment.copyFrom(MemorySegment.ofArray(values));
return ret;
}
/// Allocate a new {@link ShortPtr} in {@code arena} and copy the contents of {@code buffer} into
/// the newly allocated {@link ShortPtr}.
///
/// Be careful with {@link java.nio} buffer types' {@link Buffer#position()} property: only the
/// "remaining" (from {@link Buffer#position()} to {@link Buffer#limit()}) part of
/// {@code buffer} will be copied. If you have ever read from {@code buffer}, and you want all
/// the contents of {@code buffer} to be copied, you may want to call {@link Buffer#rewind()}.
///
/// When handling data types consisting of multiple bytes, also be careful with endianness and
/// {@link ShortBuffer#order()} property. {@link ShortPtr} always uses the native endianness. So
/// if {@code buffer} uses a different endianness, you may want to convert it to the native
/// endianness first.
///
/// @param arena the {@link Arena} to allocate the new {@link ShortPtr} in
/// @param buffer the {@link ShortBuffer} to copy the contents from
/// @return a new {@link ShortPtr} that contains the contents of {@code buffer}
public static @NotNull ShortPtr allocate(@NotNull Arena arena, @NotNull ShortBuffer buffer) {
MemorySegment s = arena.allocate(ValueLayout.JAVA_SHORT, buffer.remaining());
s.copyFrom(MemorySegment.ofBuffer(buffer));
return new ShortPtr(s);
}
/// An iterator over the short integers.
private static final class Iter implements Iterator<Short> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= Short.BYTES;
}
@Override
public Short next() {
if (!hasNext()) {
throw new NoSuchElementException("No more short integers to read");
}
short value = segment.get(ValueLayout.JAVA_SHORT, 0);
segment = segment.asSlice(Short.BYTES);
return value;
}
private @NotNull MemorySegment segment;
}
}
+222
View File
@@ -0,0 +1,222 @@
package club.doki7.ffm.ptr;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.NativeLayout;
import club.doki7.ffm.annotation.Unsafe;
import club.doki7.ffm.annotation.UnsafeConstructor;
import club.doki7.ffm.annotation.ValueBasedCandidate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.PrimitiveIterator;
/// Represents a pointer to C `wchar_t` element(s) in native memory.
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code NativeLayout.WCHAR_T.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators. For
/// normal users, {@link #checked(MemorySegment)} is a good safe alternative.
@ValueBasedCandidate
@UnsafeConstructor
public record WCharPtr(@Override @NotNull MemorySegment segment) implements IPointer, Iterable<Integer> {
public long size() {
return segment.byteSize() / NativeLayout.WCHAR_SIZE;
}
public int read() {
return NativeLayout.readWCharT(segment, 0);
}
public void write(int value) {
NativeLayout.writeWCharT(segment, 0, value);
}
public int read(long index) {
return NativeLayout.readWCharT(segment, index * NativeLayout.WCHAR_SIZE);
}
public void write(long index, int value) {
NativeLayout.writeWCharT(segment, index * NativeLayout.WCHAR_SIZE, value);
}
/// **(Windows only)** Assume the {@link WCharPtr} is a Windows wide character string, reads the
/// string from the beginning of the underlying memory segment, until the first NUL byte is
/// encountered or the end of the segment is reached.
///
/// This function requires the size of the underlying segment to be a set correctly. If the
/// size is not known in advance and correctly set (for example, the {@link WCharPtr} or the
/// underlying {@link MemorySegment} is returned from some C API), you may use
/// {@link WCharPtr#readWString()} (note that it is {@link Unsafe}) instead.
public @NotNull String readWStringSafe() {
if (NativeLayout.WCHAR_SIZE != 2) {
throw new UnsupportedOperationException("readWStringSafe only supports 2-byte wchar_t");
}
long size = size();
for (long i = 0; i < size; i++) {
if (read(i) == 0) {
return createStringFromSegment(segment, i);
}
}
return createStringFromSegment(segment, size);
}
/// **(Windows only)** Assume the {@link WCharPtr} is a Windows wide character string, reads the
/// wide string from the beginning of the underlying memory segment, until the first NUL byte is
/// encountered.
///
/// This function is {@link Unsafe} because it does not check the size of the underlying
/// memory segment. This function is suitable for the cases that the size of the underlying
/// memory segment is now known in advance or correctly set (for example, the {@link WCharPtr}
/// or the underlying {@link MemorySegment} is returned from some C API). If the size is
/// correctly set, you may use {@link #readWStringSafe()} instead.
@Unsafe
public @NotNull String readWString() {
if (NativeLayout.WCHAR_SIZE != 2) {
throw new UnsupportedOperationException("readWString only supports 2-byte wchar_t");
}
MemorySegment unsizedSegment = segment.reinterpret(Long.MAX_VALUE);
for (long i = 0; i < unsizedSegment.byteSize() / NativeLayout.WCHAR_SIZE; i++) {
if (NativeLayout.readWCharT(unsizedSegment, i * NativeLayout.WCHAR_SIZE) == 0) {
return createStringFromSegment(unsizedSegment, i);
}
}
throw new IllegalArgumentException("Segment size is too large to read as a string");
}
/// Assume the {@link WCharPtr} is capable of holding at least {@code newSize} elements, create
/// a new view {@link WCharPtr} that uses the same backing storage as this {@link WCharPtr}, but
/// with the new size. Since there is actually no way to really check whether the new size is
/// valid, while buffer overflow is undefined behavior, this method is marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
public @NotNull WCharPtr reinterpret(long newSize) {
return new WCharPtr(segment.reinterpret(newSize * NativeLayout.WCHAR_SIZE));
}
public @NotNull WCharPtr offset(long offset) {
return new WCharPtr(segment.asSlice(offset * NativeLayout.WCHAR_SIZE));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference.
public @NotNull WCharPtr slice(long start, long end) {
return new WCharPtr(segment.asSlice(
start * NativeLayout.WCHAR_SIZE,
(end - start) * NativeLayout.WCHAR_SIZE
));
}
public @NotNull WCharPtr slice(long end) {
return new WCharPtr(segment.asSlice(0, end * NativeLayout.WCHAR_SIZE));
}
@Override
public @NotNull PrimitiveIterator.OfInt iterator() {
return new Iter(segment);
}
/// Create a new {@link WCharPtr} with the given {@link MemorySegment} as the backing storage,
/// with argument validation.
///
/// This function does not ensure {@code segment}'s size to be a multiple of
/// {@link NativeLayout#WCHAR_SIZE}, since that several trailing bytes could be automatically
/// ignored by {@link #size()} method, and usually these bytes does not interfere with FFI
/// operations. If {@code segment} is not big enough to hold at least one element, that segment
/// is simply considered "empty". See the documentation of {@link IPointer#segment()} for more
/// details.
///
/// @param segment the {@link MemorySegment} to use as the backing storage
/// @return {@code null} if {@code segment} is {@link MemorySegment#NULL},
/// otherwise a new {@link WCharPtr} that uses {@code segment} as backing storage
/// @throws IllegalArgumentException if {@code segment} is not native or not properly aligned
public static @Nullable WCharPtr checked(@NotNull MemorySegment segment) {
if (segment.equals(MemorySegment.NULL)) {
return null;
}
if (!segment.isNative()) {
throw new IllegalArgumentException("Segment must be native");
}
if (segment.address() % NativeLayout.WCHAR_T.byteAlignment() != 0) {
throw new IllegalArgumentException("Segment address must be aligned to " + NativeLayout.WCHAR_T.byteAlignment() + " bytes");
}
return new WCharPtr(segment);
}
public static @NotNull WCharPtr allocate(@NotNull Arena arena) {
return new WCharPtr(arena.allocate(NativeLayout.WCHAR_T));
}
public static @NotNull WCharPtr allocate(@NotNull Arena arena, long size) {
return new WCharPtr(arena.allocate(NativeLayout.WCHAR_T, size));
}
/// **(Windows only)** allocate a new {@link WCharPtr} with the given string as the content.
public static @NotNull WCharPtr allocateWString(@NotNull Arena arena, @NotNull String string) {
if (NativeLayout.WCHAR_SIZE != 2) {
throw new UnsupportedOperationException("allocateWString only supports 2-byte wchar_t");
}
char[] charArray = string.toCharArray();
MemorySegment segment = arena.allocate(NativeLayout.WCHAR_T, charArray.length + 1);
segment.copyFrom(MemorySegment.ofArray(charArray));
segment.set(ValueLayout.JAVA_SHORT, (long) charArray.length * NativeLayout.WCHAR_SIZE, (short) 0);
return new WCharPtr(segment);
}
private static final class Iter implements PrimitiveIterator.OfInt {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
private @NotNull MemorySegment segment;
@Override
public boolean hasNext() {
return segment.byteSize() >= NativeLayout.WCHAR_SIZE;
}
@Override
public int nextInt() {
if (!hasNext()) {
throw new NoSuchElementException("No more elements to read");
}
int value = NativeLayout.readWCharT(segment, 0);
segment = segment.asSlice(NativeLayout.WCHAR_SIZE);
return value;
}
}
private static String createStringFromSegment(MemorySegment s, long charCount) {
if (charCount > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Segment size is too large to read as a string");
}
char[] characters = new char[(int) charCount];
MemorySegment.ofArray(characters)
.copyFrom(s.asSlice(0, charCount * NativeLayout.WCHAR_SIZE));
return new String(characters);
}
}
+33
View File
@@ -0,0 +1,33 @@
/// Utility classes encapsulating {@link java.lang.foreign.MemorySegment}, providing type-safe way
/// to access native memory.
///
/// ## Quick start
///
/// Java 22 FFM presents a new way to access native memory, using the new
/// {@link java.lang.foreign.MemorySegment}. However, unlike previous practices like
/// {@link java.nio.Buffer}, {@link java.lang.foreign.MemorySegment} does not have any type
/// information attached to it. Type information are more relevant with read/write operations,
/// not the memory segment itself.
///
/// In order to fill the gap, a dozen of handy pointer wrapper types are provided in this package.
/// For example, to manipulate a pointer to integer(s), you may use
/// {@link club.doki7.ffm.ptr.IntPtr}:
///
/// {@snippet :
/// try (Arena arena = Arena.ofConfined()) {
/// IntPtr ptr = IntPtr.allocate(arena);
/// ptr.write(42);
/// assert ptr.read() == 42;
///
/// // pointer to an array of integers
/// int[] arr = {1, 2, 3, 4, 5};
/// IntPtr pArray = IntPtr.allocate(arena, arr); // copy from Java array
/// assert pArray.size() == 5;
/// for (int i = 0; i < pArray.size(); i++) {
/// assert pArray.read(i) == arr[i];
/// }
/// }
/// }
///
/// See the documentation of classes in this package for more details.
package club.doki7.ffm.ptr;
+51
View File
@@ -0,0 +1,51 @@
package club.doki7.ffm.util;
import club.doki7.ffm.RawFunctionLoader;
import club.doki7.ffm.library.JavaSystemLibrary;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandle;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public final class UnixUtil {
/// Force the {@link UnixUtil} class to be loaded, in order to avoid some initialization order
/// issues.
///
/// @implNote The loading of {@link UnixUtil} is done in the static initializer, involving
/// retrieving several function handles. Thus, at the point you call the class methods, if the
/// `UnixUtil` class has not been loaded yet, the loading process may reset the `dlerror` value,
/// which is not what you want. This method is a workaround to force the class to be loaded and
/// initialized, so that the function handles are already retrieved, and the `dlerror` value is
/// not reset.
public static void forceLoad() {}
public static @Nullable String dlerror() {
try {
MethodHandle h = Objects.requireNonNull(hDlerror);
MemorySegment result = (MemorySegment) h.invokeExact();
if (result.equals(MemorySegment.NULL)) {
return null;
}
return result.reinterpret(Long.MAX_VALUE).getString(0, StandardCharsets.UTF_8);
} catch (Throwable e) {
return "dlerror failed to retrieve error message: " + e.getMessage();
}
}
private static final FunctionDescriptor DESCRIPTOR$dlerror =
FunctionDescriptor.of(ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_BYTE));
private static final @Nullable MethodHandle hDlerror;
static {
MemorySegment pfnDlerror = JavaSystemLibrary.INSTANCE.load("dlerror");
if (pfnDlerror.equals(MemorySegment.NULL)) {
hDlerror = null;
} else {
hDlerror = RawFunctionLoader.link(pfnDlerror, DESCRIPTOR$dlerror);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
package club.doki7.ffm.util;
import club.doki7.ffm.RawFunctionLoader;
import club.doki7.ffm.library.JavaSystemLibrary;
import org.jetbrains.annotations.Nullable;
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.util.Objects;
public final class WindowsUtil {
/// @deprecated According to documentation of {@link java.lang.foreign.Linker.Option Linker.Option},
/// Windows `GetLastError` should be retrieved via `captureCallState`. This function is not
/// correctly implemented and has known bugs.
@Deprecated(forRemoval = true, since = "0.4.2")
public static int getLastError() {
try {
MethodHandle h = Objects.requireNonNull(hGetLastError);
return (int) h.invokeExact();
} catch (Throwable e) {
return -1;
}
}
private static final FunctionDescriptor DESCRIPTOR$GetLastError =
FunctionDescriptor.of(ValueLayout.JAVA_INT);
private static final @Nullable MethodHandle hGetLastError;
static {
MemorySegment pfnGetLastError = JavaSystemLibrary.INSTANCE.load("GetLastError");
if (pfnGetLastError.equals(MemorySegment.NULL)) {
hGetLastError = null;
} else {
hGetLastError = RawFunctionLoader.link(pfnGetLastError, DESCRIPTOR$GetLastError);
}
}
}
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
+420
View File
@@ -0,0 +1,420 @@
package club.doki7.opencl;
import java.lang.foreign.*;
import java.lang.invoke.*;
import club.doki7.ffm.annotation.*;
import club.doki7.ffm.NativeLayout;
import org.jetbrains.annotations.NotNull;
import club.doki7.opencl.datatype.*;
import club.doki7.opencl.handle.*;
public final class CLFunctionTypes {
public static final FunctionDescriptor pfn_free_func_0 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.ADDRESS),
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_free_func_1 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.ADDRESS),
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_0 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_BYTE),
ValueLayout.ADDRESS,
NativeLayout.C_SIZE_T,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_1 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_BYTE),
ValueLayout.ADDRESS,
NativeLayout.C_SIZE_T,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_2 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_3 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_4 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_5 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_6 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_7 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_8 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor pfn_notify_9 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS,
ValueLayout.JAVA_INT,
ValueLayout.ADDRESS
);
public static final FunctionDescriptor user_func_0 = FunctionDescriptor.ofVoid(
ValueLayout.ADDRESS
);
@FunctionalInterface
public interface Ipfn_free_func_0 {
void invoke(
@NativeType("CLCommandQueue") MemorySegment p0,
@NativeType("cl_uint") @Unsigned int p1,
@Pointer(comment="void*") @NotNull MemorySegment p2,
@Pointer(comment="void*") @NotNull MemorySegment p3
);
static MethodHandle of(@NotNull Ipfn_free_func_0 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_free_func_0.class, "invoke", pfn_free_func_0.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_free_func_0 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_free_func_0 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_free_func_0, arena);
}
}
@FunctionalInterface
public interface Ipfn_free_func_1 {
void invoke(
@NativeType("CLCommandQueue") MemorySegment p0,
@NativeType("cl_uint") @Unsigned int p1,
@Pointer(comment="void*") @NotNull MemorySegment p2,
@Pointer(comment="void*") @NotNull MemorySegment p3
);
static MethodHandle of(@NotNull Ipfn_free_func_1 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_free_func_1.class, "invoke", pfn_free_func_1.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_free_func_1 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_free_func_1 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_free_func_1, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_0 {
void invoke(
@Pointer(comment="void*") @NotNull MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1,
@NativeType("size_t") MemorySegment p2,
@Pointer(comment="void*") @NotNull MemorySegment p3
);
static MethodHandle of(@NotNull Ipfn_notify_0 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_0.class, "invoke", pfn_notify_0.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_0 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_0 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_0, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_1 {
void invoke(
@Pointer(comment="void*") @NotNull MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1,
@NativeType("size_t") MemorySegment p2,
@Pointer(comment="void*") @NotNull MemorySegment p3
);
static MethodHandle of(@NotNull Ipfn_notify_1 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_1.class, "invoke", pfn_notify_1.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_1 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_1 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_1, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_2 {
void invoke(
@NativeType("CLContext") MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1
);
static MethodHandle of(@NotNull Ipfn_notify_2 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_2.class, "invoke", pfn_notify_2.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_2 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_2 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_2, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_3 {
void invoke(
@NativeType("CLMem") MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1
);
static MethodHandle of(@NotNull Ipfn_notify_3 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_3.class, "invoke", pfn_notify_3.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_3 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_3 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_3, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_4 {
void invoke(
@NativeType("CLMem") MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1
);
static MethodHandle of(@NotNull Ipfn_notify_4 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_4.class, "invoke", pfn_notify_4.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_4 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_4 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_4, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_5 {
void invoke(
@NativeType("CLProgram") MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1
);
static MethodHandle of(@NotNull Ipfn_notify_5 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_5.class, "invoke", pfn_notify_5.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_5 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_5 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_5, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_6 {
void invoke(
@NativeType("CLProgram") MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1
);
static MethodHandle of(@NotNull Ipfn_notify_6 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_6.class, "invoke", pfn_notify_6.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_6 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_6 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_6, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_7 {
void invoke(
@NativeType("CLProgram") MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1
);
static MethodHandle of(@NotNull Ipfn_notify_7 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_7.class, "invoke", pfn_notify_7.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_7 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_7 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_7, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_8 {
void invoke(
@NativeType("CLProgram") MemorySegment p0,
@Pointer(comment="void*") @NotNull MemorySegment p1
);
static MethodHandle of(@NotNull Ipfn_notify_8 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_8.class, "invoke", pfn_notify_8.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_8 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_8 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_8, arena);
}
}
@FunctionalInterface
public interface Ipfn_notify_9 {
void invoke(
@NativeType("CLEvent") MemorySegment p0,
@NativeType("cl_int") int p1,
@Pointer(comment="void*") @NotNull MemorySegment p2
);
static MethodHandle of(@NotNull Ipfn_notify_9 lambda) {
try {
return MethodHandles.lookup().findVirtual(Ipfn_notify_9.class, "invoke", pfn_notify_9.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Ipfn_notify_9 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Ipfn_notify_9 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), pfn_notify_9, arena);
}
}
@FunctionalInterface
public interface Iuser_func_0 {
void invoke(
@Pointer(comment="void*") @NotNull MemorySegment p0
);
static MethodHandle of(@NotNull Iuser_func_0 lambda) {
try {
return MethodHandles.lookup().findVirtual(Iuser_func_0.class, "invoke", user_func_0.toMethodType()).bindTo(lambda);
}
catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
static MemorySegment ofNative(@NotNull Iuser_func_0 lambda) {
return ofNative(Arena.global(), lambda);
}
static MemorySegment ofNative(@NotNull Arena arena, @NotNull Iuser_func_0 lambda) {
return Linker.nativeLinker().upcallStub(of(lambda), user_func_0, arena);
}
}
/// Constructing this class is nonsense so the constructor is made private.
private CLFunctionTypes() {}
}
Binary file not shown.
+29
View File
@@ -0,0 +1,29 @@
package club.doki7.opencl;
import org.jetbrains.annotations.NotNull;
public record CLVersion(int major, int minor, int patch) {
public static final int VERSION_MAJOR_BITS = 10;
public static final int VERSION_MINOR_BITS = 10;
public static final int VERSION_PATCH_BITS = 12;
public static final int VERSION_MAJOR_MASK = (1 << VERSION_MAJOR_BITS) - 1;
public static final int VERSION_MINOR_MASK = (1 << VERSION_MINOR_BITS) - 1;
public static final int VERSION_PATCH_MASK = (1 << VERSION_PATCH_BITS) - 1;
public static @NotNull CLVersion decode(int version) {
var major = version >> (VERSION_MINOR_BITS + VERSION_PATCH_BITS);
var minor = (version >> VERSION_PATCH_BITS) & VERSION_MINOR_MASK;
var patch = version & VERSION_PATCH_MASK;
return new CLVersion(major, minor, patch);
}
public int encode() {
var preMajor = (major & VERSION_MAJOR_MASK) << (VERSION_MINOR_BITS + VERSION_PATCH_BITS);
var preMinor = (minor & VERSION_MINOR_MASK) << VERSION_PATCH_BITS;
var prePatch = patch & VERSION_PATCH_BITS;
return preMajor | preMinor | prePatch;
}
}
Binary file not shown.
@@ -0,0 +1,210 @@
package club.doki7.opencl.datatype;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
import java.util.List;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.NativeLayout;
import club.doki7.ffm.annotation.*;
import club.doki7.ffm.ptr.*;
import club.doki7.opencl.handle.*;
import static club.doki7.opencl.CLConstants.*;
import club.doki7.opencl.CLFunctionTypes.*;
/// Represents a pointer to a <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_buffer_region.html">cl_buffer_region</a> structure in native memory.
///
/// ## Structure
///
/// {@snippet lang=c :
/// typedef struct cl_buffer_region {
/// size_t origin; // @link substring="origin" target="#origin"
/// size_t size; // @link substring="size" target="#size"
/// } cl_buffer_region;
/// }
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
///
/// @see <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_buffer_region.html">cl_buffer_region</a>
@ValueBasedCandidate
@UnsafeConstructor
public record CLBufferRegion(@NotNull MemorySegment segment) implements ICLBufferRegion {
/// Represents a pointer to / an array of <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_buffer_region.html">cl_buffer_region</a> structure(s) in native memory.
///
/// Technically speaking, this type has no difference with {@link CLBufferRegion}. This type
/// is introduced mainly for user to distinguish between a pointer to a single structure
/// and a pointer to (potentially) an array of structure(s). APIs should use interface
/// ICLBufferRegion to handle both types uniformly. See package level documentation for more
/// details.
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code CLBufferRegion.LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
@ValueBasedCandidate
@UnsafeConstructor
public record Ptr(@NotNull MemorySegment segment) implements ICLBufferRegion, Iterable<CLBufferRegion> {
public long size() {
return segment.byteSize() / CLBufferRegion.BYTES;
}
/// Returns (a pointer to) the structure at the given index.
///
/// Note that unlike {@code read} series functions ({@link IntPtr#read()} for
/// example), modification on returned structure will be reflected on the original
/// structure array. So this function is called {@code at} to explicitly
/// indicate that the returned structure is a view of the original structure.
public @NotNull CLBufferRegion at(long index) {
return new CLBufferRegion(segment.asSlice(index * CLBufferRegion.BYTES, CLBufferRegion.BYTES));
}
public CLBufferRegion.Ptr at(long index, @NotNull Consumer<@NotNull CLBufferRegion> consumer) {
consumer.accept(at(index));
return this;
}
public void write(long index, @NotNull CLBufferRegion value) {
MemorySegment s = segment.asSlice(index * CLBufferRegion.BYTES, CLBufferRegion.BYTES);
s.copyFrom(value.segment);
}
/// Assume the {@link Ptr} is capable of holding at least {@code newSize} structures,
/// create a new view {@link Ptr} that uses the same backing storage as this
/// {@link Ptr}, but with the new size. Since there is actually no way to really check
/// whether the new size is valid, while buffer overflow is undefined behavior, this method is
/// marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull Ptr reinterpret(long newSize) {
return new Ptr(segment.reinterpret(newSize * CLBufferRegion.BYTES));
}
public @NotNull Ptr offset(long offset) {
return new Ptr(segment.asSlice(offset * CLBufferRegion.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference
public @NotNull Ptr slice(long start, long end) {
return new Ptr(segment.asSlice(
start * CLBufferRegion.BYTES,
(end - start) * CLBufferRegion.BYTES
));
}
public Ptr slice(long end) {
return new Ptr(segment.asSlice(0, end * CLBufferRegion.BYTES));
}
public CLBufferRegion[] toArray() {
CLBufferRegion[] ret = new CLBufferRegion[(int) size()];
for (long i = 0; i < size(); i++) {
ret[(int) i] = at(i);
}
return ret;
}
@Override
public @NotNull Iterator<CLBufferRegion> iterator() {
return new Iter(this.segment());
}
/// An iterator over the structures.
private static final class Iter implements Iterator<CLBufferRegion> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= CLBufferRegion.BYTES;
}
@Override
public CLBufferRegion next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
CLBufferRegion ret = new CLBufferRegion(segment.asSlice(0, CLBufferRegion.BYTES));
segment = segment.asSlice(CLBufferRegion.BYTES);
return ret;
}
private @NotNull MemorySegment segment;
}
}
public static CLBufferRegion allocate(Arena arena) {
return new CLBufferRegion(arena.allocate(LAYOUT));
}
public static CLBufferRegion.Ptr allocate(Arena arena, long count) {
MemorySegment segment = arena.allocate(LAYOUT, count);
return new CLBufferRegion.Ptr(segment);
}
public static CLBufferRegion clone(Arena arena, CLBufferRegion src) {
CLBufferRegion ret = allocate(arena);
ret.segment.copyFrom(src.segment);
return ret;
}
public @Unsigned long origin() {
return NativeLayout.readCSizeT(segment, OFFSET$origin);
}
public CLBufferRegion origin(@Unsigned long value) {
NativeLayout.writeCSizeT(segment, OFFSET$origin, value);
return this;
}
public @Unsigned long size() {
return NativeLayout.readCSizeT(segment, OFFSET$size);
}
public CLBufferRegion size(@Unsigned long value) {
NativeLayout.writeCSizeT(segment, OFFSET$size, value);
return this;
}
public static final StructLayout LAYOUT = NativeLayout.structLayout(
NativeLayout.C_SIZE_T.withName("origin"),
NativeLayout.C_SIZE_T.withName("size")
);
public static final long BYTES = LAYOUT.byteSize();
public static final PathElement PATH$origin = PathElement.groupElement("origin");
public static final PathElement PATH$size = PathElement.groupElement("size");
public static final long SIZE$origin = NativeLayout.C_SIZE_T.byteSize();
public static final long SIZE$size = NativeLayout.C_SIZE_T.byteSize();
public static final long OFFSET$origin = LAYOUT.byteOffset(PATH$origin);
public static final long OFFSET$size = LAYOUT.byteOffset(PATH$size);
}
@@ -0,0 +1,272 @@
package club.doki7.opencl.datatype;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
import java.util.List;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.NativeLayout;
import club.doki7.ffm.annotation.*;
import club.doki7.ffm.ptr.*;
import club.doki7.opencl.handle.*;
import static club.doki7.opencl.CLConstants.*;
import club.doki7.opencl.CLFunctionTypes.*;
/// Represents a pointer to a <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_device_integer_dot_product_acceleration_properties_khr.html">cl_device_integer_dot_product_acceleration_properties_khr</a> structure in native memory.
///
/// ## Structure
///
/// {@snippet lang=c :
/// typedef struct cl_device_integer_dot_product_acceleration_properties_khr {
/// cl_bool signedAccelerated; // @link substring="signedAccelerated" target="#signedAccelerated"
/// cl_bool unsignedAccelerated; // @link substring="unsignedAccelerated" target="#unsignedAccelerated"
/// cl_bool mixedSignednessAccelerated; // @link substring="mixedSignednessAccelerated" target="#mixedSignednessAccelerated"
/// cl_bool accumulatingSaturatingSignedAccelerated; // @link substring="accumulatingSaturatingSignedAccelerated" target="#accumulatingSaturatingSignedAccelerated"
/// cl_bool accumulatingSaturatingUnsignedAccelerated; // @link substring="accumulatingSaturatingUnsignedAccelerated" target="#accumulatingSaturatingUnsignedAccelerated"
/// cl_bool accumulatingSaturatingMixedSignednessAccelerated; // @link substring="accumulatingSaturatingMixedSignednessAccelerated" target="#accumulatingSaturatingMixedSignednessAccelerated"
/// } cl_device_integer_dot_product_acceleration_properties_khr;
/// }
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
///
/// @see <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_device_integer_dot_product_acceleration_properties_khr.html">cl_device_integer_dot_product_acceleration_properties_khr</a>
@ValueBasedCandidate
@UnsafeConstructor
public record CLDeviceIntegerDotProductAccelerationPropertiesKhr(@NotNull MemorySegment segment) implements ICLDeviceIntegerDotProductAccelerationPropertiesKhr {
/// Represents a pointer to / an array of <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_device_integer_dot_product_acceleration_properties_khr.html">cl_device_integer_dot_product_acceleration_properties_khr</a> structure(s) in native memory.
///
/// Technically speaking, this type has no difference with {@link CLDeviceIntegerDotProductAccelerationPropertiesKhr}. This type
/// is introduced mainly for user to distinguish between a pointer to a single structure
/// and a pointer to (potentially) an array of structure(s). APIs should use interface
/// ICLDeviceIntegerDotProductAccelerationPropertiesKhr to handle both types uniformly. See package level documentation for more
/// details.
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code CLDeviceIntegerDotProductAccelerationPropertiesKhr.LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
@ValueBasedCandidate
@UnsafeConstructor
public record Ptr(@NotNull MemorySegment segment) implements ICLDeviceIntegerDotProductAccelerationPropertiesKhr, Iterable<CLDeviceIntegerDotProductAccelerationPropertiesKhr> {
public long size() {
return segment.byteSize() / CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES;
}
/// Returns (a pointer to) the structure at the given index.
///
/// Note that unlike {@code read} series functions ({@link IntPtr#read()} for
/// example), modification on returned structure will be reflected on the original
/// structure array. So this function is called {@code at} to explicitly
/// indicate that the returned structure is a view of the original structure.
public @NotNull CLDeviceIntegerDotProductAccelerationPropertiesKhr at(long index) {
return new CLDeviceIntegerDotProductAccelerationPropertiesKhr(segment.asSlice(index * CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES, CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES));
}
public CLDeviceIntegerDotProductAccelerationPropertiesKhr.Ptr at(long index, @NotNull Consumer<@NotNull CLDeviceIntegerDotProductAccelerationPropertiesKhr> consumer) {
consumer.accept(at(index));
return this;
}
public void write(long index, @NotNull CLDeviceIntegerDotProductAccelerationPropertiesKhr value) {
MemorySegment s = segment.asSlice(index * CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES, CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES);
s.copyFrom(value.segment);
}
/// Assume the {@link Ptr} is capable of holding at least {@code newSize} structures,
/// create a new view {@link Ptr} that uses the same backing storage as this
/// {@link Ptr}, but with the new size. Since there is actually no way to really check
/// whether the new size is valid, while buffer overflow is undefined behavior, this method is
/// marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull Ptr reinterpret(long newSize) {
return new Ptr(segment.reinterpret(newSize * CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES));
}
public @NotNull Ptr offset(long offset) {
return new Ptr(segment.asSlice(offset * CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference
public @NotNull Ptr slice(long start, long end) {
return new Ptr(segment.asSlice(
start * CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES,
(end - start) * CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES
));
}
public Ptr slice(long end) {
return new Ptr(segment.asSlice(0, end * CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES));
}
public CLDeviceIntegerDotProductAccelerationPropertiesKhr[] toArray() {
CLDeviceIntegerDotProductAccelerationPropertiesKhr[] ret = new CLDeviceIntegerDotProductAccelerationPropertiesKhr[(int) size()];
for (long i = 0; i < size(); i++) {
ret[(int) i] = at(i);
}
return ret;
}
@Override
public @NotNull Iterator<CLDeviceIntegerDotProductAccelerationPropertiesKhr> iterator() {
return new Iter(this.segment());
}
/// An iterator over the structures.
private static final class Iter implements Iterator<CLDeviceIntegerDotProductAccelerationPropertiesKhr> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES;
}
@Override
public CLDeviceIntegerDotProductAccelerationPropertiesKhr next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
CLDeviceIntegerDotProductAccelerationPropertiesKhr ret = new CLDeviceIntegerDotProductAccelerationPropertiesKhr(segment.asSlice(0, CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES));
segment = segment.asSlice(CLDeviceIntegerDotProductAccelerationPropertiesKhr.BYTES);
return ret;
}
private @NotNull MemorySegment segment;
}
}
public static CLDeviceIntegerDotProductAccelerationPropertiesKhr allocate(Arena arena) {
return new CLDeviceIntegerDotProductAccelerationPropertiesKhr(arena.allocate(LAYOUT));
}
public static CLDeviceIntegerDotProductAccelerationPropertiesKhr.Ptr allocate(Arena arena, long count) {
MemorySegment segment = arena.allocate(LAYOUT, count);
return new CLDeviceIntegerDotProductAccelerationPropertiesKhr.Ptr(segment);
}
public static CLDeviceIntegerDotProductAccelerationPropertiesKhr clone(Arena arena, CLDeviceIntegerDotProductAccelerationPropertiesKhr src) {
CLDeviceIntegerDotProductAccelerationPropertiesKhr ret = allocate(arena);
ret.segment.copyFrom(src.segment);
return ret;
}
public @NativeType("cl_bool") @Unsigned int signedAccelerated() {
return segment.get(LAYOUT$signedAccelerated, OFFSET$signedAccelerated);
}
public CLDeviceIntegerDotProductAccelerationPropertiesKhr signedAccelerated(@NativeType("cl_bool") @Unsigned int value) {
segment.set(LAYOUT$signedAccelerated, OFFSET$signedAccelerated, value);
return this;
}
public @NativeType("cl_bool") @Unsigned int unsignedAccelerated() {
return segment.get(LAYOUT$unsignedAccelerated, OFFSET$unsignedAccelerated);
}
public CLDeviceIntegerDotProductAccelerationPropertiesKhr unsignedAccelerated(@NativeType("cl_bool") @Unsigned int value) {
segment.set(LAYOUT$unsignedAccelerated, OFFSET$unsignedAccelerated, value);
return this;
}
public @NativeType("cl_bool") @Unsigned int mixedSignednessAccelerated() {
return segment.get(LAYOUT$mixedSignednessAccelerated, OFFSET$mixedSignednessAccelerated);
}
public CLDeviceIntegerDotProductAccelerationPropertiesKhr mixedSignednessAccelerated(@NativeType("cl_bool") @Unsigned int value) {
segment.set(LAYOUT$mixedSignednessAccelerated, OFFSET$mixedSignednessAccelerated, value);
return this;
}
public @NativeType("cl_bool") @Unsigned int accumulatingSaturatingSignedAccelerated() {
return segment.get(LAYOUT$accumulatingSaturatingSignedAccelerated, OFFSET$accumulatingSaturatingSignedAccelerated);
}
public CLDeviceIntegerDotProductAccelerationPropertiesKhr accumulatingSaturatingSignedAccelerated(@NativeType("cl_bool") @Unsigned int value) {
segment.set(LAYOUT$accumulatingSaturatingSignedAccelerated, OFFSET$accumulatingSaturatingSignedAccelerated, value);
return this;
}
public @NativeType("cl_bool") @Unsigned int accumulatingSaturatingUnsignedAccelerated() {
return segment.get(LAYOUT$accumulatingSaturatingUnsignedAccelerated, OFFSET$accumulatingSaturatingUnsignedAccelerated);
}
public CLDeviceIntegerDotProductAccelerationPropertiesKhr accumulatingSaturatingUnsignedAccelerated(@NativeType("cl_bool") @Unsigned int value) {
segment.set(LAYOUT$accumulatingSaturatingUnsignedAccelerated, OFFSET$accumulatingSaturatingUnsignedAccelerated, value);
return this;
}
public @NativeType("cl_bool") @Unsigned int accumulatingSaturatingMixedSignednessAccelerated() {
return segment.get(LAYOUT$accumulatingSaturatingMixedSignednessAccelerated, OFFSET$accumulatingSaturatingMixedSignednessAccelerated);
}
public CLDeviceIntegerDotProductAccelerationPropertiesKhr accumulatingSaturatingMixedSignednessAccelerated(@NativeType("cl_bool") @Unsigned int value) {
segment.set(LAYOUT$accumulatingSaturatingMixedSignednessAccelerated, OFFSET$accumulatingSaturatingMixedSignednessAccelerated, value);
return this;
}
public static final StructLayout LAYOUT = NativeLayout.structLayout(
ValueLayout.JAVA_INT.withName("signedAccelerated"),
ValueLayout.JAVA_INT.withName("unsignedAccelerated"),
ValueLayout.JAVA_INT.withName("mixedSignednessAccelerated"),
ValueLayout.JAVA_INT.withName("accumulatingSaturatingSignedAccelerated"),
ValueLayout.JAVA_INT.withName("accumulatingSaturatingUnsignedAccelerated"),
ValueLayout.JAVA_INT.withName("accumulatingSaturatingMixedSignednessAccelerated")
);
public static final long BYTES = LAYOUT.byteSize();
public static final PathElement PATH$signedAccelerated = PathElement.groupElement("signedAccelerated");
public static final PathElement PATH$unsignedAccelerated = PathElement.groupElement("unsignedAccelerated");
public static final PathElement PATH$mixedSignednessAccelerated = PathElement.groupElement("mixedSignednessAccelerated");
public static final PathElement PATH$accumulatingSaturatingSignedAccelerated = PathElement.groupElement("accumulatingSaturatingSignedAccelerated");
public static final PathElement PATH$accumulatingSaturatingUnsignedAccelerated = PathElement.groupElement("accumulatingSaturatingUnsignedAccelerated");
public static final PathElement PATH$accumulatingSaturatingMixedSignednessAccelerated = PathElement.groupElement("accumulatingSaturatingMixedSignednessAccelerated");
public static final OfInt LAYOUT$signedAccelerated = (OfInt) LAYOUT.select(PATH$signedAccelerated);
public static final OfInt LAYOUT$unsignedAccelerated = (OfInt) LAYOUT.select(PATH$unsignedAccelerated);
public static final OfInt LAYOUT$mixedSignednessAccelerated = (OfInt) LAYOUT.select(PATH$mixedSignednessAccelerated);
public static final OfInt LAYOUT$accumulatingSaturatingSignedAccelerated = (OfInt) LAYOUT.select(PATH$accumulatingSaturatingSignedAccelerated);
public static final OfInt LAYOUT$accumulatingSaturatingUnsignedAccelerated = (OfInt) LAYOUT.select(PATH$accumulatingSaturatingUnsignedAccelerated);
public static final OfInt LAYOUT$accumulatingSaturatingMixedSignednessAccelerated = (OfInt) LAYOUT.select(PATH$accumulatingSaturatingMixedSignednessAccelerated);
public static final long SIZE$signedAccelerated = LAYOUT$signedAccelerated.byteSize();
public static final long SIZE$unsignedAccelerated = LAYOUT$unsignedAccelerated.byteSize();
public static final long SIZE$mixedSignednessAccelerated = LAYOUT$mixedSignednessAccelerated.byteSize();
public static final long SIZE$accumulatingSaturatingSignedAccelerated = LAYOUT$accumulatingSaturatingSignedAccelerated.byteSize();
public static final long SIZE$accumulatingSaturatingUnsignedAccelerated = LAYOUT$accumulatingSaturatingUnsignedAccelerated.byteSize();
public static final long SIZE$accumulatingSaturatingMixedSignednessAccelerated = LAYOUT$accumulatingSaturatingMixedSignednessAccelerated.byteSize();
public static final long OFFSET$signedAccelerated = LAYOUT.byteOffset(PATH$signedAccelerated);
public static final long OFFSET$unsignedAccelerated = LAYOUT.byteOffset(PATH$unsignedAccelerated);
public static final long OFFSET$mixedSignednessAccelerated = LAYOUT.byteOffset(PATH$mixedSignednessAccelerated);
public static final long OFFSET$accumulatingSaturatingSignedAccelerated = LAYOUT.byteOffset(PATH$accumulatingSaturatingSignedAccelerated);
public static final long OFFSET$accumulatingSaturatingUnsignedAccelerated = LAYOUT.byteOffset(PATH$accumulatingSaturatingUnsignedAccelerated);
public static final long OFFSET$accumulatingSaturatingMixedSignednessAccelerated = LAYOUT.byteOffset(PATH$accumulatingSaturatingMixedSignednessAccelerated);
}
@@ -0,0 +1,242 @@
package club.doki7.opencl.datatype;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
import java.util.List;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.NativeLayout;
import club.doki7.ffm.annotation.*;
import club.doki7.ffm.ptr.*;
import club.doki7.opencl.handle.*;
import static club.doki7.opencl.CLConstants.*;
import club.doki7.opencl.CLFunctionTypes.*;
/// Represents a pointer to a <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_device_pci_bus_info_khr.html">cl_device_pci_bus_info_khr</a> structure in native memory.
///
/// ## Structure
///
/// {@snippet lang=c :
/// typedef struct cl_device_pci_bus_info_khr {
/// cl_uint pciDomain; // @link substring="pciDomain" target="#pciDomain"
/// cl_uint pciBus; // @link substring="pciBus" target="#pciBus"
/// cl_uint pciDevice; // @link substring="pciDevice" target="#pciDevice"
/// cl_uint pciFunction; // @link substring="pciFunction" target="#pciFunction"
/// } cl_device_pci_bus_info_khr;
/// }
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
///
/// @see <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_device_pci_bus_info_khr.html">cl_device_pci_bus_info_khr</a>
@ValueBasedCandidate
@UnsafeConstructor
public record CLDevicePciBusInfoKhr(@NotNull MemorySegment segment) implements ICLDevicePciBusInfoKhr {
/// Represents a pointer to / an array of <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_device_pci_bus_info_khr.html">cl_device_pci_bus_info_khr</a> structure(s) in native memory.
///
/// Technically speaking, this type has no difference with {@link CLDevicePciBusInfoKhr}. This type
/// is introduced mainly for user to distinguish between a pointer to a single structure
/// and a pointer to (potentially) an array of structure(s). APIs should use interface
/// ICLDevicePciBusInfoKhr to handle both types uniformly. See package level documentation for more
/// details.
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code CLDevicePciBusInfoKhr.LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
@ValueBasedCandidate
@UnsafeConstructor
public record Ptr(@NotNull MemorySegment segment) implements ICLDevicePciBusInfoKhr, Iterable<CLDevicePciBusInfoKhr> {
public long size() {
return segment.byteSize() / CLDevicePciBusInfoKhr.BYTES;
}
/// Returns (a pointer to) the structure at the given index.
///
/// Note that unlike {@code read} series functions ({@link IntPtr#read()} for
/// example), modification on returned structure will be reflected on the original
/// structure array. So this function is called {@code at} to explicitly
/// indicate that the returned structure is a view of the original structure.
public @NotNull CLDevicePciBusInfoKhr at(long index) {
return new CLDevicePciBusInfoKhr(segment.asSlice(index * CLDevicePciBusInfoKhr.BYTES, CLDevicePciBusInfoKhr.BYTES));
}
public CLDevicePciBusInfoKhr.Ptr at(long index, @NotNull Consumer<@NotNull CLDevicePciBusInfoKhr> consumer) {
consumer.accept(at(index));
return this;
}
public void write(long index, @NotNull CLDevicePciBusInfoKhr value) {
MemorySegment s = segment.asSlice(index * CLDevicePciBusInfoKhr.BYTES, CLDevicePciBusInfoKhr.BYTES);
s.copyFrom(value.segment);
}
/// Assume the {@link Ptr} is capable of holding at least {@code newSize} structures,
/// create a new view {@link Ptr} that uses the same backing storage as this
/// {@link Ptr}, but with the new size. Since there is actually no way to really check
/// whether the new size is valid, while buffer overflow is undefined behavior, this method is
/// marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull Ptr reinterpret(long newSize) {
return new Ptr(segment.reinterpret(newSize * CLDevicePciBusInfoKhr.BYTES));
}
public @NotNull Ptr offset(long offset) {
return new Ptr(segment.asSlice(offset * CLDevicePciBusInfoKhr.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference
public @NotNull Ptr slice(long start, long end) {
return new Ptr(segment.asSlice(
start * CLDevicePciBusInfoKhr.BYTES,
(end - start) * CLDevicePciBusInfoKhr.BYTES
));
}
public Ptr slice(long end) {
return new Ptr(segment.asSlice(0, end * CLDevicePciBusInfoKhr.BYTES));
}
public CLDevicePciBusInfoKhr[] toArray() {
CLDevicePciBusInfoKhr[] ret = new CLDevicePciBusInfoKhr[(int) size()];
for (long i = 0; i < size(); i++) {
ret[(int) i] = at(i);
}
return ret;
}
@Override
public @NotNull Iterator<CLDevicePciBusInfoKhr> iterator() {
return new Iter(this.segment());
}
/// An iterator over the structures.
private static final class Iter implements Iterator<CLDevicePciBusInfoKhr> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= CLDevicePciBusInfoKhr.BYTES;
}
@Override
public CLDevicePciBusInfoKhr next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
CLDevicePciBusInfoKhr ret = new CLDevicePciBusInfoKhr(segment.asSlice(0, CLDevicePciBusInfoKhr.BYTES));
segment = segment.asSlice(CLDevicePciBusInfoKhr.BYTES);
return ret;
}
private @NotNull MemorySegment segment;
}
}
public static CLDevicePciBusInfoKhr allocate(Arena arena) {
return new CLDevicePciBusInfoKhr(arena.allocate(LAYOUT));
}
public static CLDevicePciBusInfoKhr.Ptr allocate(Arena arena, long count) {
MemorySegment segment = arena.allocate(LAYOUT, count);
return new CLDevicePciBusInfoKhr.Ptr(segment);
}
public static CLDevicePciBusInfoKhr clone(Arena arena, CLDevicePciBusInfoKhr src) {
CLDevicePciBusInfoKhr ret = allocate(arena);
ret.segment.copyFrom(src.segment);
return ret;
}
public @NativeType("cl_uint") @Unsigned int pciDomain() {
return segment.get(LAYOUT$pciDomain, OFFSET$pciDomain);
}
public CLDevicePciBusInfoKhr pciDomain(@NativeType("cl_uint") @Unsigned int value) {
segment.set(LAYOUT$pciDomain, OFFSET$pciDomain, value);
return this;
}
public @NativeType("cl_uint") @Unsigned int pciBus() {
return segment.get(LAYOUT$pciBus, OFFSET$pciBus);
}
public CLDevicePciBusInfoKhr pciBus(@NativeType("cl_uint") @Unsigned int value) {
segment.set(LAYOUT$pciBus, OFFSET$pciBus, value);
return this;
}
public @NativeType("cl_uint") @Unsigned int pciDevice() {
return segment.get(LAYOUT$pciDevice, OFFSET$pciDevice);
}
public CLDevicePciBusInfoKhr pciDevice(@NativeType("cl_uint") @Unsigned int value) {
segment.set(LAYOUT$pciDevice, OFFSET$pciDevice, value);
return this;
}
public @NativeType("cl_uint") @Unsigned int pciFunction() {
return segment.get(LAYOUT$pciFunction, OFFSET$pciFunction);
}
public CLDevicePciBusInfoKhr pciFunction(@NativeType("cl_uint") @Unsigned int value) {
segment.set(LAYOUT$pciFunction, OFFSET$pciFunction, value);
return this;
}
public static final StructLayout LAYOUT = NativeLayout.structLayout(
ValueLayout.JAVA_INT.withName("pciDomain"),
ValueLayout.JAVA_INT.withName("pciBus"),
ValueLayout.JAVA_INT.withName("pciDevice"),
ValueLayout.JAVA_INT.withName("pciFunction")
);
public static final long BYTES = LAYOUT.byteSize();
public static final PathElement PATH$pciDomain = PathElement.groupElement("pciDomain");
public static final PathElement PATH$pciBus = PathElement.groupElement("pciBus");
public static final PathElement PATH$pciDevice = PathElement.groupElement("pciDevice");
public static final PathElement PATH$pciFunction = PathElement.groupElement("pciFunction");
public static final OfInt LAYOUT$pciDomain = (OfInt) LAYOUT.select(PATH$pciDomain);
public static final OfInt LAYOUT$pciBus = (OfInt) LAYOUT.select(PATH$pciBus);
public static final OfInt LAYOUT$pciDevice = (OfInt) LAYOUT.select(PATH$pciDevice);
public static final OfInt LAYOUT$pciFunction = (OfInt) LAYOUT.select(PATH$pciFunction);
public static final long SIZE$pciDomain = LAYOUT$pciDomain.byteSize();
public static final long SIZE$pciBus = LAYOUT$pciBus.byteSize();
public static final long SIZE$pciDevice = LAYOUT$pciDevice.byteSize();
public static final long SIZE$pciFunction = LAYOUT$pciFunction.byteSize();
public static final long OFFSET$pciDomain = LAYOUT.byteOffset(PATH$pciDomain);
public static final long OFFSET$pciBus = LAYOUT.byteOffset(PATH$pciBus);
public static final long OFFSET$pciDevice = LAYOUT.byteOffset(PATH$pciDevice);
public static final long OFFSET$pciFunction = LAYOUT.byteOffset(PATH$pciFunction);
}
@@ -0,0 +1,233 @@
package club.doki7.opencl.datatype;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
import java.util.List;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.NativeLayout;
import club.doki7.ffm.annotation.*;
import club.doki7.ffm.ptr.*;
import club.doki7.opencl.handle.*;
import static club.doki7.opencl.CLConstants.*;
import club.doki7.opencl.CLFunctionTypes.*;
/// Represents a pointer to a <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_dx9_surface_info_khr.html">cl_dx9_surface_info_khr</a> structure in native memory.
///
/// ## Structure
///
/// {@snippet lang=c :
/// typedef struct cl_dx9_surface_info_khr {
/// IDirect3DSurface9* resource; // @link substring="resource" target="#resource"
/// HANDLE sharedHandle; // @link substring="sharedHandle" target="#sharedHandle"
/// } cl_dx9_surface_info_khr;
/// }
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
///
/// @see <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_dx9_surface_info_khr.html">cl_dx9_surface_info_khr</a>
@ValueBasedCandidate
@UnsafeConstructor
public record CLDx9SurfaceInfoKhr(@NotNull MemorySegment segment) implements ICLDx9SurfaceInfoKhr {
/// Represents a pointer to / an array of <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_dx9_surface_info_khr.html">cl_dx9_surface_info_khr</a> structure(s) in native memory.
///
/// Technically speaking, this type has no difference with {@link CLDx9SurfaceInfoKhr}. This type
/// is introduced mainly for user to distinguish between a pointer to a single structure
/// and a pointer to (potentially) an array of structure(s). APIs should use interface
/// ICLDx9SurfaceInfoKhr to handle both types uniformly. See package level documentation for more
/// details.
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code CLDx9SurfaceInfoKhr.LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
@ValueBasedCandidate
@UnsafeConstructor
public record Ptr(@NotNull MemorySegment segment) implements ICLDx9SurfaceInfoKhr, Iterable<CLDx9SurfaceInfoKhr> {
public long size() {
return segment.byteSize() / CLDx9SurfaceInfoKhr.BYTES;
}
/// Returns (a pointer to) the structure at the given index.
///
/// Note that unlike {@code read} series functions ({@link IntPtr#read()} for
/// example), modification on returned structure will be reflected on the original
/// structure array. So this function is called {@code at} to explicitly
/// indicate that the returned structure is a view of the original structure.
public @NotNull CLDx9SurfaceInfoKhr at(long index) {
return new CLDx9SurfaceInfoKhr(segment.asSlice(index * CLDx9SurfaceInfoKhr.BYTES, CLDx9SurfaceInfoKhr.BYTES));
}
public CLDx9SurfaceInfoKhr.Ptr at(long index, @NotNull Consumer<@NotNull CLDx9SurfaceInfoKhr> consumer) {
consumer.accept(at(index));
return this;
}
public void write(long index, @NotNull CLDx9SurfaceInfoKhr value) {
MemorySegment s = segment.asSlice(index * CLDx9SurfaceInfoKhr.BYTES, CLDx9SurfaceInfoKhr.BYTES);
s.copyFrom(value.segment);
}
/// Assume the {@link Ptr} is capable of holding at least {@code newSize} structures,
/// create a new view {@link Ptr} that uses the same backing storage as this
/// {@link Ptr}, but with the new size. Since there is actually no way to really check
/// whether the new size is valid, while buffer overflow is undefined behavior, this method is
/// marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull Ptr reinterpret(long newSize) {
return new Ptr(segment.reinterpret(newSize * CLDx9SurfaceInfoKhr.BYTES));
}
public @NotNull Ptr offset(long offset) {
return new Ptr(segment.asSlice(offset * CLDx9SurfaceInfoKhr.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference
public @NotNull Ptr slice(long start, long end) {
return new Ptr(segment.asSlice(
start * CLDx9SurfaceInfoKhr.BYTES,
(end - start) * CLDx9SurfaceInfoKhr.BYTES
));
}
public Ptr slice(long end) {
return new Ptr(segment.asSlice(0, end * CLDx9SurfaceInfoKhr.BYTES));
}
public CLDx9SurfaceInfoKhr[] toArray() {
CLDx9SurfaceInfoKhr[] ret = new CLDx9SurfaceInfoKhr[(int) size()];
for (long i = 0; i < size(); i++) {
ret[(int) i] = at(i);
}
return ret;
}
@Override
public @NotNull Iterator<CLDx9SurfaceInfoKhr> iterator() {
return new Iter(this.segment());
}
/// An iterator over the structures.
private static final class Iter implements Iterator<CLDx9SurfaceInfoKhr> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= CLDx9SurfaceInfoKhr.BYTES;
}
@Override
public CLDx9SurfaceInfoKhr next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
CLDx9SurfaceInfoKhr ret = new CLDx9SurfaceInfoKhr(segment.asSlice(0, CLDx9SurfaceInfoKhr.BYTES));
segment = segment.asSlice(CLDx9SurfaceInfoKhr.BYTES);
return ret;
}
private @NotNull MemorySegment segment;
}
}
public static CLDx9SurfaceInfoKhr allocate(Arena arena) {
return new CLDx9SurfaceInfoKhr(arena.allocate(LAYOUT));
}
public static CLDx9SurfaceInfoKhr.Ptr allocate(Arena arena, long count) {
MemorySegment segment = arena.allocate(LAYOUT, count);
return new CLDx9SurfaceInfoKhr.Ptr(segment);
}
public static CLDx9SurfaceInfoKhr clone(Arena arena, CLDx9SurfaceInfoKhr src) {
CLDx9SurfaceInfoKhr ret = allocate(arena);
ret.segment.copyFrom(src.segment);
return ret;
}
/// Note: the returned {@link PointerPtr} does not have correct {@link PointerPtr#size} property. It's up
/// to user to track the size of the buffer, and use {@link PointerPtr#reinterpret} to set the size before
/// actually reading from or writing to the buffer.
public @Nullable PointerPtr resource() {
MemorySegment s = resourceRaw();
if (s.equals(MemorySegment.NULL)) {
return null;
}
return new PointerPtr(s);
}
public CLDx9SurfaceInfoKhr resource(@Nullable PointerPtr value) {
MemorySegment s = value == null ? MemorySegment.NULL : value.segment();
resourceRaw(s);
return this;
}
public @Pointer(comment="IDirect3DSurface9*") @NotNull MemorySegment resourceRaw() {
return segment.get(LAYOUT$resource, OFFSET$resource);
}
public void resourceRaw(@Pointer(comment="IDirect3DSurface9*") @NotNull MemorySegment value) {
segment.set(LAYOUT$resource, OFFSET$resource, value);
}
public @Pointer(comment="HANDLE") @NotNull MemorySegment sharedHandle() {
return segment.get(LAYOUT$sharedHandle, OFFSET$sharedHandle);
}
public CLDx9SurfaceInfoKhr sharedHandle(@Pointer(comment="HANDLE") @NotNull MemorySegment value) {
segment.set(LAYOUT$sharedHandle, OFFSET$sharedHandle, value);
return this;
}
public CLDx9SurfaceInfoKhr sharedHandle(@Nullable IPointer pointer) {
sharedHandle(pointer != null ? pointer.segment() : MemorySegment.NULL);
return this;
}
public static final StructLayout LAYOUT = NativeLayout.structLayout(
ValueLayout.ADDRESS.withTargetLayout(ValueLayout.ADDRESS).withName("resource"),
ValueLayout.ADDRESS.withName("sharedHandle")
);
public static final long BYTES = LAYOUT.byteSize();
public static final PathElement PATH$resource = PathElement.groupElement("resource");
public static final PathElement PATH$sharedHandle = PathElement.groupElement("sharedHandle");
public static final AddressLayout LAYOUT$resource = (AddressLayout) LAYOUT.select(PATH$resource);
public static final AddressLayout LAYOUT$sharedHandle = (AddressLayout) LAYOUT.select(PATH$sharedHandle);
public static final long SIZE$resource = LAYOUT$resource.byteSize();
public static final long SIZE$sharedHandle = LAYOUT$sharedHandle.byteSize();
public static final long OFFSET$resource = LAYOUT.byteOffset(PATH$resource);
public static final long OFFSET$sharedHandle = LAYOUT.byteOffset(PATH$sharedHandle);
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,331 @@
package club.doki7.opencl.datatype;
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
import java.util.List;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.function.Consumer;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import club.doki7.ffm.IPointer;
import club.doki7.ffm.NativeLayout;
import club.doki7.ffm.annotation.*;
import club.doki7.ffm.ptr.*;
import club.doki7.opencl.handle.*;
import static club.doki7.opencl.CLConstants.*;
import club.doki7.opencl.CLFunctionTypes.*;
/// Represents a pointer to a <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_image_desc.html">cl_image_desc</a> structure in native memory.
///
/// ## Structure
///
/// {@snippet lang=c :
/// typedef struct cl_image_desc {
/// cl_mem_object_type imageType; // @link substring="imageType" target="#imageType"
/// size_t imageWidth; // @link substring="imageWidth" target="#imageWidth"
/// size_t imageHeight; // @link substring="imageHeight" target="#imageHeight"
/// size_t imageDepth; // @link substring="imageDepth" target="#imageDepth"
/// size_t imageArraySize; // @link substring="imageArraySize" target="#imageArraySize"
/// size_t imageRowPitch; // @link substring="imageRowPitch" target="#imageRowPitch"
/// size_t imageSlicePitch; // @link substring="imageSlicePitch" target="#imageSlicePitch"
/// cl_uint numMipLevels; // @link substring="numMipLevels" target="#numMipLevels"
/// cl_uint numSamples; // @link substring="numSamples" target="#numSamples"
/// cl_image_desc_buffer_or_mem_object bufferOrMemObject; // @link substring="CLImageDescBufferOrMemObject" target="CLImageDescBufferOrMemObject" @link substring="bufferOrMemObject" target="#bufferOrMemObject"
/// } cl_image_desc;
/// }
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
///
/// @see <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_image_desc.html">cl_image_desc</a>
@ValueBasedCandidate
@UnsafeConstructor
public record CLImageDesc(@NotNull MemorySegment segment) implements ICLImageDesc {
/// Represents a pointer to / an array of <a href="https://registry.khronos.org/OpenCL/sdk/latest/docs/man/html/cl_image_desc.html">cl_image_desc</a> structure(s) in native memory.
///
/// Technically speaking, this type has no difference with {@link CLImageDesc}. This type
/// is introduced mainly for user to distinguish between a pointer to a single structure
/// and a pointer to (potentially) an array of structure(s). APIs should use interface
/// ICLImageDesc to handle both types uniformly. See package level documentation for more
/// details.
///
/// ## Contracts
///
/// The property {@link #segment()} should always be not-null
/// ({@code segment != NULL && !segment.equals(MemorySegment.NULL)}), and properly aligned to
/// {@code CLImageDesc.LAYOUT.byteAlignment()} bytes. To represent null pointer, you may use a Java
/// {@code null} instead. See the documentation of {@link IPointer#segment()} for more details.
///
/// The constructor of this class is marked as {@link UnsafeConstructor}, because it does not
/// perform any runtime check. The constructor can be useful for automatic code generators.
@ValueBasedCandidate
@UnsafeConstructor
public record Ptr(@NotNull MemorySegment segment) implements ICLImageDesc, Iterable<CLImageDesc> {
public long size() {
return segment.byteSize() / CLImageDesc.BYTES;
}
/// Returns (a pointer to) the structure at the given index.
///
/// Note that unlike {@code read} series functions ({@link IntPtr#read()} for
/// example), modification on returned structure will be reflected on the original
/// structure array. So this function is called {@code at} to explicitly
/// indicate that the returned structure is a view of the original structure.
public @NotNull CLImageDesc at(long index) {
return new CLImageDesc(segment.asSlice(index * CLImageDesc.BYTES, CLImageDesc.BYTES));
}
public CLImageDesc.Ptr at(long index, @NotNull Consumer<@NotNull CLImageDesc> consumer) {
consumer.accept(at(index));
return this;
}
public void write(long index, @NotNull CLImageDesc value) {
MemorySegment s = segment.asSlice(index * CLImageDesc.BYTES, CLImageDesc.BYTES);
s.copyFrom(value.segment);
}
/// Assume the {@link Ptr} is capable of holding at least {@code newSize} structures,
/// create a new view {@link Ptr} that uses the same backing storage as this
/// {@link Ptr}, but with the new size. Since there is actually no way to really check
/// whether the new size is valid, while buffer overflow is undefined behavior, this method is
/// marked as {@link Unsafe}.
///
/// This method could be useful when handling data returned from some C API, where the size of
/// the data is not known in advance.
///
/// If the size of the underlying segment is actually known in advance and correctly set, and
/// you want to create a shrunk view, you may use {@link #slice(long)} (with validation)
/// instead.
@Unsafe
public @NotNull Ptr reinterpret(long newSize) {
return new Ptr(segment.reinterpret(newSize * CLImageDesc.BYTES));
}
public @NotNull Ptr offset(long offset) {
return new Ptr(segment.asSlice(offset * CLImageDesc.BYTES));
}
/// Note that this function uses the {@link List#subList(int, int)} semantics (left inclusive,
/// right exclusive interval), not {@link MemorySegment#asSlice(long, long)} semantics
/// (offset + newSize). Be careful with the difference
public @NotNull Ptr slice(long start, long end) {
return new Ptr(segment.asSlice(
start * CLImageDesc.BYTES,
(end - start) * CLImageDesc.BYTES
));
}
public Ptr slice(long end) {
return new Ptr(segment.asSlice(0, end * CLImageDesc.BYTES));
}
public CLImageDesc[] toArray() {
CLImageDesc[] ret = new CLImageDesc[(int) size()];
for (long i = 0; i < size(); i++) {
ret[(int) i] = at(i);
}
return ret;
}
@Override
public @NotNull Iterator<CLImageDesc> iterator() {
return new Iter(this.segment());
}
/// An iterator over the structures.
private static final class Iter implements Iterator<CLImageDesc> {
Iter(@NotNull MemorySegment segment) {
this.segment = segment;
}
@Override
public boolean hasNext() {
return segment.byteSize() >= CLImageDesc.BYTES;
}
@Override
public CLImageDesc next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
CLImageDesc ret = new CLImageDesc(segment.asSlice(0, CLImageDesc.BYTES));
segment = segment.asSlice(CLImageDesc.BYTES);
return ret;
}
private @NotNull MemorySegment segment;
}
}
public static CLImageDesc allocate(Arena arena) {
return new CLImageDesc(arena.allocate(LAYOUT));
}
public static CLImageDesc.Ptr allocate(Arena arena, long count) {
MemorySegment segment = arena.allocate(LAYOUT, count);
return new CLImageDesc.Ptr(segment);
}
public static CLImageDesc clone(Arena arena, CLImageDesc src) {
CLImageDesc ret = allocate(arena);
ret.segment.copyFrom(src.segment);
return ret;
}
public @NativeType("cl_mem_object_type") @Unsigned int imageType() {
return segment.get(LAYOUT$imageType, OFFSET$imageType);
}
public CLImageDesc imageType(@NativeType("cl_mem_object_type") @Unsigned int value) {
segment.set(LAYOUT$imageType, OFFSET$imageType, value);
return this;
}
public @Unsigned long imageWidth() {
return NativeLayout.readCSizeT(segment, OFFSET$imageWidth);
}
public CLImageDesc imageWidth(@Unsigned long value) {
NativeLayout.writeCSizeT(segment, OFFSET$imageWidth, value);
return this;
}
public @Unsigned long imageHeight() {
return NativeLayout.readCSizeT(segment, OFFSET$imageHeight);
}
public CLImageDesc imageHeight(@Unsigned long value) {
NativeLayout.writeCSizeT(segment, OFFSET$imageHeight, value);
return this;
}
public @Unsigned long imageDepth() {
return NativeLayout.readCSizeT(segment, OFFSET$imageDepth);
}
public CLImageDesc imageDepth(@Unsigned long value) {
NativeLayout.writeCSizeT(segment, OFFSET$imageDepth, value);
return this;
}
public @Unsigned long imageArraySize() {
return NativeLayout.readCSizeT(segment, OFFSET$imageArraySize);
}
public CLImageDesc imageArraySize(@Unsigned long value) {
NativeLayout.writeCSizeT(segment, OFFSET$imageArraySize, value);
return this;
}
public @Unsigned long imageRowPitch() {
return NativeLayout.readCSizeT(segment, OFFSET$imageRowPitch);
}
public CLImageDesc imageRowPitch(@Unsigned long value) {
NativeLayout.writeCSizeT(segment, OFFSET$imageRowPitch, value);
return this;
}
public @Unsigned long imageSlicePitch() {
return NativeLayout.readCSizeT(segment, OFFSET$imageSlicePitch);
}
public CLImageDesc imageSlicePitch(@Unsigned long value) {
NativeLayout.writeCSizeT(segment, OFFSET$imageSlicePitch, value);
return this;
}
public @NativeType("cl_uint") @Unsigned int numMipLevels() {
return segment.get(LAYOUT$numMipLevels, OFFSET$numMipLevels);
}
public CLImageDesc numMipLevels(@NativeType("cl_uint") @Unsigned int value) {
segment.set(LAYOUT$numMipLevels, OFFSET$numMipLevels, value);
return this;
}
public @NativeType("cl_uint") @Unsigned int numSamples() {
return segment.get(LAYOUT$numSamples, OFFSET$numSamples);
}
public CLImageDesc numSamples(@NativeType("cl_uint") @Unsigned int value) {
segment.set(LAYOUT$numSamples, OFFSET$numSamples, value);
return this;
}
public @NotNull CLImageDescBufferOrMemObject bufferOrMemObject() {
return new CLImageDescBufferOrMemObject(segment.asSlice(OFFSET$bufferOrMemObject, LAYOUT$bufferOrMemObject));
}
public CLImageDesc bufferOrMemObject(@NotNull CLImageDescBufferOrMemObject value) {
MemorySegment.copy(value.segment(), 0, segment, OFFSET$bufferOrMemObject, SIZE$bufferOrMemObject);
return this;
}
public CLImageDesc bufferOrMemObject(Consumer<@NotNull CLImageDescBufferOrMemObject> consumer) {
consumer.accept(bufferOrMemObject());
return this;
}
public static final StructLayout LAYOUT = NativeLayout.structLayout(
ValueLayout.JAVA_INT.withName("imageType"),
NativeLayout.C_SIZE_T.withName("imageWidth"),
NativeLayout.C_SIZE_T.withName("imageHeight"),
NativeLayout.C_SIZE_T.withName("imageDepth"),
NativeLayout.C_SIZE_T.withName("imageArraySize"),
NativeLayout.C_SIZE_T.withName("imageRowPitch"),
NativeLayout.C_SIZE_T.withName("imageSlicePitch"),
ValueLayout.JAVA_INT.withName("numMipLevels"),
ValueLayout.JAVA_INT.withName("numSamples"),
CLImageDescBufferOrMemObject.LAYOUT.withName("bufferOrMemObject")
);
public static final long BYTES = LAYOUT.byteSize();
public static final PathElement PATH$imageType = PathElement.groupElement("imageType");
public static final PathElement PATH$imageWidth = PathElement.groupElement("imageWidth");
public static final PathElement PATH$imageHeight = PathElement.groupElement("imageHeight");
public static final PathElement PATH$imageDepth = PathElement.groupElement("imageDepth");
public static final PathElement PATH$imageArraySize = PathElement.groupElement("imageArraySize");
public static final PathElement PATH$imageRowPitch = PathElement.groupElement("imageRowPitch");
public static final PathElement PATH$imageSlicePitch = PathElement.groupElement("imageSlicePitch");
public static final PathElement PATH$numMipLevels = PathElement.groupElement("numMipLevels");
public static final PathElement PATH$numSamples = PathElement.groupElement("numSamples");
public static final PathElement PATH$bufferOrMemObject = PathElement.groupElement("bufferOrMemObject");
public static final OfInt LAYOUT$imageType = (OfInt) LAYOUT.select(PATH$imageType);
public static final OfInt LAYOUT$numMipLevels = (OfInt) LAYOUT.select(PATH$numMipLevels);
public static final OfInt LAYOUT$numSamples = (OfInt) LAYOUT.select(PATH$numSamples);
public static final UnionLayout LAYOUT$bufferOrMemObject = (UnionLayout) LAYOUT.select(PATH$bufferOrMemObject);
public static final long SIZE$imageType = LAYOUT$imageType.byteSize();
public static final long SIZE$imageWidth = NativeLayout.C_SIZE_T.byteSize();
public static final long SIZE$imageHeight = NativeLayout.C_SIZE_T.byteSize();
public static final long SIZE$imageDepth = NativeLayout.C_SIZE_T.byteSize();
public static final long SIZE$imageArraySize = NativeLayout.C_SIZE_T.byteSize();
public static final long SIZE$imageRowPitch = NativeLayout.C_SIZE_T.byteSize();
public static final long SIZE$imageSlicePitch = NativeLayout.C_SIZE_T.byteSize();
public static final long SIZE$numMipLevels = LAYOUT$numMipLevels.byteSize();
public static final long SIZE$numSamples = LAYOUT$numSamples.byteSize();
public static final long SIZE$bufferOrMemObject = LAYOUT$bufferOrMemObject.byteSize();
public static final long OFFSET$imageType = LAYOUT.byteOffset(PATH$imageType);
public static final long OFFSET$imageWidth = LAYOUT.byteOffset(PATH$imageWidth);
public static final long OFFSET$imageHeight = LAYOUT.byteOffset(PATH$imageHeight);
public static final long OFFSET$imageDepth = LAYOUT.byteOffset(PATH$imageDepth);
public static final long OFFSET$imageArraySize = LAYOUT.byteOffset(PATH$imageArraySize);
public static final long OFFSET$imageRowPitch = LAYOUT.byteOffset(PATH$imageRowPitch);
public static final long OFFSET$imageSlicePitch = LAYOUT.byteOffset(PATH$imageSlicePitch);
public static final long OFFSET$numMipLevels = LAYOUT.byteOffset(PATH$numMipLevels);
public static final long OFFSET$numSamples = LAYOUT.byteOffset(PATH$numSamples);
public static final long OFFSET$bufferOrMemObject = LAYOUT.byteOffset(PATH$bufferOrMemObject);
}

Some files were not shown because too many files have changed in this diff Show More