Merge branch 'doc/arena-session-cleanup' into develop
This commit is contained in:
commit
2676a7aa5d
8 changed files with 253 additions and 308 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -14,3 +14,4 @@
|
||||||
/.socket-repl-port
|
/.socket-repl-port
|
||||||
.hgignore
|
.hgignore
|
||||||
.hg/
|
.hg/
|
||||||
|
/.direnv
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. This change
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
### Added
|
### Added
|
||||||
- Support for JDK 22
|
- Support for JDK 22
|
||||||
|
- `reinterpret` function which changes the size associated with a segment, optionally associating it with an arena and cleanup action
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Arglists and docstrings of functions to refer to arenas rather than the outdated terms scope or session
|
||||||
|
- Change the arguments to `as-segment` to take longs to account for the removal of an Address type
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Deprecated functions referring to sessions and scopes
|
||||||
|
- Deprecated functions `slice-into` and `with-offset`, replaced by the function `slice`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Prep step when using coffi as a dependency wouldn't re-run if it failed during execution, e.g. when using the incorrect java version
|
- Prep step when using coffi as a dependency wouldn't re-run if it failed during execution, e.g. when using the incorrect java version
|
||||||
|
|
|
||||||
169
README.md
169
README.md
|
|
@ -1,15 +1,15 @@
|
||||||
# coffi
|
# coffi
|
||||||
[](https://clojars.org/org.suskalo/coffi)
|
[](https://clojars.org/org.suskalo/coffi)
|
||||||
|
|
||||||
Coffi is a foreign function interface library for Clojure, using the new
|
Coffi is a foreign function interface library for Clojure, using the [Foreign
|
||||||
[Project Panama](https://openjdk.java.net/projects/panama/) that's a part of the
|
Function & Memory API](https://openjdk.org/jeps/454) in JDK 22 and later. This
|
||||||
preview in Java 19. This allows calling native code directly from Clojure
|
allows calling native code directly from Clojure without the need for either
|
||||||
without the need for either Java or native code specific to the library, as e.g.
|
Java or native code specific to the library, as e.g. the JNI does. Coffi focuses
|
||||||
the JNI does. Coffi focuses on ease of use, including functions and macros for
|
on ease of use, including functions and macros for creating wrappers to allow
|
||||||
creating wrappers to allow the resulting native functions to act just like
|
the resulting native functions to act just like Clojure ones, however this
|
||||||
Clojure ones, however this doesn't remove the ability to write systems which
|
doesn't remove the ability to write systems which minimize the cost of
|
||||||
minimize the cost of marshaling data and optimize for performance, to make use
|
marshaling data and optimize for performance, to make use of the low-level
|
||||||
of the low-level access Panama gives us.
|
access Panama gives us.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
This library is available on Clojars. Add one of the following entries to the
|
This library is available on Clojars. Add one of the following entries to the
|
||||||
|
|
@ -27,19 +27,20 @@ library.
|
||||||
$ clj -X:deps prep
|
$ clj -X:deps prep
|
||||||
```
|
```
|
||||||
|
|
||||||
Coffi requires usage of the package `java.lang.foreign`, and everything in this
|
Coffi requires usage of the package `java.lang.foreign`, and most of the
|
||||||
package is considered to be a preview release, which are disabled by default. In
|
operations are considered unsafe by the JDK, and are therefore unavailable to
|
||||||
order to use coffi, add the following JVM arguments to your application.
|
your code without passing some command line flags. In order to use coffi, add
|
||||||
|
the following JVM arguments to your application.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
--enable-preview --enable-native-access=ALL-UNNAMED
|
--enable-native-access=ALL-UNNAMED
|
||||||
```
|
```
|
||||||
|
|
||||||
You can specify JVM arguments in a particular invocation of the Clojure CLI with
|
You can specify JVM arguments in a particular invocation of the Clojure CLI with
|
||||||
the -J flag like so:
|
the -J flag like so:
|
||||||
|
|
||||||
``` sh
|
``` sh
|
||||||
clj -J--enable-preview -J--enable-native-access=ALL-UNNAMED
|
clj -J--enable-native-access=ALL-UNNAMED
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also specify them in an alias in your `deps.edn` file under the
|
You can also specify them in an alias in your `deps.edn` file under the
|
||||||
|
|
@ -47,15 +48,20 @@ You can also specify them in an alias in your `deps.edn` file under the
|
||||||
using `-M`, `-A`, or `-X`.
|
using `-M`, `-A`, or `-X`.
|
||||||
|
|
||||||
``` clojure
|
``` clojure
|
||||||
{:aliases {:dev {:jvm-opts ["--enable-preview" "--enable-native-access=ALL-UNNAMED"]}}}
|
{:aliases {:dev {:jvm-opts ["--enable-native-access=ALL-UNNAMED"]}}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Other build tools should provide similar functionality if you check their
|
Other build tools should provide similar functionality if you check their
|
||||||
documentation.
|
documentation.
|
||||||
|
|
||||||
|
When creating an executable jar file, you can avoid the need to pass this
|
||||||
|
argument by adding the manifest attribute `Enable-Native-Access: ALL-UNNAMED` to
|
||||||
|
your jar.
|
||||||
|
|
||||||
Coffi also includes support for the linter clj-kondo. If you use clj-kondo and
|
Coffi also includes support for the linter clj-kondo. If you use clj-kondo and
|
||||||
this library's macros are not linting correctly, you may need to install the
|
this library's macros are not linting correctly, you may need to install the
|
||||||
config bundled with the library. You can do so with the following shell command:
|
config bundled with the library. You can do so with the following shell command,
|
||||||
|
run from your project directory:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ clj-kondo --copy-configs --dependencies --lint "$(clojure -Spath)"
|
$ clj-kondo --copy-configs --dependencies --lint "$(clojure -Spath)"
|
||||||
|
|
@ -193,7 +199,7 @@ be found in `coffi.layout`.
|
||||||
[[:a ::mem/char]
|
[[:a ::mem/char]
|
||||||
[:x ::mem/float]]]))
|
[:x ::mem/float]]]))
|
||||||
|
|
||||||
(mem/size-of ::needs-padding))
|
(mem/size-of ::needs-padding)
|
||||||
;; => 8
|
;; => 8
|
||||||
|
|
||||||
(mem/align-of ::needs-padding)
|
(mem/align-of ::needs-padding)
|
||||||
|
|
@ -331,7 +337,7 @@ Clojure code to make this easier.
|
||||||
native-fn
|
native-fn
|
||||||
[ints]
|
[ints]
|
||||||
(let [arr-len (count ints)
|
(let [arr-len (count ints)
|
||||||
int-array (serialize ints [::mem/array ::mem/int arr-len])]
|
int-array (mem/serialize ints [::mem/array ::mem/int arr-len])]
|
||||||
(native-fn (mem/address-of int-array) arr-len)))
|
(native-fn (mem/address-of int-array) arr-len)))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -349,38 +355,34 @@ This can be used to implement out variables often seen in native code.
|
||||||
"out_int" [::mem/pointer] ::mem/void
|
"out_int" [::mem/pointer] ::mem/void
|
||||||
native-fn
|
native-fn
|
||||||
[i]
|
[i]
|
||||||
(let [int-ptr (serialize i [::mem/pointer ::mem/int])]
|
(let [int-ptr (mem/serialize i [::mem/pointer ::mem/int])]
|
||||||
(native-fn int-ptr)
|
(native-fn int-ptr)
|
||||||
(deserialize int-ptr [::mem/pointer ::mem/int])))
|
(mem/deserialize int-ptr [::mem/pointer ::mem/int])))
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sessions
|
### Arenas
|
||||||
**Before JDK 19 Sessions were called Scopes. Coffi retains functions that are
|
|
||||||
named for creating scopes for backwards compatibility, but they will be removed
|
|
||||||
in version 1.0.**
|
|
||||||
|
|
||||||
In order to serialize any non-primitive type (such as the previous
|
In order to serialize any non-primitive type (such as the previous
|
||||||
`[::mem/pointer ::mem/int]`), off-heap memory needs to be allocated. When memory
|
`[::mem/pointer ::mem/int]`), off-heap memory needs to be allocated. When memory
|
||||||
is allocated inside the JVM, the memory is associated with a session. Because
|
is allocated inside the JVM, the memory is associated with an arena. Because
|
||||||
none was provided here, the session is an implicit session, and the memory will
|
none was provided here, the arena is an implicit arena, and the memory will be
|
||||||
be freed when the serialized object is garbage collected.
|
freed when the serialized object is garbage collected.
|
||||||
|
|
||||||
In many cases this is not desirable, because the memory is not freed in a
|
In many cases this is not desirable, because the memory is not freed in a
|
||||||
deterministic manner, causing garbage collection pauses to become longer, as
|
deterministic manner, causing garbage collection pauses to become longer, as
|
||||||
well as changing allocation performance. Instead of an implicit session, there
|
well as changing allocation performance. Instead of an implicit arena, there
|
||||||
are other kinds of sessions as well. A `stack-session` is a thread-local
|
are other kinds of arenas as well. A `confined-arena` is a thread-local arena.
|
||||||
session. Stack sessions are `Closeable`, which means they should usually be used
|
Confined arenas are `Closeable`, which means they should usually be used in a
|
||||||
in a `with-open` form. When a `stack-session` is closed, it immediately frees
|
`with-open` form. When a `confined-arena` is closed, it immediately frees all
|
||||||
all the memory associated with it. The previous example, `out-int`, can be
|
the memory associated with it. The previous example, `out-int`, can be
|
||||||
implemented with a stack session.
|
implemented with a confined arena.
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(defcfn out-int
|
(defcfn out-int
|
||||||
"out_int" [::mem/pointer] ::mem/void
|
"out_int" [::mem/pointer] ::mem/void
|
||||||
native-fn
|
native-fn
|
||||||
[i]
|
[i]
|
||||||
(with-open [session (mem/stack-session)]
|
(with-open [arena (mem/confined-arena)]
|
||||||
(let [int-ptr (mem/serialize i [::mem/pointer ::mem/int] session)]
|
(let [int-ptr (mem/serialize i [::mem/pointer ::mem/int] arena)]
|
||||||
(native-fn int-ptr)
|
(native-fn int-ptr)
|
||||||
(mem/deserialize int-ptr [::mem/pointer ::mem/int]))))
|
(mem/deserialize int-ptr [::mem/pointer ::mem/int]))))
|
||||||
```
|
```
|
||||||
|
|
@ -388,15 +390,15 @@ implemented with a stack session.
|
||||||
This will free the pointer immediately upon leaving the function.
|
This will free the pointer immediately upon leaving the function.
|
||||||
|
|
||||||
When memory needs to be accessible from multiple threads, there's
|
When memory needs to be accessible from multiple threads, there's
|
||||||
`shared-session`. When using a `shared-session`, it should be accessed inside a
|
`shared-arena`. When a `shared-arena` is `.close`d, it will release all its
|
||||||
`with-acquired` block. When a `shared-session` is `.close`d, it will release all
|
associated memory immediately, and so this should only be done once all other
|
||||||
its associated memory when every `with-acquired` block associated with it is
|
threads are done accessing memory associated with it.
|
||||||
exited.
|
|
||||||
|
|
||||||
In addition, two non-`Closeable` sessions are `global-session`, which never
|
In addition, two non-`Closeable` arenas are `global-arena`, which never frees
|
||||||
frees the resources associated with it, and `connected-session`, which is a
|
the resources associated with it, and `auto-arena`, which is an arena that frees
|
||||||
session that frees its resources on garbage collection, like an implicit
|
its resources once all of them are unreachable during a garbage collection
|
||||||
session.
|
cycle, like an implicit arena, but potentially for multiple allocations rather
|
||||||
|
than just one.
|
||||||
|
|
||||||
### Serialization and Deserialization
|
### Serialization and Deserialization
|
||||||
Custom serializers and deserializers may also be created. This is done using two
|
Custom serializers and deserializers may also be created. This is done using two
|
||||||
|
|
@ -426,34 +428,35 @@ serialize to primitives.
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(defmethod mem/serialize* ::vector
|
(defmethod mem/serialize* ::vector
|
||||||
[obj _type session]
|
[obj _type arena]
|
||||||
(mem/address-of (mem/serialize obj [::mem/array ::mem/float 3] session)))
|
(mem/serialize obj [::mem/array ::mem/float 3] arena))
|
||||||
|
|
||||||
(defmethod mem/deserialize* ::vector
|
(defmethod mem/deserialize* ::vector
|
||||||
[addr _type]
|
[segment _type]
|
||||||
(mem/deserialize (mem/slice-global addr (mem/size-of [::mem/array ::mem/float 3]))
|
(mem/deserialize (mem/reinterpret segment (mem/size-of [::mem/array ::mem/float 3]))
|
||||||
[::mem/array ::mem/float 3]))
|
[::mem/array ::mem/float 3]))
|
||||||
```
|
```
|
||||||
|
|
||||||
The `slice-global` function allows you to take an address without an associated
|
The `reinterpret` function allows you to take a segment and decorate it with a
|
||||||
session and get a memory segment which can be deserialized.
|
new size, and possibly associate it with an arena or add cleanup functions on
|
||||||
|
it.
|
||||||
|
|
||||||
In cases like this where we don't know the session of the pointer, we could use
|
In cases like this where we don't know the arena of the pointer, we could use
|
||||||
`add-close-action!` to ensure it's freed. For example if a `free-vector!`
|
`reinterpret` to ensure it's freed. For example if a `free-vector!` function
|
||||||
function that takes a pointer exists, we could use this:
|
that takes a pointer exists, we could use this:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(defcfn returns-vector
|
(defcfn returns-vector
|
||||||
"returns_vector" [] ::mem/pointer
|
"returns_vector" [] ::mem/pointer
|
||||||
native-fn
|
native-fn
|
||||||
[session]
|
[arena]
|
||||||
(let [ret-ptr (native-fn)]
|
(let [ret-ptr (native-fn)]
|
||||||
(add-close-action! session #(free-vector! ret-ptr))
|
(-> (reinterpret ret-ptr (mem/size-of ::vector) arena free-vector!)
|
||||||
(deserialize ret-ptr ::vector)))
|
(deserialize ::vector))))
|
||||||
```
|
```
|
||||||
|
|
||||||
This function takes a session and returns the deserialized vector, and it will
|
This function takes an arena and returns the deserialized vector, and it will
|
||||||
free the pointer when the session closes.
|
free the pointer when the arena closes.
|
||||||
|
|
||||||
#### Tagged Union
|
#### Tagged Union
|
||||||
For the tagged union type, we will represent the value as a vector of a keyword
|
For the tagged union type, we will represent the value as a vector of a keyword
|
||||||
|
|
@ -505,7 +508,7 @@ deserialize the value into and out of memory segments. This is accomplished with
|
||||||
(map first))))
|
(map first))))
|
||||||
|
|
||||||
(defmethod mem/serialize-into ::tagged-union
|
(defmethod mem/serialize-into ::tagged-union
|
||||||
[obj [_tagged-union tags type-map] segment session]
|
[obj [_tagged-union tags type-map] segment arena]
|
||||||
(mem/serialize-into
|
(mem/serialize-into
|
||||||
{:tag (item-index tags (first obj))
|
{:tag (item-index tags (first obj))
|
||||||
:value (second obj)}
|
:value (second obj)}
|
||||||
|
|
@ -513,7 +516,7 @@ deserialize the value into and out of memory segments. This is accomplished with
|
||||||
[[:tag ::mem/long]
|
[[:tag ::mem/long]
|
||||||
[:value (get type-map (first obj))]]]
|
[:value (get type-map (first obj))]]]
|
||||||
segment
|
segment
|
||||||
session))
|
arena))
|
||||||
```
|
```
|
||||||
|
|
||||||
This serialization method is rather simple, it just turns the vector value into
|
This serialization method is rather simple, it just turns the vector value into
|
||||||
|
|
@ -570,7 +573,7 @@ it could be represented for serialization purposes like so:
|
||||||
This union however would not include the tag when serialized.
|
This union however would not include the tag when serialized.
|
||||||
|
|
||||||
If a union is deserialized, then all that coffi does is to allocate a new
|
If a union is deserialized, then all that coffi does is to allocate a new
|
||||||
segment of the appropriate size with an implicit session so that it may later be
|
segment of the appropriate size with an implicit arena so that it may later be
|
||||||
garbage collected, and copies the data from the source segment into it. It's up
|
garbage collected, and copies the data from the source segment into it. It's up
|
||||||
to the user to call `deserialize-from` on that segment with the appropriate
|
to the user to call `deserialize-from` on that segment with the appropriate
|
||||||
type.
|
type.
|
||||||
|
|
@ -593,14 +596,14 @@ create raw function handles.
|
||||||
|
|
||||||
With raw handles, the argument types are expected to exactly match the types
|
With raw handles, the argument types are expected to exactly match the types
|
||||||
expected by the native function. For primitive types, those are primitives. For
|
expected by the native function. For primitive types, those are primitives. For
|
||||||
addresses, that is `MemoryAddress`, and for composite types like structs and
|
pointers, that is `MemorySegment`, and for composite types like structs and
|
||||||
unions, that is `MemorySegment`. Both `MemoryAddress` and `MemorySegment` come
|
unions, that is also `MemorySegment`. `MemorySegment` comes from the
|
||||||
from the `java.lang.foreign` package.
|
`java.lang.foreign` package.
|
||||||
|
|
||||||
In addition, when a raw handle returns a composite type represented with a
|
In addition, when a raw handle returns a composite type represented with a
|
||||||
`MemorySegment`, it requires an additional first argument, a `SegmentAllocator`,
|
`MemorySegment`, it requires an additional first argument, a `SegmentAllocator`,
|
||||||
which can be acquired with `session-allocator` to get one associated with a
|
which can be acquired with `arena-allocator` to get one associated with a
|
||||||
specific session. The returned value will live until that session is released.
|
specific arena. The returned value will live until that arena is released.
|
||||||
|
|
||||||
In addition, function types can be specified as being raw, in the following
|
In addition, function types can be specified as being raw, in the following
|
||||||
manner:
|
manner:
|
||||||
|
|
@ -635,14 +638,18 @@ As an example, when wrapping a function that returns an array of big-endian
|
||||||
floats, the following code might be used.
|
floats, the following code might be used.
|
||||||
|
|
||||||
``` clojure
|
``` clojure
|
||||||
|
;; int returns_float_array(float **arr)
|
||||||
(def ^:private returns-float-array* (ffi/make-downcall "returns_float_array" [::mem/pointer] ::mem/int))
|
(def ^:private returns-float-array* (ffi/make-downcall "returns_float_array" [::mem/pointer] ::mem/int))
|
||||||
|
;; void releases_float_array(float *arr)
|
||||||
(def ^:private release-floats* (ffi/make-downcall "releases_float_array" [::mem/pointer] ::mem/void))
|
(def ^:private release-floats* (ffi/make-downcall "releases_float_array" [::mem/pointer] ::mem/void))
|
||||||
|
|
||||||
(defn returns-float-array
|
(defn returns-float-array
|
||||||
[]
|
[]
|
||||||
(with-open [session (mem/stack-session)]
|
(with-open [arena (mem/confined-arena)]
|
||||||
(let [out-floats (mem/alloc mem/pointer-size session)
|
;; float *out_floats;
|
||||||
num-floats (function-handle (mem/address-of out-floats))
|
;; int num_floats = returns_float_array(&out_floats);
|
||||||
|
(let [out-floats (mem/alloc mem/pointer-size arena)
|
||||||
|
num-floats (returns-float-array* (mem/address-of out-floats))
|
||||||
floats-addr (mem/read-address out-floats)
|
floats-addr (mem/read-address out-floats)
|
||||||
floats-slice (mem/slice-global floats-addr (unchecked-multiply-int mem/float-size num-floats))]
|
floats-slice (mem/slice-global floats-addr (unchecked-multiply-int mem/float-size num-floats))]
|
||||||
;; Using a try/finally to perform an operation when the stack frame exits,
|
;; Using a try/finally to perform an operation when the stack frame exits,
|
||||||
|
|
@ -657,7 +664,7 @@ floats, the following code might be used.
|
||||||
mem/big-endian))
|
mem/big-endian))
|
||||||
(unchecked-inc-int index))))
|
(unchecked-inc-int index))))
|
||||||
(finally
|
(finally
|
||||||
(release-floats floats-addr))))))
|
(release-floats* floats-addr))))))
|
||||||
```
|
```
|
||||||
|
|
||||||
The above code manually performs all memory operations rather than relying on
|
The above code manually performs all memory operations rather than relying on
|
||||||
|
|
@ -711,6 +718,8 @@ the multimethod `reify-symbolspec`, although it's recommended that for any
|
||||||
library authors who do so, namespaced keywords be used to name types.
|
library authors who do so, namespaced keywords be used to name types.
|
||||||
|
|
||||||
## Alternatives
|
## Alternatives
|
||||||
|
**ALTERNATIVES INFORMATION IS OUT OF DATE. THE LINKS ARE FINE, BUT DESCRIPTIONS WILL BE UPDATED AT A LATER DATE.**
|
||||||
|
|
||||||
This library is not the only Clojure library providing access to native code. In
|
This library is not the only Clojure library providing access to native code. In
|
||||||
addition the following libraries exist:
|
addition the following libraries exist:
|
||||||
|
|
||||||
|
|
@ -740,7 +749,7 @@ appealing, as they have a smaller API surface area and it's easier to wrap
|
||||||
functions.
|
functions.
|
||||||
|
|
||||||
### Benchmarks
|
### Benchmarks
|
||||||
**BENCHMARKS FOR COFFI AND DTYPE-NEXT ARE BASED ON AN OLD VERSION. NEW BENCHMARKS WILL BE CREATED WHEN PANAMA COMES OUT OF PREVIEW**
|
**BENCHMARKS FOR COFFI AND DTYPE-NEXT ARE BASED ON AN OLD VERSION. NEW BENCHMARKS WILL BE CREATED SOON.**
|
||||||
|
|
||||||
An additional consideration when thinking about alternatives is the performance
|
An additional consideration when thinking about alternatives is the performance
|
||||||
of each available option. It's an established fact that JNA (used by all three
|
of each available option. It's an established fact that JNA (used by all three
|
||||||
|
|
@ -1132,24 +1141,6 @@ These features are planned for future releases.
|
||||||
- Mapped memory
|
- Mapped memory
|
||||||
- Helper macros for custom serde implementations for composite data types
|
- Helper macros for custom serde implementations for composite data types
|
||||||
|
|
||||||
### Future JDKs
|
|
||||||
The purpose of coffi is to provide a wrapper for published versions of Project
|
|
||||||
Panama, starting with JDK 17. As new JDKs are released, coffi will be ported to
|
|
||||||
the newer versions of Panama. Version `0.4.341` is the last version compatible
|
|
||||||
with JDK 17. Version `0.5.357` is the last version compatible with JDK 18.
|
|
||||||
Version `0.6.409` is the latest version compatible with JDK 19. Bugfixes, and
|
|
||||||
potential backports of newer coffi features may be found on the `jdk17-lts`
|
|
||||||
branch. Development of new features and fixes as well as support for new Panama
|
|
||||||
idioms and features will continue with focus only on the latest JDK. If a
|
|
||||||
particular feature is not specific to the newer JDK, PRs backporting it to
|
|
||||||
versions of coffi supporting Java 17 will likely be accepted.
|
|
||||||
|
|
||||||
### 1.0 Release
|
|
||||||
Because the feature that coffi wraps in the JDK is in preview as of JDK 19,
|
|
||||||
coffi itself will not be released in a 1.0.x version until the feature becomes a
|
|
||||||
core part of the JDK, likely before or during the next LTS release, Java 21, in
|
|
||||||
September 2023.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright © 2023 Joshua Suskalo
|
Copyright © 2023 Joshua Suskalo
|
||||||
|
|
|
||||||
26
flake.lock
Normal file
26
flake.lock
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1727634051,
|
||||||
|
"narHash": "sha256-S5kVU7U82LfpEukbn/ihcyNt2+EvG7Z5unsKW9H/yFA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "06cf0e1da4208d3766d898b7fdab6513366d45b9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
33
flake.nix
Normal file
33
flake.nix
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
outputs = { self, nixpkgs }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
overlays = [
|
||||||
|
(final: prev: {
|
||||||
|
clojure = prev.clojure.override { jdk = final.jdk22; };
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.${system}.default = pkgs.mkShell rec {
|
||||||
|
packages = [
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
clojure
|
||||||
|
];
|
||||||
|
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
];
|
||||||
|
|
||||||
|
inputsFrom = with pkgs; [
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -236,20 +236,20 @@
|
||||||
The return type and any arguments that are primitives will not
|
The return type and any arguments that are primitives will not
|
||||||
be (de)serialized except to be cast. If all arguments and return are
|
be (de)serialized except to be cast. If all arguments and return are
|
||||||
primitive, the `downcall` is returned directly. In cases where arguments must
|
primitive, the `downcall` is returned directly. In cases where arguments must
|
||||||
be serialized, a new [[mem/stack-session]] is generated."
|
be serialized, a new [[mem/confined-arena]] is generated."
|
||||||
[downcall arg-types ret-type]
|
[downcall arg-types ret-type]
|
||||||
(let [;; Complexity of types
|
(let [;; Complexity of types
|
||||||
const-args? (or (vector? arg-types) (nil? arg-types))
|
const-args? (or (vector? arg-types) (nil? arg-types))
|
||||||
simple-args? (when const-args?
|
simple-args? (when const-args?
|
||||||
(and (every? mem/primitive? arg-types)
|
(and (every? mem/primitive? arg-types)
|
||||||
;; NOTE(Joshua): Pointer types with serdes (e.g. [::mem/pointer ::mem/int])
|
;; NOTE(Joshua): Pointer types with serdes (e.g. [::mem/pointer ::mem/int])
|
||||||
;; still require a session, making them not qualify as "simple".
|
;; still require an arena, making them not qualify as "simple".
|
||||||
(every? keyword? (filter (comp #{::mem/pointer} mem/primitive-type) arg-types))))
|
(every? keyword? (filter (comp #{::mem/pointer} mem/primitive-type) arg-types))))
|
||||||
const-ret? (s/valid? ::mem/type ret-type)
|
const-ret? (s/valid? ::mem/type ret-type)
|
||||||
primitive-ret? (and const-ret?
|
primitive-ret? (and const-ret?
|
||||||
(or (and (mem/primitive? ret-type)
|
(or (and (mem/primitive? ret-type)
|
||||||
;; NOTE(Joshua): Pointer types with serdes require deserializing the
|
;; NOTE(Joshua): Pointer types with serdes require deserializing the
|
||||||
;; return value, but don't require passing a session to the downcall,
|
;; return value, but don't require passing an arena to the downcall,
|
||||||
;; making them cause the return to not be primitive, but it may still
|
;; making them cause the return to not be primitive, but it may still
|
||||||
;; be "simple".
|
;; be "simple".
|
||||||
(or (keyword? ret-type) (not (#{::mem/pointer} (mem/primitive-type ret-type)))))
|
(or (keyword? ret-type) (not (#{::mem/pointer} (mem/primitive-type ret-type)))))
|
||||||
|
|
@ -265,7 +265,7 @@
|
||||||
~ret-type
|
~ret-type
|
||||||
downcall#)
|
downcall#)
|
||||||
(let [;; All our symbols
|
(let [;; All our symbols
|
||||||
session (gensym "session")
|
arena (gensym "arena")
|
||||||
downcall-sym (gensym "downcall")
|
downcall-sym (gensym "downcall")
|
||||||
args-sym (when-not const-args?
|
args-sym (when-not const-args?
|
||||||
(gensym "args"))
|
(gensym "args"))
|
||||||
|
|
@ -284,7 +284,7 @@
|
||||||
(some->>
|
(some->>
|
||||||
(cond
|
(cond
|
||||||
(not (s/valid? ::mem/type type))
|
(not (s/valid? ::mem/type type))
|
||||||
`(mem/serialize ~sym ~type-sym ~session)
|
`(mem/serialize ~sym ~type-sym ~arena)
|
||||||
|
|
||||||
(and (mem/primitive? type)
|
(and (mem/primitive? type)
|
||||||
(not (#{::mem/pointer} (mem/primitive-type type))))
|
(not (#{::mem/pointer} (mem/primitive-type type))))
|
||||||
|
|
@ -295,11 +295,11 @@
|
||||||
`(or ~sym (MemorySegment/NULL))
|
`(or ~sym (MemorySegment/NULL))
|
||||||
|
|
||||||
(mem/primitive-type type)
|
(mem/primitive-type type)
|
||||||
`(mem/serialize* ~sym ~type-sym ~session)
|
`(mem/serialize* ~sym ~type-sym ~arena)
|
||||||
|
|
||||||
:else
|
:else
|
||||||
`(let [alloc# (mem/alloc-instance ~type-sym)]
|
`(let [alloc# (mem/alloc-instance ~type-sym)]
|
||||||
(mem/serialize-into ~sym ~type-sym alloc# ~session)
|
(mem/serialize-into ~sym ~type-sym alloc# ~arena)
|
||||||
alloc#))
|
alloc#))
|
||||||
(list sym)))
|
(list sym)))
|
||||||
|
|
||||||
|
|
@ -326,7 +326,7 @@
|
||||||
|
|
||||||
:else
|
:else
|
||||||
`(let [~args-sym (map (fn [obj# type#]
|
`(let [~args-sym (map (fn [obj# type#]
|
||||||
(mem/serialize obj# type# ~session))
|
(mem/serialize obj# type# ~arena))
|
||||||
~args-sym ~args-types-sym)]
|
~args-sym ~args-types-sym)]
|
||||||
~expr)))
|
~expr)))
|
||||||
|
|
||||||
|
|
@ -335,7 +335,7 @@
|
||||||
;; taking restargs, and so the downcall must be applied
|
;; taking restargs, and so the downcall must be applied
|
||||||
(-> `(~@(when (symbol? args) [`apply])
|
(-> `(~@(when (symbol? args) [`apply])
|
||||||
~downcall-sym
|
~downcall-sym
|
||||||
~@(when allocator? [`(mem/arena-allocator ~session)])
|
~@(when allocator? [`(mem/arena-allocator ~arena)])
|
||||||
~@(if (symbol? args)
|
~@(if (symbol? args)
|
||||||
[args]
|
[args]
|
||||||
args))
|
args))
|
||||||
|
|
@ -358,12 +358,12 @@
|
||||||
:else
|
:else
|
||||||
(deserialize-segment expr)))
|
(deserialize-segment expr)))
|
||||||
|
|
||||||
wrap-session (fn [expr]
|
wrap-arena (fn [expr]
|
||||||
`(with-open [~session (mem/stack-session)]
|
`(with-open [~arena (mem/confined-arena)]
|
||||||
~expr))
|
~expr))
|
||||||
wrap-fn (fn [call needs-session?]
|
wrap-fn (fn [call needs-arena?]
|
||||||
`(fn [~@(if const-args? arg-syms ['& args-sym])]
|
`(fn [~@(if const-args? arg-syms ['& args-sym])]
|
||||||
~(cond-> call needs-session? wrap-session)))]
|
~(cond-> call needs-arena? wrap-arena)))]
|
||||||
`(let [;; NOTE(Joshua): To ensure all arguments are evaluated once and
|
`(let [;; NOTE(Joshua): To ensure all arguments are evaluated once and
|
||||||
;; in-order, they must be bound here
|
;; in-order, they must be bound here
|
||||||
~downcall-sym ~downcall
|
~downcall-sym ~downcall
|
||||||
|
|
@ -395,15 +395,15 @@
|
||||||
[downcall arg-types ret-type]
|
[downcall arg-types ret-type]
|
||||||
(if (mem/primitive-type ret-type)
|
(if (mem/primitive-type ret-type)
|
||||||
(fn native-fn [& args]
|
(fn native-fn [& args]
|
||||||
(with-open [session (mem/stack-session)]
|
(with-open [arena (mem/confined-arena)]
|
||||||
(mem/deserialize*
|
(mem/deserialize*
|
||||||
(apply downcall (map #(mem/serialize %1 %2 session) args arg-types))
|
(apply downcall (map #(mem/serialize %1 %2 arena) args arg-types))
|
||||||
ret-type)))
|
ret-type)))
|
||||||
(fn native-fn [& args]
|
(fn native-fn [& args]
|
||||||
(with-open [session (mem/stack-session)]
|
(with-open [arena (mem/confined-arena)]
|
||||||
(mem/deserialize-from
|
(mem/deserialize-from
|
||||||
(apply downcall (mem/arena-allocator session)
|
(apply downcall (mem/arena-allocator arena)
|
||||||
(map #(mem/serialize %1 %2 session) args arg-types))
|
(map #(mem/serialize %1 %2 arena) args arg-types))
|
||||||
ret-type)))))
|
ret-type)))))
|
||||||
|
|
||||||
(defn make-serde-varargs-wrapper
|
(defn make-serde-varargs-wrapper
|
||||||
|
|
@ -530,13 +530,13 @@
|
||||||
|
|
||||||
(defn- upcall-serde-wrapper
|
(defn- upcall-serde-wrapper
|
||||||
"Creates a function that wraps `f` which deserializes the arguments and
|
"Creates a function that wraps `f` which deserializes the arguments and
|
||||||
serializes the return type in the [[global-session]]."
|
serializes the return type in the [[global-arena]]."
|
||||||
[f arg-types ret-type]
|
[f arg-types ret-type]
|
||||||
(fn [& args]
|
(fn [& args]
|
||||||
(mem/serialize
|
(mem/serialize
|
||||||
(apply f (map mem/deserialize args arg-types))
|
(apply f (map mem/deserialize args arg-types))
|
||||||
ret-type
|
ret-type
|
||||||
(mem/global-session))))
|
(mem/global-arena))))
|
||||||
|
|
||||||
(defmethod mem/serialize* ::fn
|
(defmethod mem/serialize* ::fn
|
||||||
[f [_fn arg-types ret-type & {:keys [raw-fn?]}] arena]
|
[f [_fn arg-types ret-type & {:keys [raw-fn?]}] arena]
|
||||||
|
|
@ -608,7 +608,7 @@
|
||||||
(mem/serialize-into
|
(mem/serialize-into
|
||||||
newval (.-type static-var)
|
newval (.-type static-var)
|
||||||
(.-seg static-var)
|
(.-seg static-var)
|
||||||
(mem/global-session))
|
(mem/global-arena))
|
||||||
newval)
|
newval)
|
||||||
|
|
||||||
(defn fswap!
|
(defn fswap!
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
(ns coffi.mem
|
(ns coffi.mem
|
||||||
"Functions for managing native allocations, memory sessions, and (de)serialization.
|
"Functions for managing native allocations, memory arenas, and (de)serialization.
|
||||||
|
|
||||||
For any new type to be implemented, three multimethods must be overriden, but
|
For any new type to be implemented, three multimethods must be overriden, but
|
||||||
which three depends on the native representation of the type.
|
which three depends on the native representation of the type.
|
||||||
|
|
@ -34,6 +34,7 @@
|
||||||
ValueLayout$OfFloat
|
ValueLayout$OfFloat
|
||||||
ValueLayout$OfDouble)
|
ValueLayout$OfDouble)
|
||||||
(java.lang.ref Cleaner)
|
(java.lang.ref Cleaner)
|
||||||
|
(java.util.function Consumer)
|
||||||
(java.nio ByteOrder)))
|
(java.nio ByteOrder)))
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
(set! *warn-on-reflection* true)
|
||||||
|
|
@ -49,24 +50,6 @@
|
||||||
(^Arena []
|
(^Arena []
|
||||||
(Arena/ofConfined)))
|
(Arena/ofConfined)))
|
||||||
|
|
||||||
(defn ^:deprecated stack-session
|
|
||||||
"Constructs a new session for use only in this thread.
|
|
||||||
|
|
||||||
The memory allocated within this session is cheap to allocate, like a native
|
|
||||||
stack."
|
|
||||||
(^Arena []
|
|
||||||
(confined-arena))
|
|
||||||
(^Arena [^Cleaner cleaner]
|
|
||||||
(assert false "Stack sessions with associated cleaners have been removed.")))
|
|
||||||
|
|
||||||
(defn ^:deprecated 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."
|
|
||||||
^Arena []
|
|
||||||
(confined-arena))
|
|
||||||
|
|
||||||
(defn shared-arena
|
(defn shared-arena
|
||||||
"Constructs a new shared memory arena.
|
"Constructs a new shared memory arena.
|
||||||
|
|
||||||
|
|
@ -75,24 +58,6 @@
|
||||||
(^Arena []
|
(^Arena []
|
||||||
(Arena/ofShared)))
|
(Arena/ofShared)))
|
||||||
|
|
||||||
(defn ^:deprecated shared-session
|
|
||||||
"Constructs a new shared memory session.
|
|
||||||
|
|
||||||
This session can be shared across threads and memory allocated in it will only
|
|
||||||
be cleaned up once every thread accessing the session closes it."
|
|
||||||
(^Arena []
|
|
||||||
(shared-arena))
|
|
||||||
(^Arena [^Cleaner cleaner]
|
|
||||||
(assert false "Shared sessions with associated cleaners have been removed.")))
|
|
||||||
|
|
||||||
(defn ^:deprecated 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."
|
|
||||||
^Arena []
|
|
||||||
(shared-arena))
|
|
||||||
|
|
||||||
(defn auto-arena
|
(defn auto-arena
|
||||||
"Constructs a new memory arena that is managed by the garbage collector.
|
"Constructs a new memory arena that is managed by the garbage collector.
|
||||||
|
|
||||||
|
|
@ -104,28 +69,6 @@
|
||||||
^Arena []
|
^Arena []
|
||||||
(Arena/ofAuto))
|
(Arena/ofAuto))
|
||||||
|
|
||||||
(defn ^:deprecated connected-session
|
|
||||||
"Constructs a new memory session to reclaim all connected resources at once.
|
|
||||||
|
|
||||||
The session 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 session cannot be closed, and therefore should not be created in
|
|
||||||
a [[with-open]] clause."
|
|
||||||
^Arena []
|
|
||||||
(auto-arena))
|
|
||||||
|
|
||||||
(defn ^:deprecated 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."
|
|
||||||
^Arena []
|
|
||||||
(auto-arena))
|
|
||||||
|
|
||||||
(defn global-arena
|
(defn global-arena
|
||||||
"Constructs the global arena, which will never reclaim its resources.
|
"Constructs the global arena, which will never reclaim its resources.
|
||||||
|
|
||||||
|
|
@ -136,64 +79,16 @@
|
||||||
^Arena []
|
^Arena []
|
||||||
(Arena/global))
|
(Arena/global))
|
||||||
|
|
||||||
(defn ^:deprecated global-session
|
|
||||||
"Constructs the global session, which will never reclaim its resources.
|
|
||||||
|
|
||||||
This session 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."
|
|
||||||
^Arena []
|
|
||||||
(global-arena))
|
|
||||||
|
|
||||||
(defn ^:deprecated 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."
|
|
||||||
^Arena []
|
|
||||||
(global-arena))
|
|
||||||
|
|
||||||
(defn arena-allocator
|
(defn arena-allocator
|
||||||
"Constructs a [[SegmentAllocator]] from the given [[Arena]].
|
"Constructs a [[SegmentAllocator]] from the given [[Arena]].
|
||||||
|
|
||||||
This is primarily used when working with unwrapped downcall functions. When a
|
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
|
downcall function returns a non-primitive type, it must be provided with an
|
||||||
allocator."
|
allocator."
|
||||||
^SegmentAllocator [^Arena scope]
|
^SegmentAllocator [^Arena arena]
|
||||||
(reify SegmentAllocator
|
(reify SegmentAllocator
|
||||||
(^MemorySegment allocate [_this ^long byte-size ^long byte-alignment]
|
(^MemorySegment allocate [_this ^long byte-size ^long byte-alignment]
|
||||||
(.allocate scope ^long byte-size ^long byte-alignment))))
|
(.allocate arena ^long byte-size ^long byte-alignment))))
|
||||||
|
|
||||||
(defn ^:deprecated session-allocator
|
|
||||||
"Constructs a segment allocator from the given `session`.
|
|
||||||
|
|
||||||
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 [^Arena session]
|
|
||||||
(arena-allocator session))
|
|
||||||
|
|
||||||
(defn ^:deprecated 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 [^Arena scope]
|
|
||||||
(arena-allocator scope))
|
|
||||||
|
|
||||||
(defn ^:deprecated segment-session
|
|
||||||
"Gets the memory session used to construct the `segment`."
|
|
||||||
^Arena [^MemorySegment segment]
|
|
||||||
(assert false "Segment sessions no longer provide linkes to the arenas that allocated them."))
|
|
||||||
|
|
||||||
(defn segment-scope
|
|
||||||
"Gets the scope associated with the `segment`."
|
|
||||||
^MemorySegment$Scope [segment]
|
|
||||||
(.scope ^MemorySegment segment))
|
|
||||||
|
|
||||||
(defn alloc
|
(defn alloc
|
||||||
"Allocates `size` bytes.
|
"Allocates `size` bytes.
|
||||||
|
|
@ -210,19 +105,6 @@
|
||||||
(^MemorySegment [allocator size alignment]
|
(^MemorySegment [allocator size alignment]
|
||||||
(.allocate ^SegmentAllocator allocator (long size) (long alignment))))
|
(.allocate ^SegmentAllocator allocator (long size) (long alignment))))
|
||||||
|
|
||||||
(defmacro ^:deprecated with-acquired
|
|
||||||
"Acquires one or more `sessions` until the `body` completes.
|
|
||||||
|
|
||||||
This is only necessary to do on shared sessions, however if you are operating
|
|
||||||
on an arbitrary passed session, it is best practice to wrap code that
|
|
||||||
interacts with it wrapped in this."
|
|
||||||
{:style/indent 1}
|
|
||||||
[sessions & body]
|
|
||||||
(assert false "Support was removed for keeping a shared arena open."))
|
|
||||||
(s/fdef with-acquired
|
|
||||||
:args (s/cat :sessions any?
|
|
||||||
:body (s/* any?)))
|
|
||||||
|
|
||||||
(defn address-of
|
(defn address-of
|
||||||
"Gets the address of a given segment.
|
"Gets the address of a given segment.
|
||||||
|
|
||||||
|
|
@ -249,32 +131,34 @@
|
||||||
(^MemorySegment [segment offset size]
|
(^MemorySegment [segment offset size]
|
||||||
(.asSlice ^MemorySegment segment (long offset) (long size))))
|
(.asSlice ^MemorySegment segment (long offset) (long size))))
|
||||||
|
|
||||||
(defn ^:deprecated slice-into
|
(defn reinterpret
|
||||||
"Get a slice into the `segment` starting at the `address`."
|
"Reinterprets the `segment` as having the passed `size`.
|
||||||
(^MemorySegment [address segment]
|
|
||||||
(.asSlice ^MemorySegment segment (address-of address)))
|
|
||||||
(^MemorySegment [address segment size]
|
|
||||||
(.asSlice ^MemorySegment segment (address-of address) (long size))))
|
|
||||||
|
|
||||||
(defn ^:deprecated with-offset
|
If `arena` is passed, the scope of the `segment` is associated with the arena,
|
||||||
"Get a new address `offset` from the old `address`."
|
as well as its access constraints. If `cleanup` is passed, it will be a
|
||||||
^MemorySegment [address offset]
|
1-argument function of a fresh memory segment backed by the same memory as the
|
||||||
(slice address offset))
|
returned segment which should perform any required cleanup operations. It will
|
||||||
|
be called when the `arena` is closed."
|
||||||
;; TODO(Joshua): Figure out if this can be replicated with [[Cleaner]]
|
(^MemorySegment [^MemorySegment segment size]
|
||||||
#_
|
(.reinterpret segment (long size) (auto-arena) nil))
|
||||||
(defn add-close-action!
|
(^MemorySegment [^MemorySegment segment size ^Arena arena]
|
||||||
"Adds a 0-arity function to be run when the `session` closes."
|
(.reinterpret segment (long size) arena nil))
|
||||||
[^MemorySession session ^Runnable action]
|
(^MemorySegment [^MemorySegment segment size ^Arena arena cleanup]
|
||||||
(.addCloseAction session action)
|
(.reinterpret segment (long size) arena
|
||||||
nil)
|
(reify Consumer
|
||||||
|
(accept [_this segment]
|
||||||
|
(cleanup segment))))))
|
||||||
|
|
||||||
(defn as-segment
|
(defn as-segment
|
||||||
"Dereferences an `address` into a memory segment associated with the `session`."
|
"Dereferences an `address` into a memory segment associated with the `arena` (default global)."
|
||||||
(^MemorySegment [^MemorySegment address size]
|
(^MemorySegment [^long address]
|
||||||
(.reinterpret (MemorySegment/ofAddress address) (long size) (connected-session) nil))
|
(MemorySegment/ofAddress address))
|
||||||
(^MemorySegment [^MemorySegment address size session]
|
(^MemorySegment [^long address size]
|
||||||
(.reinterpret (MemorySegment/ofAddress address) (long size) session nil)))
|
(reinterpret (MemorySegment/ofAddress address) size))
|
||||||
|
(^MemorySegment [^long address size ^Arena arena]
|
||||||
|
(reinterpret (MemorySegment/ofAddress address) (long size) arena nil))
|
||||||
|
(^MemorySegment [^long address size ^Arena arena cleanup]
|
||||||
|
(reinterpret (MemorySegment/ofAddress address) (long size) arena cleanup)))
|
||||||
|
|
||||||
(defn copy-segment
|
(defn copy-segment
|
||||||
"Copies the content to `dest` from `src`.
|
"Copies the content to `dest` from `src`.
|
||||||
|
|
@ -285,9 +169,9 @@
|
||||||
|
|
||||||
(defn clone-segment
|
(defn clone-segment
|
||||||
"Clones the content of `segment` into a new segment of the same size."
|
"Clones the content of `segment` into a new segment of the same size."
|
||||||
(^MemorySegment [segment] (clone-segment segment (connected-session)))
|
(^MemorySegment [segment] (clone-segment segment (auto-arena)))
|
||||||
(^MemorySegment [^MemorySegment segment session]
|
(^MemorySegment [^MemorySegment segment ^Arena arena]
|
||||||
(copy-segment ^MemorySegment (alloc (.byteSize segment) session) segment)))
|
(copy-segment ^MemorySegment (alloc (.byteSize segment) arena) segment)))
|
||||||
|
|
||||||
(defn slice-segments
|
(defn slice-segments
|
||||||
"Constructs a lazy seq of `size`-length memory segments, sliced from `segment`."
|
"Constructs a lazy seq of `size`-length memory segments, sliced from `segment`."
|
||||||
|
|
@ -938,67 +822,67 @@
|
||||||
"Constructs a serialized version of the `obj` and returns it.
|
"Constructs a serialized version of the `obj` and returns it.
|
||||||
|
|
||||||
Any new allocations made during the serialization should be tied to the given
|
Any new allocations made during the serialization should be tied to the given
|
||||||
`session`, except in extenuating circumstances.
|
`arena`, except in extenuating circumstances.
|
||||||
|
|
||||||
This method should only be implemented for types that serialize to primitives."
|
This method should only be implemented for types that serialize to primitives."
|
||||||
(fn
|
(fn
|
||||||
#_{:clj-kondo/ignore [:unused-binding]}
|
#_{:clj-kondo/ignore [:unused-binding]}
|
||||||
[obj type session]
|
[obj type arena]
|
||||||
(type-dispatch type)))
|
(type-dispatch type)))
|
||||||
|
|
||||||
(defmethod serialize* :default
|
(defmethod serialize* :default
|
||||||
[obj type _session]
|
[obj type _arena]
|
||||||
(throw (ex-info "Attempted to serialize a non-primitive type with primitive methods"
|
(throw (ex-info "Attempted to serialize a non-primitive type with primitive methods"
|
||||||
{:type type
|
{:type type
|
||||||
:object obj})))
|
:object obj})))
|
||||||
|
|
||||||
(defmethod serialize* ::byte
|
(defmethod serialize* ::byte
|
||||||
[obj _type _session]
|
[obj _type _arena]
|
||||||
(byte obj))
|
(byte obj))
|
||||||
|
|
||||||
(defmethod serialize* ::short
|
(defmethod serialize* ::short
|
||||||
[obj _type _session]
|
[obj _type _arena]
|
||||||
(short obj))
|
(short obj))
|
||||||
|
|
||||||
(defmethod serialize* ::int
|
(defmethod serialize* ::int
|
||||||
[obj _type _session]
|
[obj _type _arena]
|
||||||
(int obj))
|
(int obj))
|
||||||
|
|
||||||
(defmethod serialize* ::long
|
(defmethod serialize* ::long
|
||||||
[obj _type _session]
|
[obj _type _arena]
|
||||||
(long obj))
|
(long obj))
|
||||||
|
|
||||||
(defmethod serialize* ::char
|
(defmethod serialize* ::char
|
||||||
[obj _type _session]
|
[obj _type _arena]
|
||||||
(char obj))
|
(char obj))
|
||||||
|
|
||||||
(defmethod serialize* ::float
|
(defmethod serialize* ::float
|
||||||
[obj _type _session]
|
[obj _type _arena]
|
||||||
(float obj))
|
(float obj))
|
||||||
|
|
||||||
(defmethod serialize* ::double
|
(defmethod serialize* ::double
|
||||||
[obj _type _session]
|
[obj _type _arena]
|
||||||
(double obj))
|
(double obj))
|
||||||
|
|
||||||
(defmethod serialize* ::pointer
|
(defmethod serialize* ::pointer
|
||||||
[obj type session]
|
[obj type arena]
|
||||||
(if-not (null? obj)
|
(if-not (null? obj)
|
||||||
(if (sequential? type)
|
(if (sequential? type)
|
||||||
(let [segment (alloc-instance (second type) session)]
|
(let [segment (alloc-instance (second type) arena)]
|
||||||
(serialize-into obj (second type) segment session)
|
(serialize-into obj (second type) segment arena)
|
||||||
(address-of segment))
|
(address-of segment))
|
||||||
obj)
|
obj)
|
||||||
(MemorySegment/NULL)))
|
(MemorySegment/NULL)))
|
||||||
|
|
||||||
(defmethod serialize* ::void
|
(defmethod serialize* ::void
|
||||||
[_obj _type _session]
|
[_obj _type _arena]
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defmulti serialize-into
|
(defmulti serialize-into
|
||||||
"Writes a serialized version of the `obj` to the given `segment`.
|
"Writes a serialized version of the `obj` to the given `segment`.
|
||||||
|
|
||||||
Any new allocations made during the serialization should be tied to the given
|
Any new allocations made during the serialization should be tied to the given
|
||||||
`session`, except in extenuating circumstances.
|
`arena`, except in extenuating circumstances.
|
||||||
|
|
||||||
This method should be implemented for any type which does not
|
This method should be implemented for any type which does not
|
||||||
override [[c-layout]].
|
override [[c-layout]].
|
||||||
|
|
@ -1007,61 +891,61 @@
|
||||||
the result value into the `segment`."
|
the result value into the `segment`."
|
||||||
(fn
|
(fn
|
||||||
#_{:clj-kondo/ignore [:unused-binding]}
|
#_{:clj-kondo/ignore [:unused-binding]}
|
||||||
[obj type segment session]
|
[obj type segment arena]
|
||||||
(type-dispatch type)))
|
(type-dispatch type)))
|
||||||
|
|
||||||
(defmethod serialize-into :default
|
(defmethod serialize-into :default
|
||||||
[obj type segment session]
|
[obj type segment arena]
|
||||||
(if-some [prim-layout (primitive-type type)]
|
(if-some [prim-layout (primitive-type type)]
|
||||||
(serialize-into (serialize* obj type session) prim-layout segment session)
|
(serialize-into (serialize* obj type arena) prim-layout segment arena)
|
||||||
(throw (ex-info "Attempted to serialize an object to a type that has not been overridden"
|
(throw (ex-info "Attempted to serialize an object to a type that has not been overridden"
|
||||||
{:type type
|
{:type type
|
||||||
:object obj}))))
|
:object obj}))))
|
||||||
|
|
||||||
(defmethod serialize-into ::byte
|
(defmethod serialize-into ::byte
|
||||||
[obj _type segment _session]
|
[obj _type segment _arena]
|
||||||
(write-byte segment (byte obj)))
|
(write-byte segment (byte obj)))
|
||||||
|
|
||||||
(defmethod serialize-into ::short
|
(defmethod serialize-into ::short
|
||||||
[obj type segment _session]
|
[obj type segment _arena]
|
||||||
(if (sequential? type)
|
(if (sequential? type)
|
||||||
(write-short segment 0 (second type) (short obj))
|
(write-short segment 0 (second type) (short obj))
|
||||||
(write-short segment (short obj))))
|
(write-short segment (short obj))))
|
||||||
|
|
||||||
(defmethod serialize-into ::int
|
(defmethod serialize-into ::int
|
||||||
[obj type segment _session]
|
[obj type segment _arena]
|
||||||
(if (sequential? type)
|
(if (sequential? type)
|
||||||
(write-int segment 0 (second type) (int obj))
|
(write-int segment 0 (second type) (int obj))
|
||||||
(write-int segment (int obj))))
|
(write-int segment (int obj))))
|
||||||
|
|
||||||
(defmethod serialize-into ::long
|
(defmethod serialize-into ::long
|
||||||
[obj type segment _session]
|
[obj type segment _arena]
|
||||||
(if (sequential? type)
|
(if (sequential? type)
|
||||||
(write-long segment 0 (second type) (long obj))
|
(write-long segment 0 (second type) (long obj))
|
||||||
(write-long segment (long obj))))
|
(write-long segment (long obj))))
|
||||||
|
|
||||||
(defmethod serialize-into ::char
|
(defmethod serialize-into ::char
|
||||||
[obj _type segment _session]
|
[obj _type segment _arena]
|
||||||
(write-char segment (char obj)))
|
(write-char segment (char obj)))
|
||||||
|
|
||||||
(defmethod serialize-into ::float
|
(defmethod serialize-into ::float
|
||||||
[obj type segment _session]
|
[obj type segment _arena]
|
||||||
(if (sequential? type)
|
(if (sequential? type)
|
||||||
(write-float segment 0 (second type) (float obj))
|
(write-float segment 0 (second type) (float obj))
|
||||||
(write-float segment (float obj))))
|
(write-float segment (float obj))))
|
||||||
|
|
||||||
(defmethod serialize-into ::double
|
(defmethod serialize-into ::double
|
||||||
[obj type segment _session]
|
[obj type segment _arena]
|
||||||
(if (sequential? type)
|
(if (sequential? type)
|
||||||
(write-double segment 0 (second type) (double obj))
|
(write-double segment 0 (second type) (double obj))
|
||||||
(write-double segment (double obj))))
|
(write-double segment (double obj))))
|
||||||
|
|
||||||
(defmethod serialize-into ::pointer
|
(defmethod serialize-into ::pointer
|
||||||
[obj type segment session]
|
[obj type segment arena]
|
||||||
(write-address
|
(write-address
|
||||||
segment
|
segment
|
||||||
(cond-> obj
|
(cond-> obj
|
||||||
(sequential? type) (serialize* type session))))
|
(sequential? type) (serialize* type arena))))
|
||||||
|
|
||||||
(defn serialize
|
(defn serialize
|
||||||
"Serializes an arbitrary type.
|
"Serializes an arbitrary type.
|
||||||
|
|
@ -1069,12 +953,12 @@
|
||||||
For types which have a primitive representation, this serializes into that
|
For types which have a primitive representation, this serializes into that
|
||||||
representation. For types which do not, it allocates a new segment and
|
representation. For types which do not, it allocates a new segment and
|
||||||
serializes into that."
|
serializes into that."
|
||||||
([obj type] (serialize obj type (connected-session)))
|
([obj type] (serialize obj type (auto-arena)))
|
||||||
([obj type session]
|
([obj type arena]
|
||||||
(if (primitive-type type)
|
(if (primitive-type type)
|
||||||
(serialize* obj type session)
|
(serialize* obj type arena)
|
||||||
(let [segment (alloc-instance type session)]
|
(let [segment (alloc-instance type arena)]
|
||||||
(serialize-into obj type segment session)
|
(serialize-into obj type segment arena)
|
||||||
segment))))
|
segment))))
|
||||||
|
|
||||||
(declare deserialize deserialize*)
|
(declare deserialize deserialize*)
|
||||||
|
|
@ -1227,7 +1111,7 @@
|
||||||
(c-layout type))
|
(c-layout type))
|
||||||
|
|
||||||
(defmethod serialize-into ::raw
|
(defmethod serialize-into ::raw
|
||||||
[obj _type segment _session]
|
[obj _type segment _arena]
|
||||||
(if (instance? MemorySegment obj)
|
(if (instance? MemorySegment obj)
|
||||||
(copy-segment segment obj)
|
(copy-segment segment obj)
|
||||||
obj))
|
obj))
|
||||||
|
|
@ -1245,9 +1129,9 @@
|
||||||
::pointer)
|
::pointer)
|
||||||
|
|
||||||
(defmethod serialize* ::c-string
|
(defmethod serialize* ::c-string
|
||||||
[obj _type ^Arena session]
|
[obj _type ^Arena arena]
|
||||||
(if obj
|
(if obj
|
||||||
(.allocateFrom session ^String obj)
|
(.allocateFrom arena ^String obj)
|
||||||
(MemorySegment/NULL)))
|
(MemorySegment/NULL)))
|
||||||
|
|
||||||
(defmethod deserialize* ::c-string
|
(defmethod deserialize* ::c-string
|
||||||
|
|
@ -1264,7 +1148,7 @@
|
||||||
(into-array MemoryLayout items))))
|
(into-array MemoryLayout items))))
|
||||||
|
|
||||||
(defmethod serialize-into ::union
|
(defmethod serialize-into ::union
|
||||||
[obj [_union _types & {:keys [dispatch extract]} :as type] segment session]
|
[obj [_union _types & {:keys [dispatch extract]} :as type] segment arena]
|
||||||
(when-not dispatch
|
(when-not dispatch
|
||||||
(throw (ex-info "Attempted to serialize a union with no dispatch function"
|
(throw (ex-info "Attempted to serialize a union with no dispatch function"
|
||||||
{:type type
|
{:type type
|
||||||
|
|
@ -1276,7 +1160,7 @@
|
||||||
obj)
|
obj)
|
||||||
type
|
type
|
||||||
segment
|
segment
|
||||||
session)))
|
arena)))
|
||||||
|
|
||||||
(defmethod deserialize-from ::union
|
(defmethod deserialize-from ::union
|
||||||
[segment type]
|
[segment type]
|
||||||
|
|
@ -1293,7 +1177,7 @@
|
||||||
(into-array MemoryLayout fields))))
|
(into-array MemoryLayout fields))))
|
||||||
|
|
||||||
(defmethod serialize-into ::struct
|
(defmethod serialize-into ::struct
|
||||||
[obj [_struct fields] segment session]
|
[obj [_struct fields] segment arena]
|
||||||
(loop [offset 0
|
(loop [offset 0
|
||||||
fields fields]
|
fields fields]
|
||||||
(when (seq fields)
|
(when (seq fields)
|
||||||
|
|
@ -1301,7 +1185,7 @@
|
||||||
size (size-of type)]
|
size (size-of type)]
|
||||||
(serialize-into
|
(serialize-into
|
||||||
(get obj field) type
|
(get obj field) type
|
||||||
(slice segment offset size) session)
|
(slice segment offset size) arena)
|
||||||
(recur (long (+ offset size)) (rest fields))))))
|
(recur (long (+ offset size)) (rest fields))))))
|
||||||
|
|
||||||
(defmethod deserialize-from ::struct
|
(defmethod deserialize-from ::struct
|
||||||
|
|
@ -1327,7 +1211,7 @@
|
||||||
(MemoryLayout/paddingLayout size))
|
(MemoryLayout/paddingLayout size))
|
||||||
|
|
||||||
(defmethod serialize-into ::padding
|
(defmethod serialize-into ::padding
|
||||||
[_obj [_padding _size] _segment _session]
|
[_obj [_padding _size] _segment _arena]
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defmethod deserialize-from ::padding
|
(defmethod deserialize-from ::padding
|
||||||
|
|
@ -1343,9 +1227,9 @@
|
||||||
(c-layout type)))
|
(c-layout type)))
|
||||||
|
|
||||||
(defmethod serialize-into ::array
|
(defmethod serialize-into ::array
|
||||||
[obj [_array type count] segment session]
|
[obj [_array type count] segment arena]
|
||||||
(dorun
|
(dorun
|
||||||
(map #(serialize-into %1 type %2 session)
|
(map #(serialize-into %1 type %2 arena)
|
||||||
obj
|
obj
|
||||||
(slice-segments (slice segment 0 (* count (size-of type)))
|
(slice-segments (slice segment 0 (* count (size-of type)))
|
||||||
(size-of type)))))
|
(size-of type)))))
|
||||||
|
|
@ -1390,10 +1274,10 @@
|
||||||
variants))))
|
variants))))
|
||||||
|
|
||||||
(defmethod serialize* ::enum
|
(defmethod serialize* ::enum
|
||||||
[obj [_enum variants & {:keys [repr]}] session]
|
[obj [_enum variants & {:keys [repr]}] arena]
|
||||||
(serialize* ((enum-variants-map variants) obj)
|
(serialize* ((enum-variants-map variants) obj)
|
||||||
(or repr ::int)
|
(or repr ::int)
|
||||||
session))
|
arena))
|
||||||
|
|
||||||
(defmethod deserialize* ::enum
|
(defmethod deserialize* ::enum
|
||||||
[obj [_enum variants & {:keys [_repr]}]]
|
[obj [_enum variants & {:keys [_repr]}]]
|
||||||
|
|
@ -1408,9 +1292,9 @@
|
||||||
::int))
|
::int))
|
||||||
|
|
||||||
(defmethod serialize* ::flagset
|
(defmethod serialize* ::flagset
|
||||||
[obj [_flagset bits & {:keys [repr]}] session]
|
[obj [_flagset bits & {:keys [repr]}] arena]
|
||||||
(let [bits-map (enum-variants-map bits)]
|
(let [bits-map (enum-variants-map bits)]
|
||||||
(reduce #(bit-set %1 (get bits-map %2)) (serialize* 0 (or repr ::int) session) obj)))
|
(reduce #(bit-set %1 (get bits-map %2)) (serialize* 0 (or repr ::int) arena) obj)))
|
||||||
|
|
||||||
(defmethod deserialize* ::flagset
|
(defmethod deserialize* ::flagset
|
||||||
[obj [_flagset bits & {:keys [repr]}]]
|
[obj [_flagset bits & {:keys [repr]}]]
|
||||||
|
|
@ -1442,8 +1326,8 @@
|
||||||
[_type#]
|
[_type#]
|
||||||
(primitive-type aliased#))
|
(primitive-type aliased#))
|
||||||
(defmethod serialize* ~new-type
|
(defmethod serialize* ~new-type
|
||||||
[obj# _type# session#]
|
[obj# _type# arena#]
|
||||||
(serialize* obj# aliased# session#))
|
(serialize* obj# aliased# arena#))
|
||||||
(defmethod deserialize* ~new-type
|
(defmethod deserialize* ~new-type
|
||||||
[obj# _type#]
|
[obj# _type#]
|
||||||
(deserialize* obj# aliased#)))
|
(deserialize* obj# aliased#)))
|
||||||
|
|
@ -1452,8 +1336,8 @@
|
||||||
[_type#]
|
[_type#]
|
||||||
(c-layout aliased#))
|
(c-layout aliased#))
|
||||||
(defmethod serialize-into ~new-type
|
(defmethod serialize-into ~new-type
|
||||||
[obj# _type# segment# session#]
|
[obj# _type# segment# arena#]
|
||||||
(serialize-into obj# aliased# segment# session#))
|
(serialize-into obj# aliased# segment# arena#))
|
||||||
(defmethod deserialize-from ~new-type
|
(defmethod deserialize-from ~new-type
|
||||||
[segment# _type#]
|
[segment# _type#]
|
||||||
(deserialize-from segment# aliased#)))))
|
(deserialize-from segment# aliased#)))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue