WIP: Update documentation for arenas instead of sessions

This is a part of the push for completing coffi 1.0 for a release tested
against JDK 22 and newer.
This commit is contained in:
Joshua Suskalo 2024-09-30 12:17:04 -04:00
parent 7da20f74cf
commit 2073f15767
No known key found for this signature in database
GPG key ID: 9B6BA586EFF1B9F0
3 changed files with 172 additions and 285 deletions

135
README.md
View file

@ -1,15 +1,15 @@
# coffi # coffi
[![Clojars Project](https://img.shields.io/clojars/v/org.suskalo/coffi.svg)](https://clojars.org/org.suskalo/coffi) [![Clojars Project](https://img.shields.io/clojars/v/org.suskalo/coffi.svg)](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:

View file

@ -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!

View file

@ -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#)))))