getCodeTemplate()
451 | {
452 | ByteArrayOutputStream bout = new ByteArrayOutputStream();
453 | try {
454 | InputStream in = PassportBuilder.class.getResourceAsStream("Utils.class");
455 | byte data[] = new byte[4096];
456 | int len;
457 |
458 | while ((len = in.read(data)) > 0)
459 | bout.write(data, 0, len);
460 | in.close();
461 | }
462 | catch (IOException ex)
463 | {
464 | ex.printStackTrace();
465 | }
466 | ClassModel cm = ClassFile.of().parse(bout.toByteArray());
467 |
468 | for (var mm : cm.methods())
469 | {
470 | if (!mm.methodName().stringValue().equals("templatedMethod"))
471 | continue;
472 |
473 | for (CodeElement ce : mm.code().get().elements())
474 | {
475 | if (ce.toString().contains("ofConfined"))
476 | return Optional.of(ce);
477 | }
478 | }
479 |
480 | return Optional.empty();
481 | }
482 |
483 | }
484 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/PassportException.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport;
13 |
14 | public class PassportException extends Error
15 | {
16 | public PassportException(String msg) {
17 | super(msg);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/PassportFactory.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport;
13 |
14 |
15 | import jpassport.annotations.NotRequired;
16 | import jpassport.annotations.Critical;
17 |
18 | import java.io.File;
19 | import java.lang.foreign.*;
20 | import java.lang.invoke.MethodHandle;
21 | import java.lang.invoke.MethodHandles;
22 | import java.lang.invoke.MethodType;
23 | import java.lang.reflect.Field;
24 | import java.lang.reflect.Method;
25 | import java.lang.reflect.Modifier;
26 | import java.lang.reflect.Proxy;
27 | import java.util.*;
28 |
29 | public class PassportFactory
30 | {
31 | /**
32 | * Call this method to generate the library linkage. This version of the method will write the java file and compile
33 | * it. As a result, the start-up is a bit slower than {@link #proxy(String, Class) proxy()}, but the implementation
34 | * is a bit quicker.
35 | *
36 | * This version also supports Record -> struct conversions.
37 | *
38 | * @param libraryName The library name (the file name of the shared library without extension on all platforms,
39 | * without lib prefix on Linux and Mac). Use null to load system method calls (ex. malloc)
40 | * @param interfaceClass The class to wrap.
41 | * @param Any interface that extends Passport
42 | * @return A class linked to call into a DLL or SO using the Foreign Linker.
43 | */
44 | public synchronized static T link(String libraryName, Class interfaceClass) throws Throwable
45 | {
46 | if (!Passport.class.isAssignableFrom(interfaceClass)) {
47 | throw new IllegalArgumentException(
48 | String.format("Interface (%s) of library=%s does not extend %s",
49 | interfaceClass.getSimpleName(), libraryName, Passport.class.getSimpleName()));
50 | } else {
51 | return buildClass(libraryName, interfaceClass);
52 | }
53 | }
54 |
55 | /**
56 | * Call this method to generate the library linkage. This version of the method will write the java file and compile
57 | * it. As a result, the start-up is a bit slower than {@link #proxy(String, Class) proxy()}, but the implementation
58 | * is a bit quicker.
59 | *
60 | * This version also supports Record -> struct conversions.
61 | *
62 | * @param libraryName The library name (the file name of the shared library without extension on all platforms,
63 | * without lib prefix on Linux and Mac). Use null to load system method calls (ex. malloc)
64 | * @param interfaceClass The class to wrap.
65 | * @param Any interface that extends Passport
66 | * @return A class linked to call into a DLL or SO using the Foreign Linker.
67 | */
68 | public synchronized static T link_experimental(String libraryName, Class interfaceClass) throws Throwable
69 | {
70 | if (!Passport.class.isAssignableFrom(interfaceClass)) {
71 | throw new IllegalArgumentException(
72 | String.format("Interface (%s) of library=%s does not extend %s",
73 | interfaceClass.getSimpleName(), libraryName, Passport.class.getSimpleName()));
74 | } else {
75 | return buildClassExperimental(libraryName, interfaceClass);
76 | }
77 | }
78 |
79 | /**
80 | * Call this method to generate the library linkage. This version of the method uses a dynamic proxy to handle native
81 | * calls. As a result, the start-up is faster than {@link #link(String, Class) link()}, but the implementation is a bit slower.
82 | *
83 | * This method does not support Record -> struct conversions.
84 | *
85 | * @param libraryName The library name (the file name of the shared library without extension on all platforms,
86 | * without lib prefix on Linux and Mac). Use null to load system method calls (ex. malloc)
87 | * @param interfaceClass The class to wrap.
88 | * @param Any interface that extends Passport
89 | * @return A class linked to call into a DLL or SO using the Foreign Linker.
90 | */
91 | public static T proxy(String libraryName, Class interfaceClass) throws Throwable
92 | {
93 | if (!Passport.class.isAssignableFrom(interfaceClass)) {
94 | throw new IllegalArgumentException("Interface (" + interfaceClass.getSimpleName() + ") of library=" + libraryName + " does not extend " + Passport.class.getSimpleName());
95 | }
96 |
97 | var methods = loadMethodHandles(libraryName, interfaceClass);
98 | var handler = new PassportInvocationHandler(methods, interfaceClass);
99 | return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
100 | new Class[] { interfaceClass },
101 | handler);
102 | }
103 |
104 | private static T buildClass(String libName, Class interfaceClass) throws Throwable
105 | {
106 | HashMap handles = loadMethodHandles(libName, interfaceClass);
107 | PassportWriter classWriter = new PassportWriter<>(interfaceClass);
108 |
109 | return classWriter.build(handles);
110 | }
111 |
112 | private static T buildClassExperimental(String libName, Class interfaceClass) throws Throwable {
113 | HashMap handles = loadMethodHandles(libName, interfaceClass);
114 | PassportBuilder classWriter = new PassportBuilder<>(interfaceClass);
115 |
116 | return classWriter.build(handles);
117 | }
118 |
119 | /**
120 | * This method looks up the methods in the requested native library that match non-static
121 | * methods in the given interface class.
122 | *
123 | * @param libName Name of the native library to load, of null if the methods will be system method (ex. malloc).
124 | * @param interfaceClass The interface class to use as a reference for loading methods.
125 | * @return A map of Name to method handle pairs for the methods in the interface class.
126 | */
127 | public static HashMap loadMethodHandles(String libName, Class extends Passport> interfaceClass)
128 | {
129 | if (libName != null) {
130 | if (Utils.getPlatform().equals(Utils.Platform.Windows) && !libName.endsWith(".dll"))
131 | libName = libName + ".dll";
132 |
133 | File libPath = new File(libName);
134 | System.load(libPath.getAbsolutePath());
135 | }
136 | Linker cLinker = Linker.nativeLinker();
137 |
138 | List interfaceMethods = getDeclaredMethods(interfaceClass);
139 | HashMap methodMap = new HashMap<>();
140 |
141 | //if no library name is given then it must be a system library
142 | SymbolLookup lookup = libName == null ? cLinker.defaultLookup() : SymbolLookup.loaderLookup();
143 |
144 | for (Method method : interfaceMethods) {
145 | Class> retType = method.getReturnType();
146 | Class>[] parameters = method.getParameterTypes();
147 |
148 | for (int n = 0; n < parameters.length; ++n) {
149 | if (!parameters[n].isPrimitive() && !Arena.class.equals(parameters[n]))
150 | parameters[n] = MemorySegment.class;
151 | }
152 |
153 | MemoryLayout[] memoryLayout = Arrays.stream(parameters).filter(p -> !Arena.class.equals(p)).
154 | map(PassportFactory::classToMemory).toArray(MemoryLayout[]::new);
155 |
156 | FunctionDescriptor fd;
157 | if (void.class.equals(retType))
158 | fd = FunctionDescriptor.ofVoid(memoryLayout);
159 | else
160 | fd = FunctionDescriptor.of(classToMemory(retType), memoryLayout);
161 |
162 | var addr = lookup.find(method.getName()).orElse(null);
163 | if (addr == null && method.getAnnotation(NotRequired.class) == null)
164 | throw new PassportException("Could not find method in library: " + method.getName());
165 |
166 | if (addr != null) {
167 |
168 | MethodHandle methodHandle;
169 |
170 | if (method.getAnnotation(Critical.class) == null)
171 | methodHandle = cLinker.downcallHandle(addr, fd);
172 | else
173 | methodHandle = cLinker.downcallHandle(addr, fd, Linker.Option.critical(false));
174 |
175 | methodMap.put(method.getName(), methodHandle);
176 | }
177 | }
178 | loadNames(interfaceClass);
179 | return methodMap;
180 | }
181 |
182 | /**
183 | * This methods looks up all of the methods in the requested native library that match non-static
184 | * methods in the given interface class.
185 | *
186 | * @param interfaceClass The interface class to use as a reference for loading methods.
187 | */
188 | private static void loadNames(Class extends Passport> interfaceClass)
189 | {
190 | List names = getDeclaredNames(interfaceClass);
191 |
192 | SymbolLookup lookup = SymbolLookup.loaderLookup();
193 |
194 | for (Field field : names) {
195 | try {
196 | var named = ((NamedLookup)field.get(interfaceClass));
197 | var addr = lookup.find(named.name());
198 | if (addr.isEmpty() && field.getAnnotation(NotRequired.class) == null)
199 | throw new PassportException("Could not find field in library: " + named.name());
200 | addr.ifPresent(named::setAddress);
201 | } catch (IllegalAccessException e) {
202 | throw new PassportException("Could not find field in library: " + field.getName());
203 | }
204 | }
205 | }
206 |
207 | /**
208 | * Given an object and method name this will return a memory address that
209 | * corresponds to a method pointer that can be passed to native code.
210 | * The method cannot be static.
211 | *
212 | * @param ob The object that the method belongs to.
213 | * @param methodName The name of the method.
214 | * @return A pointer to a method handle that can be passed to native code.
215 | * @throws IllegalArgumentException if there is no method with the given name, or there is more
216 | * than 1 method with the given name.
217 | */
218 | public static FunctionPtr createCallback(Object ob, String methodName)
219 | {
220 | var methods = getDeclaredMethods(ob.getClass());
221 |
222 | methods = methods.stream().filter(m -> m.getName().equals(methodName)).toList();
223 | if (methods.isEmpty())
224 | throw new IllegalArgumentException("Could not find method " + methodName + " in class " + ob.getClass().getName());
225 | else if (methods.size() > 1)
226 | throw new IllegalArgumentException("Multiple overloads of method " + methodName + " in class " + ob.getClass().getName());
227 |
228 | Method callbackMethod = methods.get(0);
229 |
230 | Class> retType = callbackMethod.getReturnType();
231 | Class>[] parameters = callbackMethod.getParameterTypes();
232 |
233 | if (!retType.isPrimitive() && retType != MemorySegment.class)
234 | throw new IllegalArgumentException("Callback method must return void, primitives, or MemoryAddress, not " + retType.getName());
235 |
236 |
237 | for (Class> parameter : parameters) {
238 | if (!parameter.isPrimitive() && parameter != MemorySegment.class)
239 | throw new IllegalArgumentException("Callback parameters must be primitives or MemoryAddress, not " + parameter.getName());
240 | }
241 |
242 | MemoryLayout[] memoryLayout = Arrays.stream(parameters).map(PassportFactory::classToMemory).toArray(MemoryLayout[]::new);
243 | FunctionDescriptor fd;
244 | if (void.class.equals(retType))
245 | fd = FunctionDescriptor.ofVoid(memoryLayout);
246 | else
247 | fd = FunctionDescriptor.of(classToMemory(retType), memoryLayout);
248 |
249 | try {
250 | var handle = MethodHandles.publicLookup().findVirtual(ob.getClass(), methodName, MethodType.methodType(retType, parameters));
251 | var handleToCall = handle.bindTo(ob);
252 |
253 | var scope = Arena.ofAuto();
254 | return new FunctionPtr(scope, Linker.nativeLinker().upcallStub(handleToCall, fd, scope));
255 | }
256 | catch (NoSuchMethodException | IllegalAccessException ex)
257 | {
258 | throw new Error("Failed to create callback method", ex);
259 | }
260 | }
261 |
262 | static List getDeclaredMethods(Class> interfaceClass) {
263 | Method[] methods = interfaceClass.getDeclaredMethods();
264 | return Arrays.stream(methods).
265 | filter(method -> !Modifier.isStatic(method.getModifiers())).
266 | filter(method -> !method.isDefault()).toList();
267 | }
268 |
269 | static List getDeclaredNames(Class> interfaceClass) {
270 | Field[] fields = interfaceClass.getDeclaredFields();
271 | return Arrays.stream(fields).
272 | filter(field -> field.getType().equals(NamedLookup.class)).toList();
273 | }
274 |
275 |
276 | private static MemoryLayout classToMemory(Class> type)
277 | {
278 | if (double.class.equals(type))
279 | return ValueLayout.JAVA_DOUBLE;
280 | if (int.class.equals(type))
281 | return ValueLayout.JAVA_INT;
282 | if (float.class.equals(type))
283 | return ValueLayout.JAVA_FLOAT;
284 | if (short.class.equals(type))
285 | return ValueLayout.JAVA_SHORT;
286 | if (byte.class.equals(type))
287 | return ValueLayout.JAVA_BYTE;
288 | if (long.class.equals(type))
289 | return ValueLayout.JAVA_LONG;
290 | if (boolean.class.equals(type))
291 | return ValueLayout.JAVA_BOOLEAN;
292 |
293 | return ValueLayout.ADDRESS;
294 | }
295 |
296 | }
297 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/PassportInvocationHandler.java:
--------------------------------------------------------------------------------
1 | package jpassport;
2 |
3 | import java.lang.annotation.Annotation;
4 | import java.lang.foreign.Arena;
5 | import java.lang.foreign.MemorySegment;
6 | import java.lang.invoke.MethodHandle;
7 | import java.lang.reflect.InvocationHandler;
8 | import java.lang.reflect.Method;
9 | import java.lang.reflect.Parameter;
10 | import java.util.ArrayList;
11 | import java.util.Arrays;
12 | import java.util.HashMap;
13 |
14 | import static jpassport.PassportWriter.isGenericPtr;
15 |
16 | public class PassportInvocationHandler implements InvocationHandler {
17 | HashMap handles;
18 | boolean allArraysAreReadBack = false;
19 |
20 | PassportInvocationHandler( HashMap methods, Class interfaceClass)
21 | {
22 | handles = methods;
23 | allArraysAreReadBack = PassportWriter.isRefArg(interfaceClass.getAnnotations());
24 | }
25 |
26 | @Override
27 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
28 |
29 | Class> retType = method.getReturnType();
30 | Class>[] parameters = method.getParameterTypes();
31 | var params = method.getParameters();
32 | var mh = handles.get(method.getName());
33 | Annotation[][] paramAnnotations = method.getParameterAnnotations();
34 |
35 | if (method.getName().equals("hasMethod"))
36 | return handles.containsKey(args[0].toString());
37 | if (mh == null)
38 | throw new Error("Method does not exist");
39 |
40 | var passedArena = Arrays.stream(args).filter(a -> a instanceof Arena).findFirst();
41 |
42 | try (var scope = Arena.ofConfined()) {
43 | Arena useScope = (Arena) passedArena.orElse(scope);
44 | var largs = new ArrayList();
45 | for (int i = 0; i < parameters.length; ++i)
46 | {
47 | if (Arena.class.equals(parameters[i]))
48 | continue;
49 | largs.add(toArg(parameters[i], args[i], useScope, paramAnnotations[i], params[i]));
50 | }
51 |
52 | var ret = mh.invokeWithArguments(largs);
53 |
54 | for (int i = 0; i < largs.size(); ++i)
55 | {
56 | if (PassportWriter.isRefArg(paramAnnotations[i]) ||
57 | MemoryBlock.class.equals(parameters[i]) ||
58 | (parameters[i].isArray() && allArraysAreReadBack))
59 | readBack(args[i], largs.get(i));
60 | }
61 |
62 | if (String.class.equals(retType))
63 | return Utils.readString((MemorySegment) ret);
64 | else if (isGenericPtr(retType))
65 | {
66 | var cons = retType.getConstructor(MemorySegment.class);
67 | return cons.newInstance((MemorySegment)ret);
68 | }
69 | return ret;
70 | }
71 | }
72 |
73 |
74 | Object toArg(Class> type, Object value, Arena arena, Annotation[] annotations, Parameter p)
75 | {
76 | if (type.isRecord() || (type.isArray() && type.getComponentType().isRecord()))
77 | throw new IllegalArgumentException("Record types not supported");
78 |
79 | if (type.isPrimitive())
80 | return value;
81 |
82 | if (PassportWriter.isPtrPtrArg(annotations))
83 | {
84 | return switch (value)
85 | {
86 | case null -> MemorySegment.NULL;
87 | case byte[][] i -> Utils.toPtrPTrMS(arena, i);
88 | case char[][] i -> Utils.toPtrPTrMS(arena, i);
89 | case short[][] i -> Utils.toPtrPTrMS(arena, i);
90 | case int[][] i -> Utils.toPtrPTrMS(arena, i);
91 | case long[][] i -> Utils.toPtrPTrMS(arena, i);
92 | case float[][] i -> Utils.toPtrPTrMS(arena, i);
93 | case double[][] i -> Utils.toPtrPTrMS(arena, i);
94 |
95 | default -> value;
96 | };
97 | }
98 |
99 | var readBack = PassportWriter.isRefArgReadBackOnly(p);
100 | return switch (value)
101 | {
102 | case null -> MemorySegment.NULL;
103 | case GenericPointer g -> g.getPtr();
104 | case GenericPointer[] g -> Utils.toMS(arena, g,readBack);
105 | case String i -> Utils.toCString(i, arena);
106 | case MemoryBlock i -> i.toPtr(arena);
107 | case String[] i -> Utils.toCString(i, arena);
108 | case byte[] i -> Utils.toMS(arena, i, readBack);
109 | case char[] i -> Utils.toMS(arena, i, readBack);
110 | case short[] i -> Utils.toMS(arena, i, readBack);
111 | case int[] i -> Utils.toMS(arena, i, readBack);
112 | case long[] i -> Utils.toMS(arena, i, readBack);
113 | case float[] i -> Utils.toMS(arena, i, readBack);
114 | case double[] i -> Utils.toMS(arena, i, readBack);
115 | case byte[][] i -> Utils.toMS(arena, i, readBack);
116 | case char[][] i -> Utils.toMS(arena, i, readBack);
117 | case short[][] i -> Utils.toMS(arena, i, readBack);
118 | case int[][] i -> Utils.toMS(arena, i, readBack);
119 | case long[][] i -> Utils.toMS(arena, i, readBack);
120 | case float[][] i -> Utils.toMS(arena, i, readBack);
121 | case double[][] i -> Utils.toMS(arena, i, readBack);
122 |
123 | default -> value;
124 | };
125 | }
126 |
127 | void readBack(Object value, Object called)
128 | {
129 | if (value == null)
130 | return;
131 |
132 | switch (value)
133 | {
134 | // case null -> GenericPointer.NULL();
135 | //case GenericPointer g -> new GenericPointer();
136 |
137 | case byte[] i -> Utils.toArr(i, (MemorySegment) called);
138 | case char[] i -> Utils.toArr(i, (MemorySegment) called);
139 | case short[] i -> Utils.toArr(i, (MemorySegment) called);
140 | case int[] i -> Utils.toArr(i, (MemorySegment) called);
141 | case long[] i -> Utils.toArr(i, (MemorySegment) called);
142 | case float[] i -> Utils.toArr(i, (MemorySegment) called);
143 | case double[] i -> Utils.toArr(i, (MemorySegment) called);
144 |
145 | case GenericPointer[] g -> Utils.toArr(g, (MemorySegment) called);
146 | case String[] g -> Utils.fromCString((MemorySegment) called, g);
147 | case MemoryBlock fs -> fs.readBack();
148 | // case byte[][] i -> Utils.toArr(i, (MemorySegment) called);
149 | // case short[][] i -> Utils.toMS(arena, i, false);
150 | // case int[][] i -> Utils.toMS(arena, i, false);
151 | // case float[][] i -> Utils.toMS(arena, i, false);
152 | // case double[][] i -> Utils.toMS(arena, i, false);
153 |
154 | default -> {
155 | break;
156 | }
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/Pointer.java:
--------------------------------------------------------------------------------
1 | package jpassport;
2 |
3 | import java.lang.foreign.MemorySegment;
4 |
5 | public class Pointer extends GenericPointer{
6 | public Pointer(MemorySegment addr) {
7 | super(addr);
8 | }
9 |
10 | public Pointer() {
11 | super(MemorySegment.NULL);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/Utils.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport;
13 |
14 |
15 | import jpassport.annotations.Array;
16 | import jpassport.annotations.Ptr;
17 |
18 | import java.io.IOException;
19 | import java.lang.annotation.Annotation;
20 | import java.lang.foreign.*;
21 | import java.lang.invoke.MethodHandle;
22 | import java.lang.reflect.Field;
23 | import java.nio.file.FileVisitResult;
24 | import java.nio.file.Files;
25 | import java.nio.file.Path;
26 | import java.nio.file.SimpleFileVisitor;
27 | import java.nio.file.attribute.BasicFileAttributes;
28 | import java.util.ArrayList;
29 | import java.util.Arrays;
30 |
31 |
32 | public class Utils {
33 |
34 | public static MemorySegment toAddr(MemorySegment seg) {
35 | if (seg == null)
36 | return MemorySegment.NULL;
37 |
38 | return seg;
39 | }
40 |
41 | /* Double ///////////////////////////////////////////////////////////////// */
42 | public static MemorySegment toMS(SegmentAllocator scope, double[] arr, boolean isReadBackOnly) {
43 | if (arr == null)
44 | return null;
45 | return isReadBackOnly ? scope.allocate((long)arr.length * Double.BYTES) :
46 | scope.allocateFrom(ValueLayout.JAVA_DOUBLE, arr);
47 | }
48 |
49 | public static MemorySegment toMS(Arena scope, double[] arr, boolean isReadBackOnly) {
50 | if (arr == null)
51 | return null;
52 | return isReadBackOnly ? scope.allocate((long)arr.length * Double.BYTES) :
53 | scope.allocateFrom(ValueLayout.JAVA_DOUBLE, arr);
54 | }
55 |
56 | public static MemorySegment toMS(SegmentAllocator scope, double[][] arr, boolean isReadBackOnly) {
57 | if (arr == null)
58 | return null;
59 |
60 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Double.BYTES);
61 | int n = 0;
62 | for (double[] row : arr) {
63 | segment.asSlice(n, (long) row.length * Double.BYTES).copyFrom(MemorySegment.ofArray(row));
64 | n += row.length * Double.BYTES;
65 | }
66 |
67 | return segment;
68 | }
69 |
70 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, double[][] arr) {
71 | if (arr == null)
72 | return null;
73 |
74 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES);
75 | int n = 0;
76 | for (double[] a : arr) {
77 | MemorySegment subSeg = scope.allocateFrom(ValueLayout.JAVA_DOUBLE, a);
78 | segment.setAtIndex(ValueLayout.ADDRESS, n++, subSeg);
79 | }
80 | return segment;
81 | }
82 |
83 | public static void toArr(double[] arr, MemorySegment segment) {
84 | if (arr == null)
85 | return;
86 |
87 | MemorySegment.copy(segment, ValueLayout.JAVA_DOUBLE, 0, arr, 0, arr.length);
88 | }
89 |
90 | public static double[] toArr(ValueLayout.OfDouble layout, MemorySegment seg, MemorySegment addr, int count) {
91 | if (MemorySegment.NULL.equals(addr))
92 | return null;
93 |
94 | return slice(seg, addr, count * layout.byteSize()).toArray(layout);
95 | }
96 |
97 | public static double[] toArr(ValueLayout.OfDouble layout, MemorySegment addr, int count) {
98 | if (MemorySegment.NULL.equals(addr))
99 | return null;
100 |
101 | if (addr.byteSize() == 0)
102 | {
103 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret((long)Double.BYTES * count);
104 | return seg.toArray(ValueLayout.JAVA_DOUBLE);
105 | }
106 |
107 | return addr.asSlice(0, (long) count * Long.BYTES).toArray(layout);
108 | }
109 |
110 | /* Float ///////////////////////////////////////////////////////////////// */
111 |
112 | public static MemorySegment toMS(SegmentAllocator scope, float[] arr, boolean isReadBackOnly) {
113 | if (arr == null)
114 | return null;
115 |
116 | return isReadBackOnly ? scope.allocate(arr.length * Float.BYTES) :
117 | scope.allocateFrom(ValueLayout.JAVA_FLOAT, arr);
118 | }
119 |
120 | public static MemorySegment toMS(SegmentAllocator scope, float[][] arr, boolean isReadBackOnly) {
121 | if (arr == null)
122 | return null;
123 |
124 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Float.BYTES);
125 | int n = 0;
126 | for (float[] row : arr) {
127 | segment.asSlice(n, (long) row.length * Float.BYTES).copyFrom(MemorySegment.ofArray(row));
128 | n += row.length * Float.BYTES;
129 | }
130 |
131 | return segment;
132 | }
133 |
134 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, float[][] arr) {
135 | if (arr == null)
136 | return null;
137 |
138 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES);
139 | int n = 0;
140 | for (float[] a : arr) {
141 | MemorySegment subSeg = scope.allocateFrom(ValueLayout.JAVA_FLOAT, a);
142 | segment.setAtIndex(ValueLayout.ADDRESS, n++, subSeg);
143 | }
144 | return segment;
145 | }
146 |
147 | public static void toArr(float[] arr, MemorySegment segment) {
148 | if (arr == null)
149 | return;
150 |
151 | MemorySegment.copy(segment, ValueLayout.JAVA_FLOAT, 0, arr, 0, arr.length);
152 | }
153 |
154 | public static float[] toArr(ValueLayout.OfFloat layout, MemorySegment seg, MemorySegment addr, int count) {
155 | if (MemorySegment.NULL.equals(addr))
156 | return null;
157 |
158 | return slice(seg, addr, count * layout.byteSize()).toArray(layout);
159 | }
160 |
161 | public static float[] toArr(ValueLayout.OfFloat layout, MemorySegment addr, int count) {
162 | if (MemorySegment.NULL.equals(addr))
163 | return null;
164 |
165 | if (addr.byteSize() == 0)
166 | {
167 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret((long)Float.BYTES * count);
168 | return seg.toArray(ValueLayout.JAVA_FLOAT);
169 | }
170 |
171 | return addr.asSlice(0, (long) count * Long.BYTES).toArray(layout);
172 | }
173 |
174 | /* Pointers ///////////////////////////////////////////////////////////////// */
175 |
176 | public static MemorySegment toMS(SegmentAllocator scope, GenericPointer[] arr, boolean isReadBackOnly) {
177 | if (arr == null)
178 | return null;
179 |
180 | var pass = Arrays.stream(arr).mapToLong(gp -> gp == null ? MemorySegment.NULL.address() : gp.getPtr().address()).toArray();
181 | return isReadBackOnly ? scope.allocate((long)arr.length * Long.BYTES) :
182 | scope.allocateFrom(ValueLayout.JAVA_LONG, pass);
183 | }
184 | public static void toArr(GenericPointer[] arr, MemorySegment segment) {
185 | if (arr == null)
186 | return;
187 |
188 | var asLong = new long[arr.length];
189 | toArr(asLong, segment);
190 |
191 | for (int n = 0; n < asLong.length; ++n)
192 | {
193 | var addr = MemorySegment.ofAddress(asLong[n]);
194 | if (arr[n] == null)
195 | {
196 | try {
197 | var cons = arr.getClass().getComponentType().getConstructor(MemorySegment.class);
198 | arr[n] = (GenericPointer) cons.newInstance(addr);
199 | }
200 | catch(Exception ex)
201 | {
202 | ex.printStackTrace();
203 | }
204 | }
205 | else
206 | arr[n].ptr = addr;
207 | }
208 | }
209 |
210 | /* Long ///////////////////////////////////////////////////////////////// */
211 |
212 | public static MemorySegment toMS(SegmentAllocator scope, long[] arr, boolean isReadBackOnly) {
213 | if (arr == null)
214 | return null;
215 | return isReadBackOnly ? scope.allocate((long)arr.length * Long.BYTES) :
216 | scope.allocateFrom(ValueLayout.JAVA_LONG, arr);
217 | }
218 |
219 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, long[][] arr) {
220 | if (arr == null)
221 | return null;
222 |
223 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES);
224 | int n = 0;
225 | for (long[] a : arr) {
226 | MemorySegment subSeg = scope.allocateFrom(ValueLayout.JAVA_LONG, a);
227 | segment.setAtIndex(ValueLayout.ADDRESS, n++, subSeg);
228 | }
229 | return segment;
230 | }
231 |
232 | public static MemorySegment toMS(SegmentAllocator scope, long[][] arr, boolean isReadBackOnly) {
233 | if (arr == null)
234 | return null;
235 |
236 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Long.BYTES);
237 | int n = 0;
238 | for (long[] row : arr) {
239 | segment.asSlice(n, (long) row.length * Long.BYTES).copyFrom(MemorySegment.ofArray(row));
240 | n += row.length * Long.BYTES;
241 | }
242 |
243 | return segment;
244 | }
245 |
246 | public static void toArr(long[] arr, MemorySegment segment) {
247 | if (arr == null)
248 | return;
249 |
250 | MemorySegment.copy(segment, ValueLayout.JAVA_LONG, 0, arr, 0, arr.length);
251 | }
252 |
253 | public static long[] toArr(ValueLayout.OfLong layout, MemorySegment seg, MemorySegment addr, int count) {
254 | if (MemorySegment.NULL.equals(addr))
255 | return null;
256 |
257 | return slice(seg, addr, count * layout.byteSize()).toArray(layout);
258 | }
259 |
260 | public static long[] toArr(ValueLayout.OfLong layout, MemorySegment addr, int count) {
261 | if (MemorySegment.NULL.equals(addr))
262 | return null;
263 |
264 | if (addr.byteSize() == 0)
265 | {
266 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret((long)Long.BYTES * count);
267 | return seg.toArray(ValueLayout.JAVA_LONG);
268 | }
269 |
270 | return addr.asSlice(0, (long) count * Long.BYTES).toArray(layout);
271 | }
272 |
273 | /* Int ///////////////////////////////////////////////////////////////// */
274 |
275 | public static MemorySegment toMS(SegmentAllocator scope, int[] arr, boolean isReadBackOnly) {
276 | if (arr == null)
277 | return null;
278 |
279 | return isReadBackOnly ? scope.allocate(arr.length * Integer.BYTES) :
280 | scope.allocateFrom(ValueLayout.JAVA_INT, arr);
281 | }
282 |
283 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, int[][] arr) {
284 | if (arr == null)
285 | return null;
286 |
287 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES);
288 | int n = 0;
289 | for (int[] a : arr) {
290 | segment.setAtIndex(ValueLayout.ADDRESS, n++, scope.allocateFrom(ValueLayout.JAVA_INT, a));
291 | }
292 | return segment;
293 | }
294 |
295 | public static MemorySegment toMS(SegmentAllocator scope, int[][] arr, boolean isReadBackOnly) {
296 | if (arr == null)
297 | return null;
298 |
299 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Integer.BYTES);
300 | int n = 0;
301 | for (int[] row : arr) {
302 | segment.asSlice(n, (long) row.length * Integer.BYTES).copyFrom(MemorySegment.ofArray(row));
303 | n += row.length * Integer.BYTES;
304 | }
305 |
306 | return segment;
307 | }
308 |
309 | public static void toArr(int[] arr, MemorySegment segment) {
310 | if (arr == null)
311 | return;
312 |
313 | MemorySegment.copy(segment, ValueLayout.JAVA_INT, 0, arr, 0, arr.length);
314 | }
315 |
316 | public static int[] toArr(ValueLayout.OfInt layout, MemorySegment seg, MemorySegment addr, int count) {
317 | if (MemorySegment.NULL.equals(addr))
318 | return null;
319 |
320 | return slice(seg, addr, count * layout.byteSize()).toArray(layout);
321 | }
322 |
323 | public static int[] toArr(ValueLayout.OfInt layout, MemorySegment addr, int count) {
324 | if (MemorySegment.NULL.equals(addr))
325 | return null;
326 |
327 | if (addr.byteSize() == 0)
328 | {
329 | return MemorySegment.ofAddress(addr.address()).
330 | reinterpret((long)Integer.BYTES * count).toArray(ValueLayout.JAVA_INT);
331 | }
332 |
333 | return addr.asSlice(0, (long) count * Integer.BYTES).toArray(layout);
334 | }
335 |
336 |
337 | /* Short ///////////////////////////////////////////////////////////////// */
338 |
339 | public static MemorySegment toMS(SegmentAllocator scope, short[] arr, boolean isReadBackOnly) {
340 | if (arr == null)
341 | return null;
342 |
343 | return isReadBackOnly ? scope.allocate((int) (arr.length * Short.BYTES)) :
344 | scope.allocateFrom(ValueLayout.JAVA_SHORT, arr);
345 | }
346 |
347 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, short[][] arr) {
348 | if (arr == null)
349 | return null;
350 |
351 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES);
352 | int n = 0;
353 | for (short[] a : arr) {
354 | segment.setAtIndex(ValueLayout.ADDRESS, n++, scope.allocateFrom(ValueLayout.JAVA_SHORT, a));
355 | }
356 | return segment;
357 | }
358 |
359 | public static MemorySegment toMS(SegmentAllocator scope, short[][] arr, boolean isReadBackOnly) {
360 | if (arr == null)
361 | return null;
362 |
363 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Short.BYTES);
364 | int n = 0;
365 | for (short[] row : arr) {
366 | segment.asSlice(n, (long) row.length * Short.BYTES).copyFrom(MemorySegment.ofArray(row));
367 | n += row.length * Short.BYTES;
368 | }
369 |
370 | return segment;
371 | }
372 |
373 | public static void toArr(short[] arr, MemorySegment segment) {
374 | if (arr == null)
375 | return;
376 | MemorySegment.copy(segment, ValueLayout.JAVA_SHORT, 0, arr, 0, arr.length);
377 | }
378 |
379 | public static short[] toArr(ValueLayout.OfShort layout, MemorySegment seg, MemorySegment addr, int count) {
380 | if (MemorySegment.NULL.equals(addr))
381 | return null;
382 |
383 | return slice(seg, addr, count * layout.byteSize()).toArray(layout);
384 | }
385 |
386 | public static short[] toArr(ValueLayout.OfShort layout, MemorySegment addr, int count) {
387 | if (MemorySegment.NULL.equals(addr))
388 | return null;
389 |
390 | if (addr.byteSize() == 0)
391 | {
392 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret((long)Short.BYTES * count);
393 | return seg.toArray(ValueLayout.JAVA_SHORT);
394 | }
395 |
396 | return addr.asSlice(0, (long) count * Long.BYTES).toArray(layout);
397 | }
398 |
399 | /* Byte ///////////////////////////////////////////////////////////////// */
400 |
401 | public static MemorySegment toMS(SegmentAllocator scope, byte[] arr, boolean isReadBackOnly) {
402 | if (arr == null)
403 | return null;
404 |
405 | return isReadBackOnly ? scope.allocate(arr.length) : scope.allocateFrom(ValueLayout.JAVA_BYTE, arr);
406 | }
407 |
408 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, byte[][] arr) {
409 | if (arr == null)
410 | return null;
411 |
412 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES);
413 | int n = 0;
414 | for (byte[] a : arr)
415 | segment.setAtIndex(ValueLayout.ADDRESS, n++, scope.allocateFrom(ValueLayout.JAVA_BYTE, a));
416 | return segment;
417 | }
418 |
419 | public static MemorySegment toMS(SegmentAllocator scope, byte[][] arr, boolean isReadBackOnly) {
420 | if (arr == null)
421 | return null;
422 |
423 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Byte.BYTES);
424 | int n = 0;
425 | for (byte[] row : arr) {
426 | segment.asSlice(n, row.length * Byte.BYTES).copyFrom(MemorySegment.ofArray(row));
427 | n += row.length * Byte.BYTES;
428 | }
429 |
430 | return segment;
431 | }
432 |
433 | public static void toArr(byte[] arr, MemorySegment segment) {
434 | if (arr == null)
435 | return;
436 | MemorySegment.copy(segment, ValueLayout.JAVA_BYTE, 0, arr, 0, arr.length);
437 | }
438 |
439 | public static byte[] toArr(ValueLayout.OfByte layout, MemorySegment seg, MemorySegment addr, int count) {
440 | if (MemorySegment.NULL.equals(addr))
441 | return null;
442 |
443 | return slice(seg, addr, count * layout.byteSize()).toArray(layout);
444 | }
445 |
446 | public static byte[] toArr(ValueLayout.OfByte layout, MemorySegment addr, int count) {
447 | if (MemorySegment.NULL.equals(addr))
448 | return null;
449 |
450 | if (addr.byteSize() == 0)
451 | {
452 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret(count);
453 | return seg.toArray(ValueLayout.JAVA_BYTE);
454 | }
455 |
456 | return addr.asSlice(0, count).toArray(layout);
457 | }
458 |
459 | /* Char ///////////////////////////////////////////////////////////////// */
460 |
461 | public static MemorySegment toMS(SegmentAllocator scope, char[] arr, boolean isReadBackOnly) {
462 | if (arr == null)
463 | return null;
464 |
465 | return isReadBackOnly ? scope.allocate(Character.BYTES * arr.length) : scope.allocateFrom(ValueLayout.JAVA_CHAR, arr);
466 | }
467 |
468 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, char[][] arr) {
469 | if (arr == null)
470 | return null;
471 |
472 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES);
473 | int n = 0;
474 | for (char[] a : arr)
475 | segment.setAtIndex(ValueLayout.ADDRESS, n++, scope.allocateFrom(ValueLayout.JAVA_CHAR, a));
476 | return segment;
477 | }
478 |
479 | public static MemorySegment toMS(SegmentAllocator scope, char[][] arr, boolean isReadBackOnly) {
480 | if (arr == null)
481 | return null;
482 |
483 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Byte.BYTES);
484 | int n = 0;
485 | for (char[] row : arr) {
486 | segment.asSlice(n, (int)(row.length * Character.BYTES)).copyFrom(MemorySegment.ofArray(row));
487 | n += row.length * Byte.BYTES;
488 | }
489 |
490 | return segment;
491 | }
492 |
493 | public static void toArr(char[] arr, MemorySegment segment) {
494 | if (arr == null)
495 | return;
496 | MemorySegment.copy(segment, ValueLayout.JAVA_CHAR, 0, arr, 0, arr.length);
497 | }
498 |
499 | public static char[] toArr(ValueLayout.OfChar layout, MemorySegment seg, MemorySegment addr, int count) {
500 | if (MemorySegment.NULL.equals(addr))
501 | return null;
502 |
503 | return slice(seg, addr, count * layout.byteSize()).toArray(layout);
504 | }
505 |
506 | public static char[] toArr(ValueLayout.OfChar layout, MemorySegment addr, int count) {
507 | if (MemorySegment.NULL.equals(addr))
508 | return null;
509 |
510 | if (addr.byteSize() == 0)
511 | {
512 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret(count);
513 | return seg.toArray(ValueLayout.JAVA_CHAR);
514 | }
515 |
516 | return addr.asSlice(0, count).toArray(layout);
517 | }
518 |
519 | /*///////////////////////////////////////////////////////////////// */
520 |
521 | public static MemorySegment slice(MemorySegment scope, MemorySegment addr, long bytes) {
522 | if (addr.byteSize() == 0)
523 | return MemorySegment.ofAddress(addr.address()).reinterpret(bytes).asSlice(0, bytes);
524 |
525 | return MemorySegment.ofAddress(addr.address()).asSlice(0, bytes);
526 | }
527 |
528 | public static String readString(MemorySegment addr) {
529 | if (MemorySegment.NULL.equals(addr))
530 | return null;
531 |
532 | if (addr.byteSize() == 0)
533 | {
534 | // This is slightly horrible. I can't find a better way. Use C's strlen to figure out
535 | //how big the memory segment really is.
536 | return addr.reinterpret(strLen(addr)+1).getString(0);
537 | }
538 |
539 | return addr.getString(0);
540 | }
541 |
542 | public static MemorySegment resize(MemorySegment addr, long bytes)
543 | {
544 | if (addr.byteSize() == 0)
545 | addr = addr.reinterpret(bytes);
546 | return addr;
547 | }
548 |
549 | static MethodHandle strlen = null;
550 |
551 | private static long strLen(MemorySegment seg)
552 | {
553 | if (strlen == null)
554 | {
555 | Linker linker = Linker.nativeLinker();
556 | SymbolLookup stdlib = linker.defaultLookup();
557 | strlen = linker.downcallHandle(
558 | stdlib.find("strlen").get(),
559 | FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
560 | );
561 |
562 | }
563 | try {
564 | return (long)strlen.invokeExact(seg);
565 | } catch (Throwable e) {
566 | return 0;
567 | }
568 | }
569 |
570 | public static MemorySegment toCString(String[] s, Arena scope) {
571 | var segment = scope.allocate(ValueLayout.JAVA_LONG.byteSize() * s.length);
572 | for (int i = 0; i < s.length; ++i)
573 | {
574 | segment.setAtIndex(ValueLayout.ADDRESS, i, toCString(s[i], scope));
575 | }
576 | return segment;
577 | }
578 |
579 | public static void fromCString(MemorySegment mem, String[] s)
580 | {
581 | for (int n = 0; n < s.length; ++n)
582 | s[n] = readString(mem.getAtIndex(ValueLayout.ADDRESS, n));
583 | }
584 |
585 | public static MemorySegment toCString(String s, Arena scope) {
586 | return scope.allocateFrom(s);
587 | }
588 |
589 | public static MemorySegment toCString(String s, SegmentAllocator scope) {
590 | return scope.allocateFrom(s);
591 | }
592 |
593 | /**
594 | * Given a folder or file this will recursively delete it.
595 | *
596 | * @param folder The root folder to recursively delete everything under.
597 | * @return True if everything was deleted.
598 | */
599 | public static boolean deleteFolder(Path folder) {
600 | if (!Files.exists(folder))
601 | return true;
602 |
603 | try {
604 | Files.walkFileTree(folder, new SimpleFileVisitor<>() {
605 | @Override
606 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
607 | throws IOException {
608 | Files.delete(file);
609 | return FileVisitResult.CONTINUE;
610 | }
611 |
612 | @Override
613 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
614 | if (exc != null)
615 | throw exc;
616 |
617 | Files.delete(dir);
618 | return FileVisitResult.CONTINUE;
619 | }
620 | });
621 | } catch (IOException e) {
622 | e.printStackTrace();
623 | }
624 | return true;
625 | }
626 |
627 | public static Path getBuildFolder() {
628 | if (System.getProperty("jpassport.build.home") != null)
629 | return Path.of(System.getProperty("jpassport.build.home"));
630 | return Path.of(System.getProperty("java.io.tmpdir"), "jpassport");
631 | }
632 |
633 | public enum Platform {Windows, Mac, Linux, Unknown}
634 |
635 | public static Platform getPlatform() {
636 | String os = System.getProperty("os.name").toLowerCase();
637 |
638 | if (os.contains("win"))
639 | return Platform.Windows;
640 | if (os.contains("mac"))
641 | return Platform.Mac;
642 | if ((os.contains("nix") || os.contains("nux") || os.contains("aix")))
643 | return Platform.Linux;
644 |
645 | return Platform.Unknown;
646 | }
647 |
648 | private static long typeToSize(Class> type)
649 | {
650 |
651 | if (type.equals(byte.class)) return ValueLayout.JAVA_CHAR.byteSize();
652 | if (type.equals(short.class)) return ValueLayout.JAVA_SHORT.byteSize();
653 | if (type.equals(int.class)) return ValueLayout.JAVA_INT.byteSize();
654 | if (type.equals(long.class)) return ValueLayout.JAVA_LONG.byteSize();
655 | if (type.equals(float.class)) return ValueLayout.JAVA_FLOAT.byteSize();
656 | if (type.equals(double.class)) return ValueLayout.JAVA_DOUBLE.byteSize();
657 | if (type.isRecord()) return size_of(type);
658 | throw new IllegalArgumentException("Cannot get size for non-primative");
659 | };
660 | public static long size_of(Class> c)
661 | {
662 | if (!c.isRecord())
663 | throw new IllegalArgumentException("Can only get size of records, not " + c.getName());
664 |
665 | int size = 0;
666 | for (Field f : c.getDeclaredFields())
667 | {
668 | size += PassportWriter.getPaddingBytes(f);
669 |
670 | Class> type = f.getType();
671 | if (type.isPrimitive())
672 | size += typeToSize(type);
673 | else if (type.isRecord())
674 | {
675 | boolean isPtr = f.getAnnotationsByType(Ptr.class).length > 0;
676 | if (isPtr)
677 | size += ValueLayout.ADDRESS.byteSize();
678 | else
679 | size += size_of(type);
680 | }
681 | else if (String.class.equals(type))
682 | size += ValueLayout.ADDRESS.byteSize();
683 | else if (type.isArray())
684 | {
685 | Annotation[] arrays = f.getAnnotationsByType(Array.class);
686 | boolean isPointer = f.getAnnotationsByType(Ptr.class).length > 0;
687 |
688 | if (arrays.length > 0)
689 | {
690 | int length = ((Array) arrays[0]).length();
691 | size += length * typeToSize(type.getComponentType());
692 | }
693 | else if (isPointer)
694 | size += ValueLayout.ADDRESS.byteSize();
695 | }
696 | }
697 | return size;
698 | }
699 |
700 | /**
701 | * Called by generated code to build the memory layout for a struct. This automatically
702 | * tries to figure out where padding is needed in the struct in order to get proper byte
703 | * alignment.
704 | * @param layout The members of the struct
705 | * @return The full GroupLayout of the struct.
706 | */
707 | public static GroupLayout makeStruct(MemoryLayout ... layout)
708 | {
709 | int byteBarrier = System.getProperty("sun.arch.data.model").contains("64") ? 8 : 4;
710 | ArrayList memLayout = new ArrayList<>();
711 | memLayout.add(layout[0]);
712 | long nextBarrier = byteBarrier;
713 |
714 | var curSize = memLayout.stream().mapToLong(MemoryLayout::byteSize).sum();
715 | while (nextBarrier <= curSize) nextBarrier += byteBarrier;
716 |
717 | for (int n = 1; n < layout.length; ++n)
718 | {
719 | curSize = memLayout.stream().mapToLong(MemoryLayout::byteSize).sum();
720 | long nextItemSize = layout[n].byteSize();
721 |
722 | //if an array is next, then we only need to byte align the first element of the array
723 | if (layout[n] instanceof SequenceLayout seq)
724 | nextItemSize = seq.byteSize() / seq.elementCount();
725 |
726 | // If the next piece of memory we are adding crosses the byte alignement barrier
727 | // then we need to pad the struct to alow byte alignment
728 | if (curSize + nextItemSize > nextBarrier)
729 | memLayout.add(MemoryLayout.paddingLayout(nextBarrier - curSize));
730 | memLayout.add(layout[n]);
731 |
732 | curSize = memLayout.stream().mapToLong(MemoryLayout::byteSize).sum();
733 | while (nextBarrier <= curSize) nextBarrier += byteBarrier;
734 | }
735 |
736 |
737 | return MemoryLayout.structLayout(memLayout.toArray(new MemoryLayout[0]));
738 | }
739 |
740 | public void templatedMethod()
741 | {
742 | try (var scope = Arena.ofConfined();) {
743 |
744 | }
745 | catch(Throwable th)
746 | {
747 | throw new Error(th);
748 | }
749 | }
750 |
751 | }
752 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/Version.java:
--------------------------------------------------------------------------------
1 | package jpassport;
2 |
3 | import java.util.Arrays;
4 | import java.util.ResourceBundle;
5 |
6 | public class Version
7 | {
8 | private static ResourceBundle m_res;
9 |
10 | static
11 | {
12 | try
13 | {
14 | m_res = ResourceBundle.getBundle("jpassport.version");
15 | }
16 | catch (Exception ex)
17 | {
18 | ex.printStackTrace();
19 | }
20 | }
21 |
22 | public static String getVersion()
23 | {
24 | return m_res.getString("version");
25 | }
26 |
27 | public static int[] getVersionParts()
28 | {
29 | String v = getVersion();
30 |
31 | return Arrays.stream(v.split("\\.")).mapToInt(Integer::parseInt).toArray();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/annotations/Array.java:
--------------------------------------------------------------------------------
1 | package jpassport.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * This annotation can be used with record members to declare the size of an array. For example
10 | *
11 | * public record PassingArrays(
12 | * @Array(length = 5)
double[] s_double)
13 | *
14 | *
15 | * In the above example, if PassingArrays is annotated with RefArg to indicate that it should
16 | * be read back after the native call, then the Array annotation indicates that s_double[]
17 | * should always be read as 5 doubles.
18 | */
19 | @Retention(RetentionPolicy.RUNTIME)
20 | @Target({ElementType.RECORD_COMPONENT, ElementType.FIELD})
21 | public @interface Array {
22 | int length() default 1;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/annotations/Critical.java:
--------------------------------------------------------------------------------
1 | package jpassport.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * This annotation enables the MethodHandle hint "critical". This reduces some of the
10 | * overhead that is required when calling a MethodHandle. From the definition of critical:
11 | *
12 | * A critical function is a function that has an extremely short running time in all cases (similar to calling an empty
13 | * function), and does not call back into Java (e.g. using an upcall stub). Using this linker option is a hint which
14 | * some implementations may use to apply optimizations that are only valid for critical functions. Using this linker
15 | * option when linking non-critical functions is likely to have adverse effects, such as loss of performance, or JVM crashes.
16 | */
17 | @Retention(RetentionPolicy.RUNTIME)
18 | @Target(ElementType.METHOD)
19 | public @interface Critical {
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/annotations/NotRequired.java:
--------------------------------------------------------------------------------
1 | package jpassport.annotations;
2 |
3 |
4 | import java.lang.annotation.ElementType;
5 | import java.lang.annotation.Retention;
6 | import java.lang.annotation.RetentionPolicy;
7 | import java.lang.annotation.Target;
8 |
9 | /**
10 | * This annotation is for methods that may or may not be available at run-time.
11 | * For instance, if you want to use multiple library versions that do not contain
12 | * the same foreign functions you can use this annotation. All Passport interfaces contain
13 | * the method hasMethod(String) where you can ask if the native method was found.
14 | * This can be used like old school C #IFDEF's to use or avoid certain calls.
15 | */
16 | @Retention(RetentionPolicy.RUNTIME)
17 | @Target({ElementType.METHOD, ElementType.FIELD})
18 | public @interface NotRequired {
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/annotations/Ptr.java:
--------------------------------------------------------------------------------
1 | package jpassport.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * This is used to annotate a member of a Record that is also a Record and should be
10 | * treated as a pointer in the struct. Ex
11 | *
12 | * struct MyStruct1
13 | * {
14 | * ......
15 | * }
16 | *
17 | * struct MyStruct2
18 | * {
19 | * struct MyStruct1* ptrToStruct;
20 | * struct MyStruct1 regStruct;
21 | * }
22 | *
23 | * public record MyStruct1( .... ){};
24 | * public record MyStruct2(@Ptr MyStruct1 ptrToStruct, MyStruct1 regStruct) {
25 | * }
26 | */
27 | @Retention(RetentionPolicy.RUNTIME)
28 | @Target({ElementType.RECORD_COMPONENT, ElementType.FIELD})
29 | public @interface Ptr {
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/annotations/PtrPtrArg.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.annotations;
13 |
14 | import java.lang.annotation.ElementType;
15 | import java.lang.annotation.Retention;
16 | import java.lang.annotation.RetentionPolicy;
17 | import java.lang.annotation.Target;
18 |
19 | /**
20 | * This annotation is for a 2-D array that should be passed as a pointer to a list of pointers. Without
21 | * this annotation a 2-D array will be copied as a single large memory block in row major order.
22 | * double sumMatD(int rows, int cols, double mat[rows][cols]) - C function
23 | * double sumMatD(int rows, int cols, double[][] mat); - Java interface
24 | * double sumMatD(const int rows, const int cols, const double** mat) - C function
25 | * double sumMatD(int rows, int cols, @PtrPtrArg double[][] mat); - Java interface
26 | * This annotation is only observed for array arguments.
27 | */
28 | @Retention(RetentionPolicy.RUNTIME)
29 | @Target(ElementType.PARAMETER)
30 | public @interface PtrPtrArg {
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/annotations/RefArg.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.annotations;
13 |
14 | import java.lang.annotation.ElementType;
15 | import java.lang.annotation.Retention;
16 | import java.lang.annotation.RetentionPolicy;
17 | import java.lang.annotation.Target;
18 |
19 | /**
20 | * This annotation is for an array that will be changed in the foreign library and
21 | * therefore should be read back in after the library call.
22 | *
23 | * This annotation is observed for array arguments and the class as a whole. When
24 | * used on a class it means that ALL arrays in interface methods are RefArgs.
25 | *
26 | * If the read_back_only parameter is false then the value in the java array is copied
27 | * into the native memory that is passed to the function call. If read_back_only is true
28 | * then blank memory is allocated and passed to the function call. For large
29 | * blocks of memory where you will only ever receive data back (eg. a read call)
30 | * read_back_only = true can save quite a bit of time.
31 | */
32 | @Retention(RetentionPolicy.RUNTIME)
33 | @Target({ElementType.PARAMETER, ElementType.TYPE})
34 | public @interface RefArg {
35 | boolean read_back_only() default false;
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/annotations/StructPadding.java:
--------------------------------------------------------------------------------
1 | package jpassport.annotations;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * This should be used to annotate the members of a Record class that are converted to a C Struct.
10 | * The idea is that the values in a struct are often padded. At least chars, and shorts are often
11 | * padded to 32 bits. The trouble is that the exact amount of padding is dependent on the compiler
12 | * and the platform. As such, it's nearly impossible to know ahead of time what the padding should
13 | * be on a given platform with a given compiler. As the developer, you need to tell JPassport how
14 | * much padding there should be. You can specify different padding for different platforms.
15 | */
16 | @Retention(RetentionPolicy.RUNTIME)
17 | @Target({ElementType.RECORD_COMPONENT, ElementType.FIELD})
18 | public @interface StructPadding {
19 |
20 | int NO_VALUE = Integer.MIN_VALUE;
21 |
22 | /** The number of bytes of padding to add. */
23 | int bytes() default 0;
24 | int windowsBytes() default NO_VALUE;
25 | int macBytes() default NO_VALUE;
26 | int linuxBytes() default NO_VALUE;
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/jpassport/version.properties:
--------------------------------------------------------------------------------
1 | #Sat, 12 Mar 2022 19:20:26 -0500
2 | version=0.6.1
3 |
--------------------------------------------------------------------------------
/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module jpassport {
2 | requires jdk.compiler;
3 |
4 |
5 | exports jpassport;
6 | exports jpassport.annotations;
7 | }
--------------------------------------------------------------------------------
/src/main/resources/jpassport/version.properties:
--------------------------------------------------------------------------------
1 | #Sat, 12 Mar 2022 19:20:26 -0500
2 | version=1.0.0
3 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/PureJava.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.test;
13 |
14 |
15 | import jpassport.MemoryBlock;
16 | import jpassport.Pointer;
17 | import jpassport.annotations.RefArg;
18 |
19 | import java.lang.foreign.Arena;
20 | import java.lang.foreign.MemorySegment;
21 |
22 | public class PureJava implements TestLink
23 | {
24 |
25 | @Override
26 | public void functionDoesNotExist(double v) {
27 |
28 | }
29 |
30 | @Override
31 | public double sumD(double d, double d2) {
32 | return d + d2;
33 | }
34 |
35 | @Override
36 | public double sumArrD(double[] d, int len)
37 | {
38 | double ret = 0;
39 | for (int n = 0; n < len; ++n)
40 | ret += d[n];
41 | return ret;
42 | }
43 |
44 | @Override
45 | public double sumArrDD(double[] d, double[] d2, int len) {
46 | double ret = 0;
47 | for (int n = 0; n < len; ++n)
48 | {
49 | ret += d[n];
50 | ret += d2[n];
51 | }
52 | return ret;
53 | }
54 |
55 | @Override
56 | public void readD(double[] d, int set) {
57 | d[0] = set;
58 | }
59 |
60 | @Override
61 | public float sumArrF(float[] d, int len)
62 | {
63 | float ret = 0;
64 | for (int n = 0; n < len; ++n)
65 | ret += d[n];
66 | return ret;
67 | }
68 |
69 | @Override
70 | public void readF(float[] d, float set) {
71 | d[0] = set;
72 | }
73 |
74 | @Override
75 | public long sumArrL(long[] d, long len)
76 | {
77 | long ret = 0;
78 | for (int n = 0; n < len; ++n)
79 | ret += d[n];
80 | return ret;
81 | }
82 |
83 | @Override
84 | public void readL(long[] d, long set) {
85 | d[0] = set;
86 | }
87 |
88 | @Override
89 | public int sumArrI(int[] d, int len)
90 | {
91 | int ret = 0;
92 | for (int n = 0; n < len; ++n)
93 | ret += d[n];
94 | return ret;
95 | }
96 |
97 | @Override
98 | public void readI(int[] d, int set) {
99 | d[0] = set;
100 | }
101 |
102 | @Override
103 | public short sumArrS(short[] d, short len)
104 | {
105 | short ret = 0;
106 | for (int n = 0; n < len; ++n)
107 | ret += d[n];
108 | return ret;
109 | }
110 |
111 | @Override
112 | public void readS(short[] d, short set) {
113 | d[0] = set;
114 | }
115 |
116 | @Override
117 | public byte sumArrB(byte[] d, byte len)
118 | {
119 | byte ret = 0;
120 | for (int n = 0; n < len; ++n)
121 | ret += d[n];
122 | return ret;
123 | }
124 |
125 | @Override
126 | public void readB(byte[] d, byte set) {
127 | d[0] = set;
128 | }
129 |
130 | @Override
131 | public double sumMatD(int rows, int cols, double[][] mat)
132 | {
133 | double total = 0;
134 | for (int y = 0; y < rows; ++y)
135 | {
136 | for (double i : mat[y])
137 | total += i;
138 | }
139 | return total;
140 | }
141 |
142 | @Override
143 | public double sumMatDPtrPtr(int rows, int cols, double[][] mat) {
144 | return sumMatD(rows, cols, mat);
145 | }
146 |
147 | @Override
148 | public float sumMatF(int rows, int cols, float[][] mat)
149 | {
150 | float total = 0;
151 | for (int y = 0; y < rows; ++y)
152 | {
153 | for (float i : mat[y])
154 | total += i;
155 | }
156 | return total;
157 | }
158 |
159 | @Override
160 | public float sumMatFPtrPtr(int rows, int cols, float[][] mat) {
161 | return sumMatF(rows, cols, mat);
162 | }
163 |
164 | @Override
165 | public long sumMatL(int rows, int cols, long[][] mat)
166 | {
167 | long total = 0;
168 | for (int y = 0; y < rows; ++y)
169 | {
170 | for (long i : mat[y])
171 | total += i;
172 | }
173 | return total;
174 | }
175 |
176 | @Override
177 | public long sumMatLPtrPtr(int rows, int cols, long[][] mat) {
178 | return sumMatL(rows, cols, mat);
179 | }
180 |
181 | @Override
182 | public int sumMatI(int rows, int cols, int[][] mat)
183 | {
184 | int total = 0;
185 | for (int y = 0; y < rows; ++y)
186 | {
187 | for (int i : mat[y])
188 | total += i;
189 | }
190 | return total;
191 | }
192 |
193 | @Override
194 | public int sumMatIPtrPtr(int rows, int cols, int[][] mat) {
195 | return sumMatI(rows, cols, mat);
196 | }
197 |
198 | @Override
199 | public int sumMatS(int rows, int cols, short[][] mat)
200 | {
201 | int total = 0;
202 | for (int y = 0; y < rows; ++y)
203 | {
204 | for (int i : mat[y])
205 | total += i;
206 | }
207 | return total;
208 | }
209 |
210 | @Override
211 | public int sumMatSPtrPtr(int rows, int cols, short[][] mat) {
212 | return sumMatS(rows, cols, mat);
213 | }
214 |
215 | @Override
216 | public int sumMatB(int rows, int cols, byte[][] mat)
217 | {
218 | int total = 0;
219 | for (int y = 0; y < rows; ++y)
220 | {
221 | for (int i : mat[y])
222 | total += i;
223 | }
224 | return total;
225 | }
226 |
227 | @Override
228 | public int sumMatBPtrPtr(int rows, int cols, byte[][] mat) {
229 | return sumMatB(rows, cols, mat);
230 | }
231 |
232 | @Override
233 | public int cstringLength(String s)
234 | {
235 | return s.length();
236 | }
237 |
238 | @Override
239 | public String mallocString(String origString) {
240 | return new String(origString);
241 | }
242 |
243 | @Override
244 | public MemorySegment mallocDoubles(int count) {
245 | return null;
246 | }
247 |
248 | @Override
249 | public void freeMemory(MemorySegment address) {
250 | }
251 |
252 | @Override
253 | public boolean hasMethod(String name) {
254 | return true;
255 | }
256 |
257 | public void readPointer(Pointer[] val, long set)
258 | {
259 | val[0] = new Pointer(MemorySegment.ofAddress(set));
260 | }
261 | public Pointer getPointer(Pointer[] val, long set)
262 | {
263 | readPointer(val, set);
264 | return val[0];
265 | }
266 |
267 | public int swapStrings(@RefArg String[] vals, int i, int j)
268 | {
269 | var s = vals[i];
270 | vals[i] = vals[j];
271 | vals[j] = s;
272 | return vals[i].length() + vals[j].length();
273 | }
274 |
275 | public int fillChars(Arena a, MemoryBlock fillThis, int sizemax)
276 | {
277 | String s= "hello world";
278 | fillThis.setString(s);
279 | return s.length();
280 | }
281 | public int passChars(char[] fillThis, int sizemax)
282 | {
283 | int s = 0;
284 | for (char c : fillThis)
285 | s += c;
286 | return s;
287 | }
288 | }
289 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/TestJPassport.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.test;
13 |
14 | import java.lang.foreign.Arena;
15 | import java.util.stream.IntStream;
16 |
17 | import jpassport.MemoryBlock;
18 | import jpassport.PassportBuilder;
19 | import jpassport.Pointer;
20 | import org.junit.jupiter.api.BeforeAll;
21 | import jpassport.PassportFactory;
22 |
23 | import org.junit.jupiter.api.Test;
24 |
25 | import static jpassport.test.TestLinkHelp.getLibName;
26 | import static org.junit.jupiter.api.Assertions.*;
27 |
28 |
29 | public class TestJPassport
30 | {
31 | static TestLink testClass[];
32 |
33 | @BeforeAll
34 | public static void startup() throws Throwable
35 | {
36 | System.setProperty("jpassport.build.home", "out/testing");
37 | System.setProperty("jna.library.path", System.getProperty("java.library.path"));
38 | testClass = new TestLink[] {PassportFactory.link(getLibName(), TestLink.class),
39 | PassportFactory.proxy(getLibName(), TestLink.class)};
40 |
41 | new PassportBuilder(TestLink.class, "none.none", "testlinkImpl");
42 | }
43 |
44 | @Test
45 | public void testNoPresent()
46 | {
47 | for (TestLink testLink : testClass) {
48 | assertFalse(testLink.hasMethod("functionDoesNotExist"));
49 | assertThrows(Error.class, () -> testLink.functionDoesNotExist(1));
50 | }
51 | }
52 | @Test
53 | public void testAllocString()
54 | {
55 | for (TestLink testLink : testClass) {
56 | String orig = "hello";
57 | String ret = testLink.mallocString(orig);
58 | assertEquals(orig, ret);
59 | }
60 | }
61 |
62 | @Test
63 | public void testNulls()
64 | {
65 | for (TestLink testFL : testClass) {
66 | assertNull(testFL.mallocString(null));
67 | assertEquals(0, testFL.sumArrD(null, 10));
68 | assertTrue(TestLinkHelp.testMallocDouble(testFL));
69 | }
70 | }
71 |
72 | @Test
73 | public void testD()
74 | {
75 | for (TestLink testFL : testClass) {
76 | assertEquals(4 + 5, testFL.sumD(4, 5));
77 | assertEquals(1 + 2 + 3, testFL.sumArrD(new double[]{1, 2, 3}, 3));
78 | assertEquals(1 + 2 + 3 + 4 + 5 + 6, testFL.sumArrDD(new double[]{1, 2, 3}, new double[]{4, 5, 6}, 3));
79 |
80 | double[] v = new double[1];
81 | testFL.readD(v, 5);
82 | assertEquals(5, v[0]);
83 | }
84 | }
85 |
86 | @Test
87 | public void testF()
88 | {
89 | for (TestLink testFL : testClass) {
90 | assertEquals(1 + 2 + 3, testFL.sumArrF(new float[]{1, 2, 3}, 3));
91 |
92 | float[] v = new float[1];
93 | testFL.readF(v, 5);
94 | assertEquals(5, v[0]);
95 | }
96 | }
97 |
98 |
99 | @Test
100 | public void testL()
101 | {
102 | for (TestLink testFL : testClass) {
103 | assertEquals(1 + 2 + 3, testFL.sumArrL(new long[]{1, 2, 3}, 3));
104 |
105 | long[] v = new long[1];
106 | testFL.readL(v, 5);
107 | assertEquals(5, v[0]);
108 | }
109 | }
110 |
111 |
112 | @Test
113 | public void testI()
114 | {
115 | int[] testRange = IntStream.range(1, 5).toArray();
116 | int correct = IntStream.range(1, 5).sum();
117 |
118 | for (TestLink testFL : testClass) {
119 | assertEquals(correct, testFL.sumArrI(testRange, testRange.length));
120 |
121 | int[] v = new int[1];
122 | testFL.readI(v, 5);
123 | assertEquals(5, v[0]);
124 | }
125 | }
126 |
127 |
128 | @Test
129 | public void testS()
130 | {
131 | for (TestLink testFL : testClass) {
132 | assertEquals(1 + 2 + 3, testFL.sumArrS(new short[]{1, 2, 3}, (short) 3));
133 |
134 | short[] v = new short[1];
135 | testFL.readS(v, (short) 5);
136 | assertEquals(5, v[0]);
137 | }
138 | }
139 |
140 |
141 | @Test
142 | public void testB()
143 | {
144 | for (TestLink testFL : testClass) {
145 | assertEquals(1 + 2 + 3, testFL.sumArrB(new byte[]{1, 2, 3}, (byte) 3));
146 |
147 | byte[] v = new byte[1];
148 | testFL.readB(v, (byte) 5);
149 | assertEquals(5, v[0]);
150 | }
151 | }
152 |
153 | @Test
154 | public void testSumMatD()
155 | {
156 | for (TestLink testFL : testClass) {
157 | double[][] mat = new double[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
158 | int correct = IntStream.range(1, 13).sum();
159 | assertEquals(correct, testFL.sumMatD(mat.length, mat[0].length, mat));
160 | assertEquals(correct, testFL.sumMatDPtrPtr(mat.length, mat[0].length, mat));
161 | }
162 | }
163 |
164 | @Test
165 | public void testSumMatF()
166 | {
167 | for (TestLink testFL : testClass) {
168 | float[][] mat = new float[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
169 | int correct = IntStream.range(1, 13).sum();
170 | assertEquals(correct, testFL.sumMatF(mat.length, mat[0].length, mat));
171 | assertEquals(correct, testFL.sumMatFPtrPtr(mat.length, mat[0].length, mat));
172 | }
173 | }
174 |
175 | @Test
176 | public void testSumMatL()
177 | {
178 | for (TestLink testFL : testClass) {
179 | long[][] mat = new long[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
180 | int correct = IntStream.range(1, 13).sum();
181 | assertEquals(correct, testFL.sumMatL(mat.length, mat[0].length, mat));
182 | assertEquals(correct, testFL.sumMatLPtrPtr(mat.length, mat[0].length, mat));
183 | }
184 | }
185 |
186 | @Test
187 | public void testSumMatI()
188 | {
189 | for (TestLink testFL : testClass) {
190 | int[][] mat = new int[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
191 | int correct = IntStream.range(1, 13).sum();
192 | assertEquals(correct, testFL.sumMatI(mat.length, mat[0].length, mat));
193 | assertEquals(correct, testFL.sumMatIPtrPtr(mat.length, mat[0].length, mat));
194 | }
195 | }
196 |
197 | @Test
198 | public void testSumMatS()
199 | {
200 | for (TestLink testFL : testClass) {
201 | short[][] mat = new short[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
202 | int correct = IntStream.range(1, 13).sum();
203 | assertEquals(correct, testFL.sumMatS(mat.length, mat[0].length, mat));
204 | assertEquals(correct, testFL.sumMatSPtrPtr(mat.length, mat[0].length, mat));
205 | }
206 | }
207 |
208 | @Test
209 | public void testSumMatB()
210 | {
211 | for (TestLink testFL : testClass) {
212 | byte[][] mat = new byte[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
213 | int correct = IntStream.range(1, 13).sum();
214 | assertEquals(correct, testFL.sumMatB(mat.length, mat[0].length, mat));
215 | assertEquals(correct, testFL.sumMatBPtrPtr(mat.length, mat[0].length, mat));
216 | }
217 | }
218 |
219 | @Test
220 | public void testStrLen()
221 | {
222 | for (TestLink testFL : testClass) {
223 | assertEquals(5, testFL.cstringLength("12345"));
224 | }
225 | }
226 |
227 | // @Test
228 | // public void testReturnPointer()
229 | // {
230 | // TestLink.calling(testFL);
231 | // }
232 |
233 | @Test
234 | public void testPointerPassing()
235 | {
236 |
237 | for (TestLink testFL : testClass) {
238 | var pt = new Pointer[1];
239 | pt[0] = new Pointer();
240 |
241 | testFL.readPointer(pt, 5);
242 | assertEquals(5, pt[0].getPtr().address());
243 |
244 | try (var scope = Arena.ofConfined();) {
245 | var mem = scope.allocate(8);
246 | pt[0] = new Pointer();
247 | var ret = testFL.getPointer (pt, mem.address());
248 |
249 | assertEquals(mem.address(), pt[0].getPtr().address());
250 | assertEquals(mem.address(), ret.getPtr().address());
251 |
252 | }
253 | }
254 | }
255 |
256 | @Test
257 | public void testStringArr()
258 | {
259 | for (TestLink testFL : testClass) {
260 | String[] var= new String[] {"hello", "Goodbye"};
261 |
262 | var len = testFL.swapStrings(var, 0, 1);
263 | assertEquals(var[0].length() + var[1].length(), len);
264 | assertEquals("hello".length(), var[1].length());
265 | assertEquals("Goodbye".length(), var[0].length());
266 | }
267 | }
268 |
269 | @Test
270 | public void testCharArgs()
271 | {
272 | String expected = "hello world";
273 |
274 | for (TestLink testFL : testClass) {
275 | MemoryBlock fill = new MemoryBlock(100);
276 | try (Arena a = Arena.ofConfined()) {
277 | int ll = testFL.fillChars(a, fill, (int) fill.size());
278 | assertEquals(expected.length(), ll);
279 | assertEquals(expected, fill.toString());
280 |
281 | int s = 0;
282 | for (char c : expected.toCharArray())
283 | s += c;
284 |
285 | assertEquals(s, testFL.passChars(fill.toString().toCharArray(), (int) expected.length() * 2));
286 | }
287 | }
288 |
289 | }
290 |
291 | }
292 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/TestLink.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.test;
13 |
14 | import com.sun.jna.Library;
15 | import jpassport.MemoryBlock;
16 | import jpassport.Passport;
17 | import jpassport.Pointer;
18 | import jpassport.annotations.NotRequired;
19 | import jpassport.annotations.PtrPtrArg;
20 | import jpassport.annotations.RefArg;
21 |
22 | import java.lang.foreign.Arena;
23 | import java.lang.foreign.MemorySegment;
24 |
25 |
26 | public interface TestLink extends Passport, Library {
27 |
28 | default double SUMD(double d, double d2)
29 | {
30 | return this.sumD(d, d2);
31 | }
32 |
33 | @NotRequired
34 | void functionDoesNotExist(double v);
35 |
36 | double sumD(double d, double d2);
37 | double sumArrD(double[] d, int len);
38 | double sumArrDD(double[] d, double[] d2, int len);
39 | void readD(@RefArg double[] d, int set);
40 |
41 | float sumArrF(float[] i, int len);
42 | void readF(@RefArg float[] d, float set);
43 |
44 | long sumArrL(long[] i, long len);
45 | void readL(@RefArg long[] d, long set);
46 |
47 | int sumArrI(int[] i, int len);
48 | void readI(@RefArg int[] d, int set);
49 |
50 | short sumArrS(short[] i, short len);
51 | void readS(@RefArg short[] d, short set);
52 |
53 | byte sumArrB(byte[] i, byte len);
54 | void readB(@RefArg byte[] d, byte set);
55 |
56 | double sumMatD(int rows, int cols, double[][] mat);
57 | double sumMatDPtrPtr(int rows, int cols, @PtrPtrArg double[][] mat);
58 | float sumMatF(int rows, int cols, float[][] mat);
59 | float sumMatFPtrPtr(int rows, int cols, @PtrPtrArg float[][] mat);
60 |
61 | long sumMatL(int rows, int cols, long[][] mat);
62 | long sumMatLPtrPtr(int rows, int cols, @PtrPtrArg long[][] mat);
63 | int sumMatI(int rows, int cols, int[][] mat);
64 | int sumMatIPtrPtr(int rows, int cols, @PtrPtrArg int[][] mat);
65 | int sumMatS(int rows, int cols, short[][] mat);
66 | int sumMatSPtrPtr(int rows, int cols, @PtrPtrArg short[][] mat);
67 | int sumMatB(int rows, int cols, byte[][] mat);
68 | int sumMatBPtrPtr(int rows, int cols, @PtrPtrArg byte[][] mat);
69 |
70 | int cstringLength(String s);
71 |
72 | String mallocString(String origString);
73 | MemorySegment mallocDoubles(int count);
74 | void freeMemory(MemorySegment address);
75 |
76 | void readPointer(@RefArg Pointer[] val, long set);
77 | Pointer getPointer(@RefArg Pointer[] val, long set);
78 |
79 | int swapStrings(@RefArg String[] vals, int i, int j);
80 |
81 | int fillChars(Arena a, MemoryBlock fillThis, int sizemax);
82 | int passChars(char[] fillThis, int sizemax);
83 |
84 | // static void calling(TestLink tl)
85 | // {
86 | // double[] values = new double[5];
87 | // MemoryAddress address = tl.mallocDoubles(values.length);
88 | // MemorySegment segment = address.asSegmentRestricted(values.length * Double.BYTES);
89 | // Utils.toArr(values, segment);
90 | //
91 | // assertArrayEquals(new double[] {0, 1, 2, 3, 4}, values);
92 | //
93 | // tl.freeMemory(address);
94 | // }
95 | }
96 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/TestLinkHelp.java:
--------------------------------------------------------------------------------
1 | package jpassport.test;
2 |
3 |
4 | import java.lang.foreign.MemorySegment;
5 | import java.util.Locale;
6 |
7 | public class TestLinkHelp {
8 |
9 | public static boolean testMallocDouble(TestLink link)
10 | {
11 | return MemorySegment.NULL.equals(link.mallocDoubles(0));
12 | }
13 |
14 |
15 | public static String getLibName()
16 | {
17 | if (System.getProperty("os.name").equalsIgnoreCase("linux"))
18 | return "libpassport_test.so";
19 | if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows"))
20 | return "libpassport_test";
21 |
22 | throw new IllegalArgumentException("Unknown OS: " + System.getProperty("os.name"));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/TestLinkJNADirect.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.test;
13 |
14 | import com.sun.jna.Native;
15 | import jpassport.MemoryBlock;
16 | import jpassport.Pointer;
17 | import jpassport.annotations.RefArg;
18 | import jpassport.test.performance.PerfTest;
19 |
20 | import java.lang.foreign.Arena;
21 | import java.lang.foreign.MemorySegment;
22 |
23 | public class TestLinkJNADirect
24 | {
25 | public static native double sumD(double d, double d2);
26 | public static native double sumArrD(double[] d, int len);
27 | public static native double sumArrDD(double[] d, double[] dd, int len);
28 | public static native void readD(double[] d, int set);
29 |
30 | public static native float sumArrF(float[] d, int len);
31 | public static native void readF(float[] d, float set);
32 |
33 | public static native long sumArrL(long[] i, long len);
34 | public static native void readL(long[] d, long set);
35 |
36 | public static native int sumArrI(int[] i, int len);
37 | public static native void readI(int[] d, int set);
38 |
39 | public static native short sumArrS(short[] i, short len);
40 | public static native void readS(short[] d, short set);
41 |
42 | public static native byte sumArrB(byte[] i, byte len);
43 | public static native void readB(byte[] d, byte set);
44 | // public static native int[] mallocInts(int count);
45 |
46 | static
47 | {
48 | Native.register("passport_test");
49 | }
50 |
51 | public static class JNADirect implements TestLink, PerfTest {
52 | @Override
53 | public void functionDoesNotExist(double v) {
54 |
55 | }
56 |
57 |
58 |
59 | @Override
60 | public double sumD(double d, double d2) {
61 | return TestLinkJNADirect.sumD(d, d2);
62 | }
63 |
64 | @Override
65 | public double sumArrD(double[] d, int len) {
66 | return TestLinkJNADirect.sumArrD(d, len);
67 | }
68 |
69 | @Override
70 | public double sumArrDD(double[] d, double[] d2, int len) {
71 | return TestLinkJNADirect.sumArrDD(d, d2, len);
72 | }
73 |
74 | @Override
75 | public void readD(double[] d, int set) {
76 | TestLinkJNADirect.readD(d, set);
77 | }
78 |
79 | @Override
80 | public float sumArrF(float[] i, int len) {
81 | return TestLinkJNADirect.sumArrF(i, len);
82 | }
83 |
84 | @Override
85 | public void readF(float[] d, float set) {
86 | TestLinkJNADirect.readF(d, set);
87 | }
88 |
89 | @Override
90 | public long sumArrL(long[] i, long len) {
91 | return TestLinkJNADirect.sumArrL(i, len);
92 | }
93 |
94 | @Override
95 | public void readL(long[] d, long set) {
96 | TestLinkJNADirect.readL(d, set);
97 | }
98 |
99 | @Override
100 | public int sumArrI(int[] i, int len) {
101 | return TestLinkJNADirect.sumArrI(i, len);
102 | }
103 |
104 | @Override
105 | public void readI(int[] d, int set) {
106 | TestLinkJNADirect.readI(d, set);
107 | }
108 |
109 | @Override
110 | public short sumArrS(short[] i, short len) {
111 | return TestLinkJNADirect.sumArrS(i, len);
112 | }
113 |
114 | @Override
115 | public void readS(short[] d, short set) {
116 | TestLinkJNADirect.readS(d, set);
117 | }
118 |
119 | @Override
120 | public byte sumArrB(byte[] i, byte len) {
121 | return TestLinkJNADirect.sumArrB(i, len);
122 | }
123 |
124 | @Override
125 | public void readB(byte[] d, byte set) {
126 | TestLinkJNADirect.readB(d, set);
127 | }
128 |
129 | @Override
130 | public double sumMatD(int rows, int cols, double[][] mat) {
131 | return 0;
132 | }
133 |
134 | @Override
135 | public double sumMatDPtrPtr(int rows, int cols, double[][] mat) {
136 | return 0;
137 | }
138 |
139 | @Override
140 | public float sumMatF(int rows, int cols, float[][] mat) {
141 | return 0;
142 | }
143 |
144 | @Override
145 | public float sumMatFPtrPtr(int rows, int cols, float[][] mat) {
146 | return 0;
147 | }
148 |
149 | @Override
150 | public long sumMatL(int rows, int cols, long[][] mat) {
151 | return 0;
152 | }
153 |
154 | @Override
155 | public long sumMatLPtrPtr(int rows, int cols, long[][] mat) {
156 | return 0;
157 | }
158 |
159 | @Override
160 | public int sumMatI(int rows, int cols, int[][] mat) {
161 | return 0;
162 | }
163 |
164 | @Override
165 | public int sumMatIPtrPtr(int rows, int cols, int[][] mat) {
166 | return 0;
167 | }
168 |
169 | @Override
170 | public int sumMatS(int rows, int cols, short[][] mat) {
171 | return 0;
172 | }
173 |
174 | @Override
175 | public int sumMatSPtrPtr(int rows, int cols, short[][] mat) {
176 | return 0;
177 | }
178 |
179 | @Override
180 | public int sumMatB(int rows, int cols, byte[][] mat) {
181 | return 0;
182 | }
183 |
184 | @Override
185 | public int sumMatBPtrPtr(int rows, int cols, byte[][] mat) {
186 | return 0;
187 | }
188 |
189 | @Override
190 | public int cstringLength(String s) {
191 | return 0;
192 | }
193 |
194 | @Override
195 | public String mallocString(String orig) {
196 | return new String(orig);
197 | }
198 |
199 | @Override
200 | public MemorySegment mallocDoubles(int count) {
201 | return null;
202 | }
203 |
204 | @Override
205 | public void freeMemory(MemorySegment address) {
206 |
207 | }
208 |
209 | @Override
210 | public boolean hasMethod(String name) {
211 | return true;
212 | }
213 |
214 | @Override
215 | public Object readStruct(MemorySegment segment, Object rec) {
216 | return null;
217 | }
218 |
219 | public void readPointer(Pointer[] val, long set)
220 | {}
221 |
222 | public Pointer getPointer(Pointer[] val, long set)
223 | {
224 | return null;
225 | }
226 |
227 | public int swapStrings(@RefArg String[] vals, int i, int j)
228 | {
229 | return 0;
230 | }
231 |
232 | public int fillChars(Arena a, MemoryBlock fillThis, int sizemax)
233 | {
234 | String s= "hello world";
235 | fillThis.setString(s);
236 | return s.length();
237 | }
238 | public int passChars(char[] fillThis, int sizemax)
239 | {
240 | int s = 0;
241 | for (char c : fillThis)
242 | s += c;
243 | return s;
244 | }
245 |
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/callback/CallbackNative.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.callback;
2 |
3 | import jpassport.FunctionPtr;
4 | import jpassport.Passport;
5 |
6 | import java.lang.foreign.MemorySegment;
7 |
8 | public interface CallbackNative extends Passport {
9 | int call_CB(FunctionPtr fn, int v, double v2);
10 | void call_CBArr(FunctionPtr fn, int[] vals, int count);
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/callback/CallbackObj.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.callback;
2 |
3 | import jpassport.FunctionPtr;
4 | import jpassport.PassportFactory;
5 | import jpassport.Utils;
6 |
7 | import java.lang.foreign.MemorySegment;
8 | import java.lang.foreign.ValueLayout;
9 | import java.util.Arrays;
10 |
11 | public class CallbackObj {
12 | public int calls = 0;
13 |
14 | public int callback(int n, double m) {
15 | calls++;
16 | return (int) (n + m);
17 | }
18 |
19 | public FunctionPtr getAsFunctionPtr()
20 | {
21 | return PassportFactory.createCallback(this, "callback");
22 | }
23 |
24 | public int sum = 0;
25 |
26 | // public void callbackArr(MemorySegment ptr, int count) {
27 | // var vals = Utils.toArr(ValueLayout.JAVA_INT, ptr, ptr.address(), count);
28 | // sum = Arrays.stream(vals).sum();
29 | // }
30 |
31 | public void callbackArr(MemorySegment ptr, int count) {
32 | var vals = Utils.toArr(ValueLayout.JAVA_INT, ptr, count);
33 | sum = Arrays.stream(vals).sum();
34 | }
35 |
36 | public FunctionPtr getAsFunctionArrPtr()
37 | {
38 | return PassportFactory.createCallback(this, "callbackArr");
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/callback/TestCallback.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.callback;
2 |
3 | import jpassport.PassportFactory;
4 | import org.junit.jupiter.api.Test;
5 |
6 | import java.util.Arrays;
7 |
8 | import static jpassport.test.TestLinkHelp.getLibName;
9 | import static org.junit.jupiter.api.Assertions.assertEquals;
10 |
11 | public class TestCallback {
12 |
13 | @Test
14 | public void testCallback() throws Throwable {
15 | var callBack = PassportFactory.link(getLibName(), CallbackNative.class);
16 |
17 | var myCB = new CallbackObj();
18 |
19 | int ret = callBack.call_CB(myCB.getAsFunctionPtr(), 5, 1);
20 | assertEquals(5, myCB.calls);
21 | assertEquals(30, ret);
22 |
23 | int[] test = {1, 2, 3 ,4 ,5};
24 | callBack.call_CBArr(myCB.getAsFunctionArrPtr(), test, test.length);
25 | assertEquals(Arrays.stream(test).sum(), myCB.sum);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/performance/JPassportMicroBenchmark.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.performance;
2 |
3 | import com.sun.jna.Native;
4 | import jpassport.PassportFactory;
5 | import jpassport.test.PureJava;
6 | import jpassport.test.TestLink;
7 | import jpassport.test.TestLinkJNADirect;
8 | import org.openjdk.jmh.annotations.*;
9 | import org.openjdk.jmh.runner.Runner;
10 | import org.openjdk.jmh.runner.options.Options;
11 | import org.openjdk.jmh.runner.options.OptionsBuilder;
12 |
13 |
14 | import java.util.stream.IntStream;
15 |
16 | @State(Scope.Benchmark)
17 | public class JPassportMicroBenchmark
18 | {
19 | public static void main(String[] args) throws Exception {
20 | Options opt = new OptionsBuilder()
21 | .include(JPassportMicroBenchmark.class.getSimpleName())
22 | .forks(1)
23 | .build();
24 |
25 | new Runner(opt).run();
26 | }
27 |
28 | static TestLink testFL;
29 | static TestLink testJNA;
30 | static TestLink testJNADirect;
31 | static TestLink testJava;
32 |
33 | @Param({"1024", "2048", "16384", "262144"})
34 | public int array_size;
35 |
36 | public double[] test_arr;
37 |
38 | @Setup(Level.Trial)
39 | public void updateArray()
40 | {
41 | test_arr = IntStream.range(0, array_size).mapToDouble(i -> i).toArray();
42 |
43 | }
44 |
45 |
46 | @Setup()
47 | public void setUp() throws Throwable
48 | {
49 | System.setProperty("jna.library.path", System.getProperty("java.library.path"));
50 | testFL = PassportFactory.link("libforeign_link", TestLink.class);
51 | testJNA = Native.load("libforeign_link.dll", TestLink.class);
52 | testJNADirect = new TestLinkJNADirect.JNADirect();
53 | testJava = new PureJava();
54 | }
55 |
56 |
57 | @Benchmark
58 | @Fork(value = 2, warmups = 1)
59 | public void sumTestArrDJava()
60 | {
61 | testJava.sumArrD(test_arr, test_arr.length);
62 | }
63 |
64 | @Benchmark
65 | @Fork(value = 2, warmups = 1)
66 | public void sumTestArrDJNA()
67 | {
68 | testJNA.sumArrD(test_arr, test_arr.length);
69 | }
70 |
71 | @Benchmark
72 | @Fork(value = 2, warmups = 1)
73 | public void sumTestArrDJNADirect()
74 | {
75 | testJNADirect.sumArrD(test_arr, test_arr.length);
76 | }
77 |
78 | @Benchmark
79 | @Fork(value = 2, warmups = 1)
80 | public void sumTestArrDJPassport()
81 | {
82 | testFL.sumArrD(test_arr, test_arr.length);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/performance/PerfTest.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.test.performance;
13 |
14 | import com.sun.jna.Library;
15 | import jpassport.Passport;
16 | import jpassport.annotations.Critical;
17 |
18 | public interface PerfTest extends Passport, Library {
19 | @Critical
20 | double sumD(double d, double d2);
21 | @Critical
22 | double sumArrD(double[] d, int len);
23 | float sumArrF(float[] d, int len);
24 | int sumArrI(int[] d, int len);
25 | }
26 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/performance/PerformanceTest.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.test.performance;
13 |
14 | import com.sun.jna.Native;
15 | import jpassport.PassportFactory;
16 | import jpassport.test.TestLinkJNADirect;
17 | import jpassport.test.util.CSVOutput;
18 |
19 | import java.io.IOException;
20 | import java.nio.file.Path;
21 | import java.util.Arrays;
22 | import java.util.stream.IntStream;
23 |
24 |
25 | public class PerformanceTest
26 | {
27 | static PerfTest testFL;
28 | static PerfTest testFLP;
29 | static PerfTest testJNA;
30 | static PerfTest testJNADirect;
31 | static PerfTest testJava;
32 |
33 |
34 | public static void startup() throws Throwable
35 | {
36 | System.setProperty("jpassport.build.home", "out/testing");
37 | System.setProperty("jna.library.path", System.getProperty("java.library.path"));
38 | testFL = PassportFactory.link("libpassport_test", PerfTest.class);
39 | testFLP = PassportFactory.proxy("libpassport_test", PerfTest.class);
40 | testJNA = Native.load("passport_test", PerfTest.class);
41 | testJNADirect = new TestLinkJNADirect.JNADirect();
42 | testJava = new PureJavaPerf();
43 | }
44 |
45 | public static void main(String[] str) throws Throwable
46 | {
47 | startup();
48 |
49 | PerfTest[] tests = new PerfTest[] {testJava, testJNA, testJNADirect, testFL, testFLP};
50 |
51 | try(var csv = new CSVOutput(Path.of("performance", "doubles_add_2.csv")))
52 | {
53 | csv.add("iteration", "pure java", "JNA", "JNA Direct", "JPassport", "Proxy").endLine();
54 |
55 | for (int loops = 1000; loops < 100000; loops += 1000) {
56 |
57 | double[][] results = new double[5][5];
58 | for (int n = 0; n < 5; ++n) {
59 | for (int m = 0; m < tests.length; ++m)
60 | results[m][n] = sumTest(tests[m], loops);
61 | }
62 |
63 | csv.addF(loops);
64 | for (double[] arr : results)
65 | {
66 | Arrays.sort(arr);
67 | csv.addF(arr[arr.length/2]);
68 | }
69 | csv.endLine();
70 | System.out.println("loops: " + loops);
71 | }
72 | }
73 | catch (IOException ex)
74 | {
75 | ex.printStackTrace();
76 | }
77 |
78 | try(var csv = new CSVOutput(Path.of("performance", "double_arr_add.csv")))
79 | {
80 | csv.add("array size", "pure java", "JNA", "JNA Direct", "JPassport", "Proxy").endLine();
81 | for (int size = 1024; size <= 1024*256; size += 1024)
82 | {
83 | double[][] results = new double[5][5];
84 |
85 | for (int n = 0; n < 5; ++n) {
86 | for (int m = 0; m < tests.length; ++m)
87 | results[m][n] = sumTestArrD(tests[m], 100, size);;
88 | }
89 |
90 | csv.addF(size);
91 | for (double[] arr : results)
92 | {
93 | Arrays.sort(arr);
94 | csv.addF(arr[arr.length/2]);
95 | }
96 | csv.endLine();
97 | System.out.println("array size: " + size);
98 | }
99 | }
100 | catch (IOException ex)
101 | {
102 | ex.printStackTrace();
103 | }
104 |
105 | }
106 |
107 |
108 | static double sumTest(PerfTest testLib, int count) {
109 | long start = System.nanoTime();
110 | double m = 0;
111 | for (double n = 0; n < count; ++n) {
112 | m = testLib.sumD(n, m);
113 | }
114 | return (System.nanoTime() - start) / 1e9;
115 | }
116 |
117 | static double sumTestArrD(PerfTest testLib, int count, int arrSize) {
118 | double[] d = IntStream.range(0, arrSize).mapToDouble(i -> i).toArray();
119 | long start = System.nanoTime();
120 | double m = 0;
121 | for (double n = 0; n < count; ++n) {
122 | m = testLib.sumArrD(d, arrSize);
123 | }
124 | return (System.nanoTime() - start) / 1e9;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/performance/PureJavaPerf.java:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved
2 | *
3 | * The contents of this file is dual-licensed under the
4 | * Apache License 2.0.
5 | *
6 | * You may obtain a copy of the Apache License at:
7 | *
8 | * http://www.apache.org/licenses/
9 | *
10 | * A copy is also included in the downloadable source code.
11 | */
12 | package jpassport.test.performance;
13 |
14 | import jpassport.test.structs.TestStruct;
15 |
16 | public class PureJavaPerf implements PerfTest{
17 | @Override
18 | public double sumD(double d, double d2) {
19 | return d + d2;
20 | }
21 |
22 | @Override
23 | public double sumArrD(double[] d, int len)
24 | {
25 | double ret = 0;
26 | for (int n = 0; n < len; ++n)
27 | ret += d[n];
28 | return ret;
29 | }
30 |
31 | @Override
32 | public int sumArrI(int[] d, int len)
33 | {
34 | int ret = 0;
35 | for (int n = 0; n < len; ++n)
36 | ret += d[n];
37 | return ret;
38 | }
39 |
40 | @Override
41 | public float sumArrF(float[] d, int len)
42 | {
43 | float ret = 0;
44 | for (int n = 0; n < len; ++n)
45 | ret += d[n];
46 | return ret;
47 | }
48 |
49 | public double passStruct(TestStruct simpleStruct) {
50 | return simpleStruct.s_int() + simpleStruct.s_long() + simpleStruct.s_float() + simpleStruct.s_double();
51 | }
52 |
53 | @Override
54 | public boolean hasMethod(String name) {
55 | return true;
56 | }
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/structs/ComplexStruct.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.structs;
2 |
3 | import jpassport.annotations.Ptr;
4 |
5 | /**
6 | * This record is meant to match the ComplexPassing struct in the C code.
7 | * This is considered complex in that it contains references to other records.
8 | */
9 | public record ComplexStruct(
10 | int ID,
11 | TestStruct ts,
12 | @Ptr TestStruct tsPtr,
13 | String string)
14 | {
15 | }
16 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/structs/PassingArrays.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.structs;
2 |
3 | import jpassport.annotations.Array;
4 | import jpassport.annotations.Ptr;
5 |
6 | public record PassingArrays(
7 | @Array(length = 5) double[] s_double,
8 | @Array(length = 8) long[] s_long,
9 | long s_doublePtrCount,
10 | @Ptr double[] s_doublePtr,
11 | long s_longPtrCount,
12 | @Ptr long[] s_longPtr)
13 | {
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/structs/StructWithPrt.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.structs;
2 |
3 | import jpassport.annotations.StructPadding;
4 |
5 | import java.lang.foreign.MemorySegment;
6 |
7 | public record StructWithPrt(@StructPadding(bytes = 4)int n, MemorySegment addr, float f) {
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/structs/TestStruct.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.structs;
2 |
3 | import jpassport.annotations.StructPadding;
4 |
5 | public record TestStruct(
6 | @StructPadding(bytes = 4) int s_int,
7 | long s_long,
8 | @StructPadding(bytes = 4) float s_float,
9 | double s_double) {
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/structs/TestStructCalls.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.structs;
2 |
3 | import jpassport.Passport;
4 | import jpassport.annotations.NotRequired;
5 | import jpassport.annotations.RefArg;
6 |
7 | public interface TestStructCalls extends Passport {
8 | double passStruct(TestStruct address);
9 | double passComplex(@RefArg ComplexStruct[] complexStruct);
10 | double passStructWithArrays(@RefArg PassingArrays[] arrays);
11 | @NotRequired
12 | void testAddrCall(StructWithPrt test);
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/structs/TestUsingStructs.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.structs;
2 |
3 | import jpassport.PassportFactory;
4 | import jpassport.Utils;
5 | import org.junit.jupiter.api.BeforeAll;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import java.util.stream.IntStream;
9 |
10 | import static java.lang.foreign.MemoryLayout.PathElement.groupElement;
11 | import static java.lang.foreign.ValueLayout.*;
12 | import static jpassport.test.TestLinkHelp.getLibName;
13 | import static org.junit.jupiter.api.Assertions.assertArrayEquals;
14 | import static org.junit.jupiter.api.Assertions.assertEquals;
15 |
16 | public class TestUsingStructs {
17 |
18 | static TestStructCalls PassingStructs;
19 |
20 | @BeforeAll
21 | public static void startup() throws Throwable
22 | {
23 | System.setProperty("jpassport.build.home", "out/testing");
24 | PassingStructs = PassportFactory.link(getLibName(), TestStructCalls.class);
25 | // PassingStructs = new TestStructCalls_impl(PassportFactory.loadMethodHandles("libpassport_test.dll", TestStructCalls.class));
26 | }
27 |
28 | @Test
29 | public void testSimpleStruct()
30 | {
31 | assertEquals(4 * JAVA_LONG.byteSize(), Utils.size_of(TestStruct.class));
32 | assertEquals(JAVA_INT.byteSize() + Utils.size_of(TestStruct.class) +
33 | ADDRESS.byteSize() * 2, Utils.size_of(ComplexStruct.class));
34 |
35 |
36 | assertEquals(2+3+4+5, PassingStructs.passStruct(new TestStruct(2, 3, 4, 5)));
37 | // long[] times = new long[1000];
38 | //
39 | // for (int n = 0; n < times.length; n++)
40 | // {
41 | // long t = System.currentTimeMillis();
42 | //
43 | // for (int m = 0; m < 10000; ++m)
44 | // {
45 | // PassingStructs.passStruct(new TestStruct(m, m+1, m+2, m+3));
46 | // }
47 | //
48 | // times[n] = System.currentTimeMillis() - t;
49 | // System.out.printf("%d = %d ms\n", n, times[n]);
50 | // }
51 | //
52 | // times = Arrays.copyOfRange(times, 5, times.length);
53 | // System.out.println("Mean = " + Arrays.stream(times).average().getAsDouble());
54 | }
55 |
56 | @Test
57 | public void testComplexStruct()
58 | {
59 | TestStruct ts = new TestStruct(1, 2, 3, 4);
60 | TestStruct tsPtr = new TestStruct(5, 6, 7, 8);
61 | ComplexStruct[] complex = new ComplexStruct[] {new ComplexStruct(55, ts, tsPtr, "hello")};
62 |
63 | double d = PassingStructs.passComplex(complex);
64 | assertEquals(IntStream.range(1, 9).sum(), d);
65 | assertEquals(65, complex[0].ID());
66 | assertEquals(11, complex[0].ts().s_int());
67 | assertEquals(25, complex[0].tsPtr().s_int());
68 | assertEquals("HELLO", complex[0].string());
69 | //
70 | // long[] times = new long[500];
71 | //
72 | // for (int n = 0; n < times.length; n++)
73 | // {
74 | // long t = System.currentTimeMillis();
75 | //
76 | // for (int m = 0; m < 10000; ++m)
77 | // {
78 | // complex = new ComplexStruct[] {new ComplexStruct(55, ts, tsPtr, "hello")};
79 | // PassingStructs.passComplex(complex);
80 | // }
81 | //
82 | // times[n] = System.currentTimeMillis() - t;
83 | // System.out.printf("%d = %d ms\n", n, times[n]);
84 | // }
85 | //
86 | // times = Arrays.copyOfRange(times, 5, times.length);
87 | // System.out.println("Mean = " + Arrays.stream(times).average().getAsDouble());
88 | }
89 |
90 | @Test
91 | public void testStructsWithArrays()
92 | {
93 | int expected = IntStream.range(1, 21).sum();
94 |
95 | var doublesArr = new double[] {1, 2, 3, 4, 5};
96 | var longArr = new long[] {6, 7, 8, 9, 10, 11, 12, 13};
97 | var doublePtr = new double[] {14, 15, 16};
98 | var longPtr = new long[] {17,18,19,20};
99 |
100 | PassingArrays pa = new PassingArrays(doublesArr, longArr, doublePtr.length, doublePtr, longPtr.length, longPtr);
101 | PassingArrays[] regArg = new PassingArrays[] {pa};
102 | assertEquals(expected, PassingStructs.passStructWithArrays(regArg));
103 |
104 | assertArrayEquals(new double[] {14,15,16,4,5}, regArg[0].s_double());
105 | assertArrayEquals(new long[] {6,7,8,9}, regArg[0].s_longPtr());
106 | assertArrayEquals(longArr, regArg[0].s_long());
107 | assertArrayEquals(doublePtr, regArg[0].s_doublePtr());
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/test/java/jpassport/test/util/CSVOutput.java:
--------------------------------------------------------------------------------
1 | package jpassport.test.util;
2 |
3 | import org.apache.commons.csv.CSVFormat;
4 | import org.apache.commons.csv.CSVPrinter;
5 |
6 | import java.io.IOException;
7 | import java.nio.charset.Charset;
8 | import java.nio.file.Files;
9 | import java.nio.file.Path;
10 | import java.util.ArrayList;
11 | import java.util.Arrays;
12 | import java.util.List;
13 |
14 | public class CSVOutput implements AutoCloseable
15 | {
16 | private final List m_curLine = new ArrayList<>();
17 | private final CSVPrinter m_printer;
18 |
19 | public CSVOutput(Path path) throws IOException
20 | {
21 | if (!Files.exists(path.getParent()))
22 | Files.createDirectories(path.getParent());
23 |
24 | m_printer = CSVFormat.DEFAULT.print(path, Charset.defaultCharset());
25 | }
26 |
27 | public void close() throws IOException
28 | {
29 | m_printer.close();
30 | }
31 |
32 | public CSVOutput add(String ... value)
33 | {
34 | m_curLine.addAll(Arrays.asList(value));
35 | return this;
36 | }
37 |
38 | public CSVOutput addF(double ...value)
39 | {
40 | Arrays.stream(value).forEach(dd -> m_curLine.add(Double.toString(dd)));
41 | return this;
42 | }
43 |
44 | public CSVOutput endLine() throws IOException
45 | {
46 | m_printer.printRecord(m_curLine);
47 | m_curLine.clear();
48 | return this;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/java/module-info.java:
--------------------------------------------------------------------------------
1 | module test.passport {
2 | requires jpassport;
3 | requires com.sun.jna;
4 | requires com.sun.jna.platform;
5 |
6 | requires org.junit.jupiter.api;
7 | requires org.junit.platform.engine;
8 | requires jmh.core;
9 | requires jmh.generator.annprocess;
10 |
11 | requires commons.csv;
12 |
13 | exports jpassport.test;
14 | exports jpassport.test.performance;
15 | exports jpassport.test.structs;
16 | exports jpassport.test.callback;
17 | }
--------------------------------------------------------------------------------