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 ### Added
- New `coffi.mem/null` var for implementing custom types - 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 ### Fixed
- Usage of deprecated `(Class/STATIC_FIELD)` access pattern - Usage of deprecated `(Class/STATIC_FIELD)` access pattern

View file

@ -130,53 +130,70 @@
[:invokevirtual (prim-classes prim-type) (unbox-fn-for-type prim-type) [prim]]] [:invokevirtual (prim-classes prim-type) (unbox-fn-for-type prim-type) [prim]]]
[])))) []))))
(defn- downcall-class (defn- downcall-class-ctor*
"Class definition for an implementation of [[IFn]] which calls a closed over "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." method handle without reflection, unboxing primitives when needed."
[args ret] [args ret]
{:flags #{:public :final} (let [klass (insn/define
:version 8 {:flags #{:public :final}
:super clojure.lang.AFunction :version 8
:fields [{:name "downcall_handle" :super clojure.lang.AFunction
:type MethodHandle :fields [{:name "downcall_handle"
:flags #{:final}}] :type MethodHandle
:methods [{:name :init :flags #{:final}}]
:flags #{:public} :methods [{:name :init
:desc [MethodHandle :void] :flags #{:public}
:emit [[:aload 0] :desc [MethodHandle :void]
[:dup] :emit [[:aload 0]
[:invokespecial :super :init [:void]] [:dup]
[:aload 1] [:invokespecial :super :init [:void]]
[:putfield :this "downcall_handle" MethodHandle] [:aload 1]
[:return]]} [:putfield :this "downcall_handle" MethodHandle]
{:name :invoke [:return]]}
:flags #{:public} {:name :invoke
:desc (repeat (cond-> (inc (count args)) :flags #{:public}
(not (mem/primitive-type ret)) inc) :desc (repeat (cond-> (inc (count args))
Object) (not (mem/primitive-type ret)) inc)
:emit [[:aload 0] Object)
[:getfield :this "downcall_handle" MethodHandle] :emit [[:aload 0]
(when-not (mem/primitive-type ret) [:getfield :this "downcall_handle" MethodHandle]
[[:aload 1] (when-not (mem/primitive-type ret)
[:checkcast SegmentAllocator]]) [[:aload 1]
(map-indexed [:checkcast SegmentAllocator]])
(fn [idx arg] (map-indexed
[[:aload (cond-> (inc idx) (fn [idx arg]
(not (mem/primitive-type ret)) inc)] [[:aload (cond-> (inc idx)
(to-prim-asm arg)]) (not (mem/primitive-type ret)) inc)]
args) (to-prim-asm arg)])
[:invokevirtual MethodHandle "invokeExact" args)
(cond->> [:invokevirtual MethodHandle "invokeExact"
(conj (mapv insn-layout args) (cond->>
(insn-layout ret)) (conj (mapv insn-layout args)
(not (mem/primitive-type ret)) (cons SegmentAllocator))] (insn-layout ret))
(to-object-asm ret) (not (mem/primitive-type ret)) (cons SegmentAllocator))]
[:areturn]]}]}) (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 (defn- downcall-fn
"Creates a function to call `handle` without reflection." "Creates a function to call `handle` without reflection."
[handle args ret] [handle args ret]
(insn/new-instance (downcall-class args ret) ^MethodHandle handle)) ((downcall-class-ctor args ret) ^MethodHandle handle))
(defn ensure-symbol (defn ensure-symbol
"Returns the argument if it is a [[MemorySegment]], otherwise "Returns the argument if it is a [[MemorySegment]], otherwise
@ -462,50 +479,69 @@
"Set of primitive types which require 2 indices in the constant pool." "Set of primitive types which require 2 indices in the constant pool."
#{::mem/double ::mem/long}) #{::mem/double ::mem/long})
(defn- upcall-class (defn- upcall-class-ctor*
"Constructs a class definition for a class with a single method, `upcall`, which "Returns a function to construct an upcall class for the given `arg-types` and `ret-types`.
boxes any primitives passed to it and calls a closed over [[IFn]]."
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] [arg-types ret-type]
{:flags #{:public :final} (let [klass (insn/define
:version 8 {:flags #{:public :final}
:fields [{:name "upcall_ifn" :version 8
:type IFn :fields [{:name "upcall_ifn"
:flags #{:final}}] :type IFn
:methods [{:name :init :flags #{:final}}]
:flags #{:public} :methods [{:name :init
:desc [IFn :void] :flags #{:public}
:emit [[:aload 0] :desc [IFn :void]
[:dup] :emit [[:aload 0]
[:invokespecial :super :init [:void]] [:dup]
[:aload 1] [:invokespecial :super :init [:void]]
[:putfield :this "upcall_ifn" IFn] [:aload 1]
[:return]]} [:putfield :this "upcall_ifn" IFn]
{:name :upcall [:return]]}
:flags #{:public} {:name :upcall
:desc (conj (mapv insn-layout arg-types) :flags #{:public}
(insn-layout ret-type)) :desc (conj (mapv insn-layout arg-types)
:emit [[:aload 0] (insn-layout ret-type))
[:getfield :this "upcall_ifn" IFn] :emit [[:aload 0]
(loop [types arg-types [:getfield :this "upcall_ifn" IFn]
acc [] (loop [types arg-types
idx 1] acc []
(if (seq types) idx 1]
(let [prim (mem/primitive-type (first types))] (if (seq types)
(recur (rest types) (let [prim (mem/primitive-type (first types))]
(conj acc [[(load-instructions prim :aload) idx] (recur (rest types)
(to-object-asm (first types))]) (conj acc [[(load-instructions prim :aload) idx]
(cond-> (inc idx) (to-object-asm (first types))])
(double-sized? prim) (cond-> (inc idx)
inc))) (double-sized? prim)
acc)) inc)))
[:invokeinterface IFn "invoke" (repeat (inc (count arg-types)) Object)] acc))
(to-prim-asm ret-type) [:invokeinterface IFn "invoke" (repeat (inc (count arg-types)) Object)]
[(return-for-type ret-type :areturn)]]}]}) (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 (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] [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 (defn- method-type
"Gets the [[MethodType]] for a set of `args` and `ret` types." "Gets the [[MethodType]] for a set of `args` and `ret` types."