2021-09-26 19:11:29 +00:00
|
|
|
(ns coffi.mem
|
|
|
|
|
"Functions for managing native allocations, resource scopes, and (de)serialization.
|
|
|
|
|
|
|
|
|
|
For any new type to be implemented, three multimethods must be overriden, but
|
|
|
|
|
which three depends on the native representation of the type.
|
|
|
|
|
|
|
|
|
|
If the native representation of the type is a primitive (whether or not other
|
|
|
|
|
data beyond the primitive is associated with it, as e.g. a pointer),
|
|
|
|
|
then [[primitive-type]] must be overriden to return which primitive type it is
|
|
|
|
|
serialized as, then [[serialize*]] and [[deserialize*]] should be overriden.
|
|
|
|
|
|
|
|
|
|
If the native representation of the type is a composite type, like a union,
|
|
|
|
|
struct, or array, then [[c-layout]] must be overriden to return the native
|
|
|
|
|
layout of the type, and [[serialize-into]] and [[deserialize-from]] should be
|
|
|
|
|
overriden to allow marshaling values of the type into and out of memory
|
|
|
|
|
segments.
|
|
|
|
|
|
|
|
|
|
When writing code that manipulates a segment, it's best practice to
|
|
|
|
|
use [[with-acquired]] on the [[segment-scope]] in order to ensure it won't be
|
|
|
|
|
released during its manipulation."
|
|
|
|
|
(:require
|
|
|
|
|
[clojure.spec.alpha :as s])
|
|
|
|
|
(:import
|
|
|
|
|
(jdk.incubator.foreign
|
|
|
|
|
Addressable
|
|
|
|
|
CLinker
|
|
|
|
|
MemoryAccess
|
|
|
|
|
MemoryAddress
|
|
|
|
|
MemoryLayout
|
|
|
|
|
MemorySegment
|
|
|
|
|
ResourceScope
|
2021-10-01 00:54:48 +00:00
|
|
|
ResourceScope$Handle
|
2021-09-26 19:11:29 +00:00
|
|
|
SegmentAllocator)))
|
|
|
|
|
|
|
|
|
|
(defn stack-scope
|
|
|
|
|
"Constructs a new scope for use only in this thread.
|
|
|
|
|
|
|
|
|
|
The memory allocated within this scope is cheap to allocate, like a native
|
|
|
|
|
stack."
|
|
|
|
|
^ResourceScope []
|
|
|
|
|
(ResourceScope/newConfinedScope))
|
|
|
|
|
|
|
|
|
|
(defn shared-scope
|
|
|
|
|
"Constructs a new shared scope.
|
|
|
|
|
|
|
|
|
|
This scope can be shared across threads and memory allocated in it will only
|
|
|
|
|
be cleaned up once every thread accessing the scope closes it."
|
|
|
|
|
^ResourceScope []
|
|
|
|
|
(ResourceScope/newSharedScope))
|
|
|
|
|
|
|
|
|
|
(defn connected-scope
|
|
|
|
|
"Constructs a new scope to reclaim all connected resources at once.
|
|
|
|
|
|
|
|
|
|
The scope may be shared across threads, and all resources created with it will
|
|
|
|
|
be cleaned up at the same time, when all references have been collected.
|
|
|
|
|
|
|
|
|
|
This type of scope cannot be closed, and therefore should not be created in
|
|
|
|
|
a [[with-open]] clause."
|
|
|
|
|
^ResourceScope []
|
|
|
|
|
(ResourceScope/newImplicitScope))
|
|
|
|
|
|
|
|
|
|
(defn global-scope
|
|
|
|
|
"Constructs the global scope, which will never reclaim its resources.
|
|
|
|
|
|
|
|
|
|
This scope may be shared across threads, but is intended mainly in cases where
|
|
|
|
|
memory is allocated with [[alloc]] but is either never freed or whose
|
|
|
|
|
management is relinquished to a native library, such as when returned from a
|
|
|
|
|
callback."
|
|
|
|
|
^ResourceScope []
|
|
|
|
|
(ResourceScope/globalScope))
|
|
|
|
|
|
|
|
|
|
(defn scope-allocator
|
|
|
|
|
"Constructs a segment allocator from the given `scope`.
|
|
|
|
|
|
|
|
|
|
This is primarily used when working with unwrapped downcall functions. When a
|
|
|
|
|
downcall function returns a non-primitive type, it must be provided with an
|
|
|
|
|
allocator."
|
|
|
|
|
^SegmentAllocator [^ResourceScope scope]
|
|
|
|
|
(SegmentAllocator/ofScope scope))
|
|
|
|
|
|
|
|
|
|
(defn segment-scope
|
|
|
|
|
"Gets the scope used to construct the `segment`."
|
|
|
|
|
^ResourceScope [segment]
|
|
|
|
|
(.scope ^MemorySegment segment))
|
|
|
|
|
|
|
|
|
|
(defn alloc
|
|
|
|
|
"Allocates `size` bytes.
|
|
|
|
|
|
|
|
|
|
If a `scope` is provided, the allocation will be reclaimed when it is closed."
|
|
|
|
|
([size] (alloc size (connected-scope)))
|
|
|
|
|
([size scope] (MemorySegment/allocateNative (long size) ^ResourceScope scope)))
|
|
|
|
|
|
|
|
|
|
(defn alloc-with
|
|
|
|
|
"Allocates `size` bytes using the `allocator`."
|
|
|
|
|
([allocator size]
|
|
|
|
|
(.allocate ^SegmentAllocator allocator (long size)))
|
|
|
|
|
([allocator size alignment]
|
|
|
|
|
(.allocate ^SegmentAllocator allocator (long size) (long alignment))))
|
|
|
|
|
|
|
|
|
|
(defmacro with-acquired
|
2021-09-27 17:09:23 +00:00
|
|
|
"Acquires one or more `scopes` until the `body` completes.
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
This is only necessary to do on shared scopes, however if you are operating on
|
|
|
|
|
an arbitrary passed scope, it is best practice to wrap code that interacts
|
|
|
|
|
with it wrapped in this."
|
2021-09-27 17:09:23 +00:00
|
|
|
{:style/indent 1}
|
|
|
|
|
[scopes & body]
|
2021-10-01 00:54:48 +00:00
|
|
|
`(let [scopes# (vec ~scopes)
|
|
|
|
|
handles# (mapv #(.acquire ^ResourceScope %) scopes#)]
|
|
|
|
|
(try ~@body
|
|
|
|
|
(finally
|
|
|
|
|
(doseq [idx# (range (count scopes#))
|
|
|
|
|
:let [scope# (nth scopes# idx#)
|
|
|
|
|
handle# (nth handles# idx#)]]
|
|
|
|
|
(.release ^ResourceScope scope#
|
|
|
|
|
^ResourceScope$Handle handle#))))))
|
2021-09-27 17:09:23 +00:00
|
|
|
(s/fdef with-acquired
|
|
|
|
|
:args (s/cat :scopes any?
|
|
|
|
|
:body (s/* any?)))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
(defn address-of
|
|
|
|
|
"Gets the address of a given segment.
|
|
|
|
|
|
|
|
|
|
This value can be used as an argument to functions which take a pointer."
|
|
|
|
|
[addressable]
|
|
|
|
|
(.address ^Addressable addressable))
|
|
|
|
|
|
|
|
|
|
(defn null?
|
|
|
|
|
"Checks if a memory address is null."
|
|
|
|
|
[addr]
|
2021-10-01 17:03:39 +00:00
|
|
|
(or (.equals (MemoryAddress/NULL) addr) (not addr)))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
2021-10-05 00:20:47 +00:00
|
|
|
(defn address?
|
|
|
|
|
"Checks if an object is a memory address.
|
|
|
|
|
|
|
|
|
|
`nil` is considered an address."
|
|
|
|
|
[addr]
|
|
|
|
|
(or (nil? addr) (instance? MemoryAddress addr)))
|
|
|
|
|
|
2021-09-26 19:11:29 +00:00
|
|
|
(defn slice-global
|
|
|
|
|
"Gets a slice of the global address space.
|
|
|
|
|
|
|
|
|
|
Because this fetches from the global segment, it has no associated scope, and
|
|
|
|
|
therefore the reference created here cannot prevent the value from being
|
|
|
|
|
freed. Be careful to ensure that you are not retaining an object incorrectly."
|
|
|
|
|
[address size]
|
|
|
|
|
(.asSlice (MemorySegment/globalNativeSegment)
|
|
|
|
|
^MemoryAddress address (long size)))
|
|
|
|
|
|
|
|
|
|
(defn slice
|
|
|
|
|
"Get a slice over the `segment` with the given `offset`."
|
|
|
|
|
([segment offset]
|
|
|
|
|
(.asSlice ^MemorySegment segment (long offset)))
|
|
|
|
|
([segment offset size]
|
|
|
|
|
(.asSlice ^MemorySegment segment (long offset) (long size))))
|
|
|
|
|
|
|
|
|
|
(defn slice-into
|
|
|
|
|
"Get a slice into the `segment` starting at the `address`."
|
|
|
|
|
([address segment]
|
|
|
|
|
(.asSlice ^MemorySegment segment ^MemoryAddress address))
|
|
|
|
|
([address segment size]
|
|
|
|
|
(.asSlice ^MemorySegment segment ^MemoryAddress address (long size))))
|
|
|
|
|
|
|
|
|
|
(defn with-offset
|
|
|
|
|
"Get a new address `offset` from the old `address`."
|
|
|
|
|
[address offset]
|
|
|
|
|
(.addOffset ^MemoryAddress address (long offset)))
|
|
|
|
|
|
|
|
|
|
(defn as-segment
|
|
|
|
|
"Dereferences an `address` into a memory segment associated with the `scope`.
|
|
|
|
|
|
|
|
|
|
If `cleanup` is provided, it is a 0-arity function run when the scope is
|
|
|
|
|
closed. This can be used to register a free method for the memory, or do other
|
|
|
|
|
cleanup in a way that doesn't require modifying the code at the point of
|
|
|
|
|
freeing, and allows shared or garbage collected resources to be freed
|
|
|
|
|
correctly."
|
|
|
|
|
([address size scope]
|
|
|
|
|
(.asSegment ^MemoryAddress address size scope))
|
|
|
|
|
([address size scope cleanup]
|
|
|
|
|
(.asSegment ^MemoryAddress address size cleanup scope)))
|
|
|
|
|
|
|
|
|
|
(defn add-close-action!
|
|
|
|
|
"Adds a 0-arity function to be run when the `scope` closes."
|
|
|
|
|
[scope action]
|
|
|
|
|
(.addCloseAction ^ResourceScope scope action))
|
|
|
|
|
|
|
|
|
|
(defn copy-segment
|
|
|
|
|
"Copies the content to `dest` from `src`"
|
|
|
|
|
[dest src]
|
2021-09-27 17:09:23 +00:00
|
|
|
(with-acquired (map segment-scope [src dest])
|
|
|
|
|
(.copyFrom ^MemorySegment dest ^MemorySegment src)))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
(defn clone-segment
|
|
|
|
|
"Clones the content of `segment` into a new segment of the same size."
|
|
|
|
|
([segment] (clone-segment segment (connected-scope)))
|
|
|
|
|
([segment scope]
|
2021-09-27 17:09:23 +00:00
|
|
|
(with-acquired [(segment-scope segment) scope]
|
|
|
|
|
(doto ^MemorySegment (alloc (.byteSize ^MemorySegment segment) scope)
|
|
|
|
|
(copy-segment segment)))))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
(defn slice-segments
|
|
|
|
|
"Constructs a lazy seq of `size`-length memory segments, sliced from `segment`."
|
|
|
|
|
[segment size]
|
|
|
|
|
(let [num-segments (quot (.byteSize ^MemorySegment segment) size)]
|
|
|
|
|
(map #(slice segment (* % size) size)
|
|
|
|
|
(range num-segments))))
|
|
|
|
|
|
|
|
|
|
(def primitive-types
|
|
|
|
|
"A set of keywords representing all the primitive types which may be passed to
|
|
|
|
|
or returned from native functions."
|
|
|
|
|
#{::byte ::short ::int ::long ::long-long
|
|
|
|
|
::char
|
|
|
|
|
::float ::double
|
|
|
|
|
::pointer ::void})
|
|
|
|
|
|
|
|
|
|
(defn- type-dispatch
|
|
|
|
|
"Gets a type dispatch value from a (potentially composite) type."
|
|
|
|
|
[type]
|
|
|
|
|
(cond
|
|
|
|
|
(qualified-keyword? type) type
|
|
|
|
|
(sequential? type) (keyword (first type))))
|
|
|
|
|
|
|
|
|
|
(defmulti primitive-type
|
|
|
|
|
"Gets the primitive type that is used to pass as an argument for the `type`.
|
|
|
|
|
|
|
|
|
|
This is for objects which are passed to native functions as primitive types,
|
|
|
|
|
but which need additional logic to be performed during serialization and
|
|
|
|
|
deserialization.
|
|
|
|
|
|
|
|
|
|
Returns nil for any type which does not have a primitive representation."
|
|
|
|
|
type-dispatch)
|
|
|
|
|
|
|
|
|
|
(defmethod primitive-type :default
|
|
|
|
|
[type]
|
|
|
|
|
(primitive-types type))
|
|
|
|
|
|
|
|
|
|
(defmethod primitive-type ::pointer
|
|
|
|
|
[_type]
|
|
|
|
|
::pointer)
|
|
|
|
|
|
|
|
|
|
(def c-prim-layout
|
|
|
|
|
"Map of primitive type names to the [[CLinker]] types for a method handle."
|
|
|
|
|
{::byte CLinker/C_CHAR
|
|
|
|
|
::short CLinker/C_SHORT
|
|
|
|
|
::int CLinker/C_INT
|
|
|
|
|
::long CLinker/C_LONG
|
|
|
|
|
::long-long CLinker/C_LONG_LONG
|
|
|
|
|
::char CLinker/C_CHAR
|
|
|
|
|
::float CLinker/C_FLOAT
|
|
|
|
|
::double CLinker/C_DOUBLE
|
|
|
|
|
::pointer CLinker/C_POINTER})
|
|
|
|
|
|
|
|
|
|
(defmulti c-layout
|
|
|
|
|
"Gets the layout object for a given `type`.
|
|
|
|
|
|
|
|
|
|
If a type is primitive it will return the appropriate primitive
|
|
|
|
|
layout (see [[c-prim-layout]]).
|
|
|
|
|
|
|
|
|
|
Otherwise, it should return a [[GroupLayout]] for the given type."
|
|
|
|
|
type-dispatch)
|
|
|
|
|
|
|
|
|
|
(defmethod c-layout :default
|
|
|
|
|
[type]
|
|
|
|
|
(c-prim-layout (or (primitive-type type) type)))
|
|
|
|
|
|
|
|
|
|
(def java-prim-layout
|
|
|
|
|
"Map of primitive type names to the Java types for a method handle."
|
|
|
|
|
{::byte Byte/TYPE
|
|
|
|
|
::short Short/TYPE
|
|
|
|
|
::int Integer/TYPE
|
|
|
|
|
::long Long/TYPE
|
|
|
|
|
::long-long Long/TYPE
|
|
|
|
|
::char Byte/TYPE
|
|
|
|
|
::float Float/TYPE
|
|
|
|
|
::double Double/TYPE
|
|
|
|
|
::pointer MemoryAddress
|
|
|
|
|
::void Void/TYPE})
|
|
|
|
|
|
|
|
|
|
(defn java-layout
|
|
|
|
|
"Gets the Java class to an argument of this type for a method handle.
|
|
|
|
|
|
|
|
|
|
If a type serializes to a primitive it returns return a Java primitive type.
|
|
|
|
|
Otherwise, it returns [[MemorySegment]]."
|
|
|
|
|
[type]
|
|
|
|
|
(java-prim-layout (or (primitive-type type) type) MemorySegment))
|
|
|
|
|
|
|
|
|
|
(defn size-of
|
|
|
|
|
"The size in bytes of the given `type`."
|
|
|
|
|
[type]
|
|
|
|
|
(.byteSize ^MemoryLayout (c-layout type)))
|
|
|
|
|
|
|
|
|
|
(defn alloc-instance
|
|
|
|
|
"Allocates a memory segment for the given `type`."
|
|
|
|
|
([type] (alloc-instance type (connected-scope)))
|
|
|
|
|
([type scope] (MemorySegment/allocateNative (long (size-of type)) ^ResourceScope scope)))
|
|
|
|
|
|
|
|
|
|
(declare serialize serialize-into)
|
|
|
|
|
|
|
|
|
|
(defmulti serialize*
|
|
|
|
|
"Constructs a serialized version of the `obj` and returns it.
|
|
|
|
|
|
|
|
|
|
Any new allocations made during the serialization should be tied to the given
|
|
|
|
|
`scope`, except in extenuating circumstances.
|
|
|
|
|
|
|
|
|
|
This method should only be implemented for types that serialize to primitives."
|
|
|
|
|
(fn
|
|
|
|
|
#_{:clj-kondo/ignore [:unused-binding]}
|
|
|
|
|
[obj type scope]
|
|
|
|
|
(type-dispatch type)))
|
|
|
|
|
|
|
|
|
|
(def ^:private primitive-cast
|
|
|
|
|
"Map from primitive type names to the function to cast it to a primitive."
|
|
|
|
|
{::byte byte
|
|
|
|
|
::short short
|
|
|
|
|
::int int
|
|
|
|
|
::long long
|
|
|
|
|
::long-long long
|
|
|
|
|
::char char
|
|
|
|
|
::float float
|
|
|
|
|
::double double})
|
|
|
|
|
|
|
|
|
|
(defmethod serialize* :default
|
|
|
|
|
[obj type _scope]
|
|
|
|
|
(if-let [prim (primitive-type type)]
|
2021-10-01 15:37:38 +00:00
|
|
|
(when-not (= ::void prim)
|
|
|
|
|
((primitive-cast prim) obj))
|
2021-09-26 19:11:29 +00:00
|
|
|
(throw (ex-info "Attempted to serialize a non-primitive type with primitive methods"
|
|
|
|
|
{:type type
|
|
|
|
|
:object obj}))))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize* ::pointer
|
|
|
|
|
[obj type scope]
|
2021-10-01 17:03:25 +00:00
|
|
|
(if-not (null? obj)
|
2021-09-26 19:11:29 +00:00
|
|
|
(if (sequential? type)
|
2021-09-27 17:09:23 +00:00
|
|
|
(with-acquired [scope]
|
|
|
|
|
(let [segment (alloc-instance (second type) scope)]
|
|
|
|
|
(serialize-into obj (second type) segment scope)
|
|
|
|
|
(address-of segment)))
|
2021-10-01 17:03:25 +00:00
|
|
|
obj)
|
|
|
|
|
(MemoryAddress/NULL)))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
(defmulti serialize-into
|
|
|
|
|
"Writes a serialized version of the `obj` to the given `segment`.
|
|
|
|
|
|
|
|
|
|
Any new allocations made during the serialization should be tied to the given
|
|
|
|
|
`scope`, except in extenuating circumstances.
|
|
|
|
|
|
|
|
|
|
This method should be implemented for any type which does not
|
|
|
|
|
override [[c-layout]].
|
|
|
|
|
|
|
|
|
|
For any other type, this will serialize it as [[serialize*]] before writing
|
2021-09-27 17:09:23 +00:00
|
|
|
the result value into the `segment`.
|
|
|
|
|
|
|
|
|
|
Implementations of this should be inside a [[with-acquired]] block for the
|
|
|
|
|
`scope` if they perform multiple memory operations."
|
2021-09-26 19:11:29 +00:00
|
|
|
(fn
|
|
|
|
|
#_{:clj-kondo/ignore [:unused-binding]}
|
|
|
|
|
[obj type segment scope]
|
|
|
|
|
(type-dispatch type)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into :default
|
|
|
|
|
[obj type segment scope]
|
|
|
|
|
(if-some [prim-layout (primitive-type type)]
|
2021-10-01 01:15:58 +00:00
|
|
|
(with-acquired [(segment-scope segment) scope]
|
2021-09-27 17:09:23 +00:00
|
|
|
(serialize-into (serialize* obj type scope) prim-layout segment scope))
|
2021-09-26 19:11:29 +00:00
|
|
|
(throw (ex-info "Attempted to serialize an object to a type that has not been overriden"
|
|
|
|
|
{:type type
|
|
|
|
|
:object obj}))))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::byte
|
|
|
|
|
[obj _type segment _scope]
|
|
|
|
|
(MemoryAccess/setByte segment (byte obj)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::short
|
|
|
|
|
[obj _type segment _scope]
|
|
|
|
|
(MemoryAccess/setShort segment (short obj)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::int
|
|
|
|
|
[obj _type segment _scope]
|
|
|
|
|
(MemoryAccess/setInt segment (int obj)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::long
|
|
|
|
|
[obj _type segment _scope]
|
|
|
|
|
(MemoryAccess/setLong segment (long obj)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::long-long
|
|
|
|
|
[obj _type segment _scope]
|
|
|
|
|
(MemoryAccess/setLong segment (long obj)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::char
|
|
|
|
|
[obj _type segment _scope]
|
|
|
|
|
(MemoryAccess/setChar segment (char obj)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::float
|
|
|
|
|
[obj _type segment _scope]
|
|
|
|
|
(MemoryAccess/setFloat segment (float obj)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::double
|
|
|
|
|
[obj _type segment _scope]
|
|
|
|
|
(MemoryAccess/setDouble segment (double obj)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::pointer
|
2021-09-27 16:18:53 +00:00
|
|
|
[obj type segment scope]
|
|
|
|
|
(with-acquired [(segment-scope segment) scope]
|
|
|
|
|
(MemoryAccess/setAddress
|
|
|
|
|
segment
|
|
|
|
|
(cond-> obj
|
|
|
|
|
(sequential? type) (serialize* type scope)))))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
(defn serialize
|
|
|
|
|
"Serializes an arbitrary type.
|
|
|
|
|
|
|
|
|
|
For types which have a primitive representation, this serializes into that
|
|
|
|
|
representation. For types which do not, it allocates a new segment and
|
|
|
|
|
serializes into that."
|
|
|
|
|
([obj type] (serialize obj type (connected-scope)))
|
|
|
|
|
([obj type scope]
|
|
|
|
|
(if (primitive-type type)
|
|
|
|
|
(serialize* obj type scope)
|
|
|
|
|
(let [segment (alloc-instance type scope)]
|
|
|
|
|
(serialize-into obj type segment scope)
|
|
|
|
|
segment))))
|
|
|
|
|
|
|
|
|
|
(declare deserialize deserialize*)
|
|
|
|
|
|
|
|
|
|
(defmulti deserialize-from
|
|
|
|
|
"Deserializes the given segment into a Clojure data structure.
|
|
|
|
|
|
|
|
|
|
For types that serialize to primitives, a default implementation will
|
2021-09-27 17:09:23 +00:00
|
|
|
deserialize the primitive before calling [[deserialize*]].
|
|
|
|
|
|
|
|
|
|
Implementations of this should be inside a [[with-acquired]] block for the the
|
|
|
|
|
`segment`'s scope if they perform multiple memory operations."
|
2021-09-26 19:11:29 +00:00
|
|
|
(fn
|
|
|
|
|
#_{:clj-kondo/ignore [:unused-binding]}
|
|
|
|
|
[segment type]
|
|
|
|
|
(type-dispatch type)))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from :default
|
|
|
|
|
[segment type]
|
|
|
|
|
(if-some [prim (primitive-type type)]
|
2021-09-27 17:09:23 +00:00
|
|
|
(with-acquired [(segment-scope segment)]
|
|
|
|
|
(-> segment
|
|
|
|
|
(deserialize-from prim)
|
|
|
|
|
(deserialize* type)))
|
2021-09-26 19:11:29 +00:00
|
|
|
(throw (ex-info "Attempted to deserialize a non-primitive type that has not been overriden"
|
|
|
|
|
{:type type
|
|
|
|
|
:segment segment}))))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::byte
|
|
|
|
|
[segment _type]
|
|
|
|
|
(MemoryAccess/getByte segment))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::short
|
|
|
|
|
[segment _type]
|
|
|
|
|
(MemoryAccess/getShort segment))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::int
|
|
|
|
|
[segment _type]
|
|
|
|
|
(MemoryAccess/getInt segment))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::long
|
|
|
|
|
[segment _type]
|
|
|
|
|
(MemoryAccess/getLong segment))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::long-long
|
|
|
|
|
[segment _type]
|
|
|
|
|
(MemoryAccess/getLong segment))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::char
|
|
|
|
|
[segment _type]
|
|
|
|
|
(MemoryAccess/getChar segment))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::float
|
|
|
|
|
[segment _type]
|
|
|
|
|
(MemoryAccess/getFloat segment))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::double
|
|
|
|
|
[segment _type]
|
|
|
|
|
(MemoryAccess/getDouble segment))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::pointer
|
|
|
|
|
[segment type]
|
2021-09-27 17:09:23 +00:00
|
|
|
(with-acquired [(segment-scope segment)]
|
|
|
|
|
(cond-> (MemoryAccess/getAddress segment)
|
|
|
|
|
(sequential? type) (deserialize* type))))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
(defmulti deserialize*
|
|
|
|
|
"Deserializes a primitive object into a Clojure data structure.
|
|
|
|
|
|
|
|
|
|
This is intended for use with types that are returned as a primitive but which
|
|
|
|
|
need additional processing before they can be returned."
|
|
|
|
|
(fn
|
|
|
|
|
#_{:clj-kondo/ignore [:unused-binding]}
|
|
|
|
|
[obj type]
|
|
|
|
|
(type-dispatch type)))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize* :default
|
|
|
|
|
[obj type]
|
|
|
|
|
(if (primitive-type type)
|
|
|
|
|
obj
|
|
|
|
|
(throw (ex-info "Attempted to deserialize a non-primitive type with primitive methods"
|
|
|
|
|
{:type type
|
|
|
|
|
:segment obj}))))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize* ::pointer
|
|
|
|
|
[addr type]
|
|
|
|
|
(when-not (null? addr)
|
|
|
|
|
(if (sequential? type)
|
|
|
|
|
(deserialize-from (slice-global addr (size-of (second type)))
|
|
|
|
|
(second type))
|
|
|
|
|
addr)))
|
|
|
|
|
|
|
|
|
|
(defn deserialize
|
|
|
|
|
"Deserializes an arbitrary type.
|
|
|
|
|
|
|
|
|
|
For types which have a primitive representation, this deserializes the
|
|
|
|
|
primitive representation. For types which do not, this deserializes out of
|
|
|
|
|
a segment."
|
|
|
|
|
[obj type]
|
|
|
|
|
(when-not (identical? ::void type)
|
2021-09-27 17:09:23 +00:00
|
|
|
(if (primitive-type type)
|
|
|
|
|
(deserialize* obj type)
|
|
|
|
|
(deserialize-from obj type))))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
(defn seq-of
|
|
|
|
|
"Constructs a lazy sequence of `type` elements deserialized from `segment`."
|
|
|
|
|
[type segment]
|
2021-09-27 17:09:23 +00:00
|
|
|
(with-acquired [(segment-scope segment)]
|
|
|
|
|
(map #(deserialize % type) (slice-segments segment (size-of type)))))
|
2021-09-26 19:11:29 +00:00
|
|
|
|
|
|
|
|
;;; C String type
|
|
|
|
|
|
|
|
|
|
(defmethod primitive-type ::c-string
|
|
|
|
|
[_type]
|
|
|
|
|
::pointer)
|
|
|
|
|
|
|
|
|
|
(defmethod serialize* ::c-string
|
|
|
|
|
[obj _type scope]
|
|
|
|
|
(if obj
|
|
|
|
|
(address-of (CLinker/toCString ^String obj ^ResourceScope scope))
|
|
|
|
|
(MemoryAddress/NULL)))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize* ::c-string
|
|
|
|
|
[addr _type]
|
|
|
|
|
(when-not (null? addr)
|
|
|
|
|
(CLinker/toJavaString ^MemoryAddress addr)))
|
|
|
|
|
|
|
|
|
|
;;; Union types
|
|
|
|
|
|
|
|
|
|
(defmethod c-layout ::union
|
|
|
|
|
[[_union types & {:as _opts} :as _type]]
|
|
|
|
|
(let [items (map c-layout types)]
|
|
|
|
|
(MemoryLayout/unionLayout
|
|
|
|
|
(into-array MemoryLayout items))))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::union
|
|
|
|
|
[obj [_union _types & {:keys [dispatch extract]} :as type] segment scope]
|
|
|
|
|
(when-not dispatch
|
|
|
|
|
(throw (ex-info "Attempted to serialize a union with no dispatch function"
|
|
|
|
|
{:type type
|
|
|
|
|
:value obj})))
|
|
|
|
|
(let [type (dispatch obj)]
|
|
|
|
|
(serialize-into
|
|
|
|
|
(if extract
|
|
|
|
|
(extract type obj)
|
|
|
|
|
obj)
|
|
|
|
|
type
|
|
|
|
|
segment
|
|
|
|
|
scope)))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::union
|
|
|
|
|
[segment type]
|
|
|
|
|
(clone-segment (slice segment 0 (size-of type))))
|
|
|
|
|
|
|
|
|
|
;;; Struct types
|
|
|
|
|
|
|
|
|
|
(defmethod c-layout ::struct
|
|
|
|
|
[[_struct fields]]
|
|
|
|
|
(let [fields (for [[field-name field] fields]
|
|
|
|
|
(.withName ^MemoryLayout (c-layout field)
|
|
|
|
|
(name field-name)))]
|
|
|
|
|
(MemoryLayout/structLayout
|
|
|
|
|
(into-array MemoryLayout fields))))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::struct
|
|
|
|
|
[obj [_struct fields] segment scope]
|
|
|
|
|
(loop [offset 0
|
|
|
|
|
fields fields]
|
|
|
|
|
(when (seq fields)
|
|
|
|
|
(let [[field type] (first fields)
|
|
|
|
|
size (size-of type)]
|
|
|
|
|
(serialize-into
|
|
|
|
|
(get obj field) type
|
|
|
|
|
(slice segment offset size) scope)
|
|
|
|
|
(recur (long (+ offset size)) (rest fields))))))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::struct
|
|
|
|
|
[segment [_struct fields]]
|
|
|
|
|
(loop [offset 0
|
|
|
|
|
fields fields
|
|
|
|
|
obj {}]
|
|
|
|
|
(if (seq fields)
|
|
|
|
|
(let [[field type] (first fields)
|
|
|
|
|
size (size-of type)]
|
|
|
|
|
(recur
|
|
|
|
|
(long (+ offset size))
|
|
|
|
|
(rest fields)
|
|
|
|
|
(assoc obj field (deserialize-from
|
|
|
|
|
(slice segment offset size)
|
|
|
|
|
type))))
|
|
|
|
|
obj)))
|
|
|
|
|
|
|
|
|
|
;;; Padding type
|
|
|
|
|
|
|
|
|
|
(defmethod c-layout ::padding
|
|
|
|
|
[[_padding size]]
|
|
|
|
|
(MemoryLayout/paddingLayout (* 8 size)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::padding
|
|
|
|
|
[_obj [_padding _size] _segment _scope]
|
|
|
|
|
nil)
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::padding
|
|
|
|
|
[_segment [_padding _size]]
|
|
|
|
|
nil)
|
|
|
|
|
|
|
|
|
|
;;; Array types
|
|
|
|
|
|
|
|
|
|
(defmethod c-layout ::array
|
|
|
|
|
[[_array type count]]
|
|
|
|
|
(MemoryLayout/sequenceLayout
|
|
|
|
|
count
|
|
|
|
|
(c-layout type)))
|
|
|
|
|
|
|
|
|
|
(defmethod serialize-into ::array
|
|
|
|
|
[obj [_array type count] segment scope]
|
|
|
|
|
(dorun
|
|
|
|
|
(map #(serialize-into %1 type %2 scope)
|
|
|
|
|
obj
|
|
|
|
|
(slice-segments (slice segment 0 (* count (size-of type)))
|
|
|
|
|
(size-of type)))))
|
|
|
|
|
|
|
|
|
|
(defmethod deserialize-from ::array
|
|
|
|
|
[segment [_array type count]]
|
|
|
|
|
(map #(deserialize-from % type)
|
|
|
|
|
(slice-segments (slice segment 0 (* count (size-of type)))
|
|
|
|
|
(size-of type))))
|
|
|
|
|
|
|
|
|
|
(s/def ::type
|
|
|
|
|
(s/spec
|
|
|
|
|
(s/nonconforming
|
|
|
|
|
(s/or :simple-type qualified-keyword?
|
|
|
|
|
:complex-type (s/cat :base-type qualified-keyword?
|
|
|
|
|
:type-args (s/* any?))))))
|
|
|
|
|
|
|
|
|
|
(defmacro defalias
|
|
|
|
|
"Defines a type alias from `new-type` to `aliased-type`.
|
|
|
|
|
|
|
|
|
|
This creates needed serialization and deserialization implementations for the
|
|
|
|
|
aliased type."
|
|
|
|
|
{:style/indent [:defn]}
|
|
|
|
|
[new-type aliased-type]
|
|
|
|
|
(if (primitive-type aliased-type)
|
|
|
|
|
`(let [aliased# ~aliased-type]
|
|
|
|
|
(defmethod primitive-type ~new-type
|
|
|
|
|
[_type#]
|
|
|
|
|
(primitive-type aliased#))
|
|
|
|
|
(defmethod serialize* ~new-type
|
|
|
|
|
[obj# _type# scope#]
|
|
|
|
|
(serialize* obj# aliased# scope#))
|
|
|
|
|
(defmethod deserialize* ~new-type
|
|
|
|
|
[obj# _type#]
|
|
|
|
|
(deserialize* obj# aliased#)))
|
|
|
|
|
`(let [aliased# ~aliased-type]
|
|
|
|
|
(defmethod c-layout ~new-type
|
|
|
|
|
[_type#]
|
|
|
|
|
(c-layout aliased#))
|
|
|
|
|
(defmethod serialize-into ~new-type
|
|
|
|
|
[obj# _type# segment# scope#]
|
|
|
|
|
(serialize-into obj# aliased# segment# scope#))
|
|
|
|
|
(defmethod deserialize-from ~new-type
|
|
|
|
|
[segment# _type#]
|
|
|
|
|
(deserialize-from segment# aliased#)))))
|
|
|
|
|
(s/fdef defalias
|
|
|
|
|
:args (s/cat :new-type qualified-keyword?
|
|
|
|
|
:aliased-type ::type))
|