Implement memoization of upcall and downcall classes

This commit is contained in:
Joshua Suskalo 2024-10-03 10:55:54 -04:00
parent bd7216a06e
commit cce6c823f6
No known key found for this signature in database
GPG key ID: 9B6BA586EFF1B9F0
2 changed files with 120 additions and 81 deletions

View file

@ -5,6 +5,9 @@ All notable changes to this project will be documented in this file. This change
### Added
- New `coffi.mem/null` var for implementing custom types
### Performance
- Upcall and downcall classes have been changed to be memoized, meaning ASM is no longer invoked every time a function is serialized, which should drastically improve performance where functions are serialized in a hot loop
### Fixed
- Usage of deprecated `(Class/STATIC_FIELD)` access pattern

View file

@ -130,10 +130,13 @@
[:invokevirtual (prim-classes prim-type) (unbox-fn-for-type prim-type) [prim]]]
[]))))
(defn- downcall-class
"Class definition for an implementation of [[IFn]] which calls a closed over
(defn- downcall-class-ctor*
"Returns a function to construct a downcall class for the given `args` and `ret` types.
A downcall class is an implementation of [[IFn]] which calls a closed over
method handle without reflection, unboxing primitives when needed."
[args ret]
(let [klass (insn/define
{:flags #{:public :final}
:version 8
:super clojure.lang.AFunction
@ -172,11 +175,25 @@
(not (mem/primitive-type ret)) (cons SegmentAllocator))]
(to-object-asm ret)
[:areturn]]}]})
ctor (.getConstructor klass
(doto ^"[Ljava.lang.Class;" (make-array Class 1)
(aset 0 MethodHandle)))]
(fn [^MethodHandle h]
(.newInstance ctor
(doto (object-array 1)
(aset 0 h))))))
(def ^:private downcall-class-ctor
"Returns a function to construct a downcall class for the given memoized `args` and `ret` types.
A downcall class is an implementation of [[IFn]] which calls a closed over
method handle without reflection, unboxing primitives when needed."
(memoize downcall-class-ctor*))
(defn- downcall-fn
"Creates a function to call `handle` without reflection."
[handle args ret]
(insn/new-instance (downcall-class args ret) ^MethodHandle handle))
((downcall-class-ctor args ret) ^MethodHandle handle))
(defn ensure-symbol
"Returns the argument if it is a [[MemorySegment]], otherwise
@ -462,10 +479,13 @@
"Set of primitive types which require 2 indices in the constant pool."
#{::mem/double ::mem/long})
(defn- upcall-class
"Constructs a class definition for a class with a single method, `upcall`, which
boxes any primitives passed to it and calls a closed over [[IFn]]."
(defn- upcall-class-ctor*
"Returns a function to construct an upcall class for the given `arg-types` and `ret-types`.
An upcall class is a class with a single method, `upcall`, which boxes any
primitives passed to it and calls a closed over [[IFn]]."
[arg-types ret-type]
(let [klass (insn/define
{:flags #{:public :final}
:version 8
:fields [{:name "upcall_ifn"
@ -501,11 +521,27 @@
[:invokeinterface IFn "invoke" (repeat (inc (count arg-types)) Object)]
(to-prim-asm ret-type)
[(return-for-type ret-type :areturn)]]}]})
ctor (.getConstructor klass
(doto ^"[Ljava.lang.Class;" (make-array Class 1)
(aset 0 IFn)))]
(fn [^IFn f]
(.newInstance ctor
(doto (object-array 1)
(aset 0 f))))))
(def ^:private upcall-class-ctor
"Returns a function to construct an upcall class for the given memoized `arg-types` and `ret-types`.
An upcall class is a class with a single method, `upcall`, which boxes any
primitives passed to it and calls a closed over [[IFn]]."
(memoize upcall-class-ctor*))
(defn- upcall
"Constructs an instance of [[upcall-class]], closing over `f`."
"Constructs an instance of an upcall class, closing over `f`.
See [[upcall-class-ctor]]."
[f arg-types ret-type]
(insn/new-instance (upcall-class arg-types ret-type) ^IFn f))
((upcall-class-ctor arg-types ret-type) ^IFn f))
(defn- method-type
"Gets the [[MethodType]] for a set of `args` and `ret` types."