From c26c8a40c8a30e02e99c26f8fe1bd4c161843357 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 11:40:40 -0500 Subject: [PATCH 01/19] Add todos for optimization --- src/clj/coffi/ffi.clj | 1 + src/clj/coffi/mem.clj | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/clj/coffi/ffi.clj b/src/clj/coffi/ffi.clj index a3bc11c..9c2d015 100644 --- a/src/clj/coffi/ffi.clj +++ b/src/clj/coffi/ffi.clj @@ -231,6 +231,7 @@ (let [args (concat required-args types)] (make-downcall symbol args ret))))) +;; TODO(Joshua): Optimize this to not serialize things if possible (defn make-serde-wrapper "Constructs a wrapper function for the `downcall` which serializes the arguments and deserializes the return value." diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index 7378384..6418b35 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -230,6 +230,7 @@ Returns nil for any type which does not have a primitive representation." type-dispatch) +;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod primitive-type :default [type] (primitive-types type)) @@ -259,6 +260,7 @@ Otherwise, it should return a [[GroupLayout]] for the given type." type-dispatch) +;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod c-layout :default [type] (c-prim-layout (or (primitive-type type) type))) @@ -319,6 +321,7 @@ ::float float ::double double}) +;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod serialize* :default [obj type _scope] (if-let [prim (primitive-type type)] @@ -358,6 +361,7 @@ [obj type segment scope] (type-dispatch type))) +;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod serialize-into :default [obj type segment scope] (if-some [prim-layout (primitive-type type)] @@ -436,6 +440,7 @@ [segment type] (type-dispatch type))) +;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod deserialize-from :default [segment type] (if-some [prim (primitive-type type)] @@ -495,6 +500,7 @@ [obj type] (type-dispatch type))) +;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod deserialize* :default [obj type] (if (primitive-type type) From 7f62e72d6703307bba9ef6517955333ff4311875 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 12:42:53 -0500 Subject: [PATCH 02/19] Add unreleased section to the changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e50d58..31866bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). +## [Unreleased] + ## [0.1.220] - 2021-10-09 ### Fixed - All-primitive method types still used serialization when called from `cfn` @@ -47,6 +49,7 @@ All notable changes to this project will be documented in this file. This change - Support for serializing and deserializing arbitrary Clojure functions - Support for serializing and deserializing arbitrary Clojure data structures +[Unreleased]: https://github.com/IGJoshua/coffi/compare/v0.1.220...HEAD [0.1.220]: https://github.com/IGJoshua/coffi/compare/v0.1.205...v0.1.220 [0.1.205]: https://github.com/IGJoshua/coffi/compare/v0.1.192...v0.1.205 [0.1.192]: https://github.com/IGJoshua/coffi/compare/v0.1.184...v0.1.192 From 8f5f4a2cc4767f56a108b1c0bc11ecb8fd72987b Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 11:55:06 -0500 Subject: [PATCH 03/19] Add multimethod implementations for `primitive-type` This should improve performance --- src/clj/coffi/mem.clj | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index 6418b35..2cf1bd9 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -230,15 +230,50 @@ Returns nil for any type which does not have a primitive representation." type-dispatch) -;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod primitive-type :default - [type] - (primitive-types type)) + [_type] + nil) + +(defmethod primitive-type ::byte + [_type] + ::byte) + +(defmethod primitive-type ::short + [_type] + ::short) + +(defmethod primitive-type ::int + [_type] + ::int) + +(defmethod primitive-type ::long + [_type] + ::long) + +(defmethod primitive-type ::long-long + [_type] + ::long-long) + +(defmethod primitive-type ::char + [_type] + ::char) + +(defmethod primitive-type ::float + [_type] + ::float) + +(defmethod primitive-type ::double + [_type] + ::double) (defmethod primitive-type ::pointer [_type] ::pointer) +(defmethod primitive-type ::void + [_type] + ::void) + (def c-prim-layout "Map of primitive type names to the [[CLinker]] types for a method handle." {::byte CLinker/C_CHAR From 4f6e8d7bac584445fef0356242dc04e657a5af12 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 11:59:57 -0500 Subject: [PATCH 04/19] Move c-layout to multimethod versions --- src/clj/coffi/mem.clj | 59 +++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index 2cf1bd9..7a2813e 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -205,14 +205,6 @@ (map #(slice segment (* % size) size) (range num-segments)))) -(def primitive-types - "A set of keywords representing all the primitive types which may be passed to - or returned from native functions." - #{::byte ::short ::int ::long ::long-long - ::char - ::float ::double - ::pointer ::void}) - (defn- type-dispatch "Gets a type dispatch value from a (potentially composite) type." [type] @@ -274,18 +266,6 @@ [_type] ::void) -(def c-prim-layout - "Map of primitive type names to the [[CLinker]] types for a method handle." - {::byte CLinker/C_CHAR - ::short CLinker/C_SHORT - ::int CLinker/C_INT - ::long CLinker/C_LONG - ::long-long CLinker/C_LONG_LONG - ::char CLinker/C_CHAR - ::float CLinker/C_FLOAT - ::double CLinker/C_DOUBLE - ::pointer CLinker/C_POINTER}) - (defmulti c-layout "Gets the layout object for a given `type`. @@ -295,10 +275,45 @@ Otherwise, it should return a [[GroupLayout]] for the given type." type-dispatch) -;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod c-layout :default [type] - (c-prim-layout (or (primitive-type type) type))) + (c-layout (primitive-type type))) + +(defmethod c-layout ::byte + [_type] + CLinker/C_CHAR) + +(defmethod c-layout ::short + [_type] + CLinker/C_SHORT) + +(defmethod c-layout ::int + [_type] + CLinker/C_INT) + +(defmethod c-layout ::long + [_type] + CLinker/C_LONG) + +(defmethod c-layout ::long-long + [_type] + CLinker/C_LONG_LONG) + +(defmethod c-layout ::char + [_type] + CLinker/C_CHAR) + +(defmethod c-layout ::float + [_type] + CLinker/C_FLOAT) + +(defmethod c-layout ::double + [_type] + CLinker/C_DOUBLE) + +(defmethod c-layout ::pointer + [_type] + CLinker/C_POINTER) (def java-prim-layout "Map of primitive type names to the Java types for a method handle." From 80beace1966b4f1fbaf9d307f1ec8a318b76b57a Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 12:05:19 -0500 Subject: [PATCH 05/19] Move serialize* to multimethod impls --- src/clj/coffi/mem.clj | 53 ++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index 7a2813e..92f334d 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -360,26 +360,43 @@ [obj type scope] (type-dispatch type))) -(def ^:private primitive-cast - "Map from primitive type names to the function to cast it to a primitive." - {::byte byte - ::short short - ::int int - ::long long - ::long-long long - ::char char - ::float float - ::double double}) - -;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod serialize* :default [obj type _scope] - (if-let [prim (primitive-type type)] - (when-not (= ::void prim) - ((primitive-cast prim) obj)) - (throw (ex-info "Attempted to serialize a non-primitive type with primitive methods" - {:type type - :object obj})))) + (throw (ex-info "Attempted to serialize a non-primitive type with primitive methods" + {:type type + :object obj}))) + +(defmethod serialize* ::byte + [obj _type _scope] + (byte obj)) + +(defmethod serialize* ::short + [obj _type _scope] + (short obj)) + +(defmethod serialize* ::int + [obj _type _scope] + (int obj)) + +(defmethod serialize* ::long + [obj _type _scope] + (long obj)) + +(defmethod serialize* ::long-long + [obj _type _scope] + (long obj)) + +(defmethod serialize* ::char + [obj _type _scope] + (char obj)) + +(defmethod serialize* ::float + [obj _type _scope] + (float obj)) + +(defmethod serialize* ::double + [obj _type _scope] + (double obj)) (defmethod serialize* ::pointer [obj type scope] From c43ff5a848d48087fdac35d8fbc48ff23b446fd5 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 12:11:14 -0500 Subject: [PATCH 06/19] remove performance comments on things already split out --- src/clj/coffi/mem.clj | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index 92f334d..f67cf16 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -428,7 +428,6 @@ [obj type segment scope] (type-dispatch type))) -;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod serialize-into :default [obj type segment scope] (if-some [prim-layout (primitive-type type)] @@ -507,14 +506,12 @@ [segment type] (type-dispatch type))) -;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod deserialize-from :default [segment type] (if-some [prim (primitive-type type)] - (with-acquired [(segment-scope segment)] - (-> segment - (deserialize-from prim) - (deserialize* type))) + (-> segment + (deserialize-from prim) + (deserialize* type)) (throw (ex-info "Attempted to deserialize a non-primitive type that has not been overriden" {:type type :segment segment})))) From 53f72bdb4932a4fe0eb99cd6725a429cde1083c0 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 12:11:26 -0500 Subject: [PATCH 07/19] Move to multimethod implementations for deserialize* --- src/clj/coffi/mem.clj | 44 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index f67cf16..d9e4370 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -567,11 +567,41 @@ ;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod deserialize* :default [obj type] - (if (primitive-type type) - obj - (throw (ex-info "Attempted to deserialize a non-primitive type with primitive methods" - {:type type - :segment obj})))) + (throw (ex-info "Attempted to deserialize a non-primitive type with primitive methods" + {:type type + :segment obj}))) + +(defmethod deserialize* ::byte + [obj _type] + obj) + +(defmethod deserialize* ::short + [obj _type] + obj) + +(defmethod deserialize* ::int + [obj _type] + obj) + +(defmethod deserialize* ::long + [obj _type] + obj) + +(defmethod deserialize* ::long-long + [obj _type] + obj) + +(defmethod deserialize* ::char + [obj _type] + obj) + +(defmethod deserialize* ::float + [obj _type] + obj) + +(defmethod deserialize* ::double + [obj _type] + obj) (defmethod deserialize* ::pointer [addr type] @@ -581,6 +611,10 @@ (second type)) addr))) +(defmethod deserialize* ::void + [_obj _type] + nil) + (defn deserialize "Deserializes an arbitrary type. From 814edb7e164d90ccff4809125b12d40cee84fc46 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 12:44:21 -0500 Subject: [PATCH 08/19] Update changelog for multimethod impls --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31866bb..a113059 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). ## [Unreleased] +### Performance +- Added multimethod implementations for primitives in (de)serialization functions, rather than using the default ## [0.1.220] - 2021-10-09 ### Fixed From 15c190572fe30986a38aa0d817dd026de28192e6 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Sun, 10 Oct 2021 13:32:15 -0500 Subject: [PATCH 09/19] Remove todo line --- src/clj/coffi/mem.clj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index d9e4370..888d0cd 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -564,7 +564,6 @@ [obj type] (type-dispatch type))) -;; TODO(Joshua): For performance, turn this into a bunch of specific defmethods (defmethod deserialize* :default [obj type] (throw (ex-info "Attempted to deserialize a non-primitive type with primitive methods" From d3050c34ffc92c45202656a3055884103fc35c65 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Mon, 11 Oct 2021 11:19:13 -0500 Subject: [PATCH 10/19] Move arg and ret types into the cfn call in defcfn This is in preparation for an optimization in cfn with constant arg and ret types. --- src/clj/coffi/ffi.clj | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/clj/coffi/ffi.clj b/src/clj/coffi/ffi.clj index 9c2d015..e6fd9c5 100644 --- a/src/clj/coffi/ffi.clj +++ b/src/clj/coffi/ffi.clj @@ -549,8 +549,6 @@ :style/indent [:defn]} [& args] (let [args (s/conform ::defcfn-args args) - args-types (gensym "args-types") - ret-type (gensym "ret-type") address (gensym "symbol") native-sym (gensym "native") [arity fn-tail] (-> args :wrapper :fn-tail) @@ -562,10 +560,8 @@ :single-arity [fn-tail] :multi-arity fn-tail nil))] - `(let [~args-types ~(:native-arglist args) - ~ret-type ~(:return-type args) - ~address (find-symbol ~(name (:symbol args))) - ~native-sym (cfn ~address ~args-types ~ret-type) + `(let [~address (find-symbol ~(name (:symbol args))) + ~native-sym (cfn ~address ~(:native-arglist args) ~(:return-type args)) fun# ~(if (:wrapper args) `(fn ~(:name args) ~@fn-tail) From bcc6b29e9e17edc303da0fc944e17e11b5be9040 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Mon, 11 Oct 2021 11:21:02 -0500 Subject: [PATCH 11/19] Remove unused import --- src/clj/coffi/ffi.clj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/clj/coffi/ffi.clj b/src/clj/coffi/ffi.clj index e6fd9c5..a94982d 100644 --- a/src/clj/coffi/ffi.clj +++ b/src/clj/coffi/ffi.clj @@ -20,7 +20,6 @@ CLinker FunctionDescriptor MemoryLayout - MemorySegment SegmentAllocator))) ;;; FFI Code loading and function access From 868c843875e1591bc8cdc112891abe9cda048000 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Wed, 13 Oct 2021 09:57:23 -0500 Subject: [PATCH 12/19] Add perdicate for primitive types --- src/clj/coffi/mem.clj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index 888d0cd..6ed30cd 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -212,6 +212,11 @@ (qualified-keyword? type) type (sequential? type) (keyword (first type)))) +(def primitive? + "A set of all primitive types." + #{::byte ::short ::int ::long ::long-long + ::char ::float ::double ::pointer}) + (defmulti primitive-type "Gets the primitive type that is used to pass as an argument for the `type`. From 6f28994526d68c5709cfc7008353bafb613388b9 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Wed, 13 Oct 2021 09:57:36 -0500 Subject: [PATCH 13/19] Add inline function for `make-serde-wrapper` --- src/clj/coffi/ffi.clj | 139 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 129 insertions(+), 10 deletions(-) diff --git a/src/clj/coffi/ffi.clj b/src/clj/coffi/ffi.clj index a94982d..d1cfc1a 100644 --- a/src/clj/coffi/ffi.clj +++ b/src/clj/coffi/ffi.clj @@ -230,20 +230,138 @@ (let [args (concat required-args types)] (make-downcall symbol args ret))))) -;; TODO(Joshua): Optimize this to not serialize things if possible +(def ^:private primitive-cast-sym + "Map from non-pointer primitive types to functions that cast to the appropriate + java primitive." + {::mem/byte `byte + ::mem/short `short + ::mem/int `int + ::mem/long `long + ::mem/long-long `long + ::mem/char `char + ::mem/float `float + ::mem/double `double}) + +(defn- inline-serde-wrapper + "Builds a form that returns a function that calls `downcall` with serdes. + + The return type and any arguments that are primitives will not + be (de)serialized except to be cast. If all arguments and return are + primitive, the `downcall` is returned directly. In cases where arguments must + be serialized, a new [[mem/stack-scope]] is generated." + [downcall arg-types ret-type] + (let [const-ret? (s/valid? ::mem/type ret-type) + primitive-ret? (mem/primitive? ret-type) + scope (gensym "scope")] + (if-not (seqable? arg-types) + (let [args (gensym "args") + serialized-args `(map (fn [arg# type#] (mem/serialize arg# type# ~scope)) ~args ~arg-types) + prim-call `(apply ~downcall ~serialized-args) + non-prim-call `(apply ~downcall (mem/scope-allocator ~scope) ~serialized-args)] + (cond + (and const-ret? + primitive-ret?) + `(fn ~'native-fn + [~'& ~args] + (with-open [~scope (mem/stack-scope)] + ~prim-call)) + + const-ret? + `(fn ~'native-fn + [~'& ~args] + (with-open [~scope (mem/stack-scopee)] + ~(if (mem/primitive-type ret-type) + `(mem/deserialize* ~prim-call ~ret-type) + `(mem/deserialize-from ~non-prim-call ~ret-type)))) + + :else + `(if (mem/primitive-type ~ret-type) + (fn ~'native-fn + [~'& ~args] + (with-open [~scope mem/stack-scope] + (mem/deserialize* ~prim-call ~ret-type))) + (fn ~'native-fn + [~'& ~args] + (with-open [~scope mem/stack-scope] + (mem/deserialize-from ~non-prim-call ~ret-type)))))) + (let [arg-syms (repeatedly (count arg-types) #(gensym "arg")) + serialize-args (map (fn [sym type] + (if (s/valid? ::mem/type type) + (if-not (mem/primitive? type) + (list sym + (if (mem/primitive-type type) + `(mem/serialize* ~sym ~type ~scope) + `(let [alloc# (mem/alloc-instance ~type ~scope)] + (mem/serialize-into ~sym ~type alloc# ~scope) + alloc#))) + (if (primitive-cast-sym type) + (list sym (list (primitive-cast-sym type) sym)) + nil)) + (list sym `(mem/serialize ~sym ~type ~scope)))) + arg-syms arg-types) + wrap-serialize (fn [expr] + `(with-open [~scope (mem/stack-scope)] + (let [~@(mapcat identity serialize-args)] + ~expr))) + native-fn (fn [expr] + `(fn ~'native-fn [~@arg-syms] + ~expr)) + none-to-serialize? (zero? (count (filter some? serialize-args)))] + (cond + (and none-to-serialize? + primitive-ret?) + downcall + + primitive-ret? + (-> (cons downcall arg-syms) + wrap-serialize + native-fn) + + :else + (let [call (cons downcall arg-syms) + prim-call `(mem/deserialize* ~call ~ret-type) + non-prim-call `(mem/deserialize-from ~(list* (first call) + `(mem/scope-allocator ~scope) + (rest call)) + ~ret-type)] + (cond + (and none-to-serialize? + const-ret?) + (native-fn (if (mem/primitive-type ret-type) + prim-call + non-prim-call)) + + none-to-serialize? + `(if (mem/primitive-type ~ret-type) + ~(native-fn prim-call) + ~(native-fn non-prim-call)) + + const-ret? + (native-fn (wrap-serialize + (if (mem/primitive-type ret-type) + prim-call + non-prim-call))) + + :else + `(if (mem/primitive-type ~ret-type) + ~(native-fn (wrap-serialize prim-call)) + ~(native-fn (wrap-serialize non-prim-call)))))))))) + (defn make-serde-wrapper "Constructs a wrapper function for the `downcall` which serializes the arguments and deserializes the return value." + {:inline (fn [downcall arg-types ret-type] + (inline-serde-wrapper downcall arg-types ret-type))} [downcall arg-types ret-type] (if (mem/primitive-type ret-type) (fn native-fn [& args] (with-open [scope (mem/stack-scope)] - (mem/deserialize + (mem/deserialize* (apply downcall (map #(mem/serialize %1 %2 scope) args arg-types)) ret-type))) (fn native-fn [& args] (with-open [scope (mem/stack-scope)] - (mem/deserialize + (mem/deserialize-from (apply downcall (mem/scope-allocator scope) (map #(mem/serialize %1 %2 scope) args arg-types)) ret-type))))) @@ -264,15 +382,15 @@ "Constructs a Clojure function to call the native function referenced by `symbol`. The function returned will serialize any passed arguments into the `args` - types, and deserialize the return to the `ret` type." + types, and deserialize the return to the `ret` type. + + If your `args` and `ret` are constants, then it is more efficient to + call [[make-downcall]] followed by [[make-serde-wrapper]] because the latter + has an inline definition which will result in less overhead from serdes." [symbol args ret] (-> symbol - ensure-address (make-downcall args ret) - (cond-> - (every? #(= % (mem/primitive-type %)) - (cons ret args)) - (make-serde-wrapper args ret)))) + (make-serde-wrapper args ret))) (defn vacfn-factory "Constructs a varargs factory to call the native function referenced by `symbol`. @@ -560,7 +678,8 @@ :multi-arity fn-tail nil))] `(let [~address (find-symbol ~(name (:symbol args))) - ~native-sym (cfn ~address ~(:native-arglist args) ~(:return-type args)) + ~native-sym (-> (make-downcall ~address ~(:native-arglist args) ~(:return-type args)) + (make-serde-wrapper ~(:native-arglist args) ~(:return-type args))) fun# ~(if (:wrapper args) `(fn ~(:name args) ~@fn-tail) From b1133811a0493c1da3d05b273b052912e4c82fda Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Wed, 13 Oct 2021 13:46:03 -0500 Subject: [PATCH 14/19] Ensure there's no double-evaluation of the return types --- src/clj/coffi/ffi.clj | 85 +++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/src/clj/coffi/ffi.clj b/src/clj/coffi/ffi.clj index d1cfc1a..16d6b3a 100644 --- a/src/clj/coffi/ffi.clj +++ b/src/clj/coffi/ffi.clj @@ -255,6 +255,7 @@ scope (gensym "scope")] (if-not (seqable? arg-types) (let [args (gensym "args") + ret (gensym "ret") serialized-args `(map (fn [arg# type#] (mem/serialize arg# type# ~scope)) ~args ~arg-types) prim-call `(apply ~downcall ~serialized-args) non-prim-call `(apply ~downcall (mem/scope-allocator ~scope) ~serialized-args)] @@ -267,24 +268,27 @@ ~prim-call)) const-ret? - `(fn ~'native-fn - [~'& ~args] - (with-open [~scope (mem/stack-scopee)] - ~(if (mem/primitive-type ret-type) - `(mem/deserialize* ~prim-call ~ret-type) - `(mem/deserialize-from ~non-prim-call ~ret-type)))) + `(let [~ret ~ret-type] + (fn ~'native-fn + [~'& ~args] + (with-open [~scope (mem/stack-scopee)] + ~(if (mem/primitive-type ret-type) + `(mem/deserialize* ~prim-call ~ret) + `(mem/deserialize-from ~non-prim-call ~ret))))) :else - `(if (mem/primitive-type ~ret-type) - (fn ~'native-fn - [~'& ~args] - (with-open [~scope mem/stack-scope] - (mem/deserialize* ~prim-call ~ret-type))) - (fn ~'native-fn - [~'& ~args] - (with-open [~scope mem/stack-scope] - (mem/deserialize-from ~non-prim-call ~ret-type)))))) + `(let [~ret ~ret-type] + (if (mem/primitive-type ~ret) + (fn ~'native-fn + [~'& ~args] + (with-open [~scope mem/stack-scope] + (mem/deserialize* ~prim-call ~ret))) + (fn ~'native-fn + [~'& ~args] + (with-open [~scope mem/stack-scope] + (mem/deserialize-from ~non-prim-call ~ret))))))) (let [arg-syms (repeatedly (count arg-types) #(gensym "arg")) + ret (gensym "ret") serialize-args (map (fn [sym type] (if (s/valid? ::mem/type type) (if-not (mem/primitive? type) @@ -318,34 +322,35 @@ native-fn) :else - (let [call (cons downcall arg-syms) - prim-call `(mem/deserialize* ~call ~ret-type) - non-prim-call `(mem/deserialize-from ~(list* (first call) - `(mem/scope-allocator ~scope) - (rest call)) - ~ret-type)] - (cond - (and none-to-serialize? - const-ret?) - (native-fn (if (mem/primitive-type ret-type) - prim-call - non-prim-call)) + `(let [~ret ~ret-type] + ~(let [call (cons downcall arg-syms) + prim-call `(mem/deserialize* ~call ~ret) + non-prim-call `(mem/deserialize-from ~(list* (first call) + `(mem/scope-allocator ~scope) + (rest call)) + ~ret)] + (cond + (and none-to-serialize? + const-ret?) + (native-fn (if (mem/primitive-type ret-type) + prim-call + non-prim-call)) - none-to-serialize? - `(if (mem/primitive-type ~ret-type) - ~(native-fn prim-call) - ~(native-fn non-prim-call)) + none-to-serialize? + (if (mem/primitive-type ~ret) + ~(native-fn prim-call) + ~(native-fn non-prim-call)) - const-ret? - (native-fn (wrap-serialize - (if (mem/primitive-type ret-type) - prim-call - non-prim-call))) + const-ret? + (native-fn (wrap-serialize + (if (mem/primitive-type ret-type) + prim-call + non-prim-call))) - :else - `(if (mem/primitive-type ~ret-type) - ~(native-fn (wrap-serialize prim-call)) - ~(native-fn (wrap-serialize non-prim-call)))))))))) + :else + `(if (mem/primitive-type ~ret) + ~(native-fn (wrap-serialize prim-call)) + ~(native-fn (wrap-serialize non-prim-call))))))))))) (defn make-serde-wrapper "Constructs a wrapper function for the `downcall` which serializes the arguments From 7975adbd9488def2a497b61fcf27159fc5872351 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Wed, 13 Oct 2021 13:51:13 -0500 Subject: [PATCH 15/19] Add caveat in primitive-type about evaluated arguments --- src/clj/coffi/mem.clj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index 6ed30cd..8e87d4a 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -224,6 +224,9 @@ but which need additional logic to be performed during serialization and deserialization. + Implementations of this method should take into account that type arguments + may not always be evaluated before passing to this function. + Returns nil for any type which does not have a primitive representation." type-dispatch) From 39d0cd854f9a153ec7987f5bbdf7519118670574 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Wed, 13 Oct 2021 13:57:32 -0500 Subject: [PATCH 16/19] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a113059..ca45c56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,12 @@ All notable changes to this project will be documented in this file. This change ## [Unreleased] ### Performance +- Added an `:inline` function to `make-serde-wrapper` to remove serialization overhead on primitives - Added multimethod implementations for primitives in (de)serialization functions, rather than using the default +### Fixed +- `cfn` didn't add serializers with non-primitive types in some cases + ## [0.1.220] - 2021-10-09 ### Fixed - All-primitive method types still used serialization when called from `cfn` From a620752f2cc9a86ee123166a27b2f300d4f40b54 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Wed, 13 Oct 2021 15:28:34 -0500 Subject: [PATCH 17/19] Ensure that the downcalls in the inline expansion are evaluated correctly --- src/clj/coffi/ffi.clj | 186 +++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 92 deletions(-) diff --git a/src/clj/coffi/ffi.clj b/src/clj/coffi/ffi.clj index 16d6b3a..5910ea1 100644 --- a/src/clj/coffi/ffi.clj +++ b/src/clj/coffi/ffi.clj @@ -252,105 +252,107 @@ [downcall arg-types ret-type] (let [const-ret? (s/valid? ::mem/type ret-type) primitive-ret? (mem/primitive? ret-type) - scope (gensym "scope")] - (if-not (seqable? arg-types) - (let [args (gensym "args") - ret (gensym "ret") - serialized-args `(map (fn [arg# type#] (mem/serialize arg# type# ~scope)) ~args ~arg-types) - prim-call `(apply ~downcall ~serialized-args) - non-prim-call `(apply ~downcall (mem/scope-allocator ~scope) ~serialized-args)] - (cond - (and const-ret? - primitive-ret?) - `(fn ~'native-fn - [~'& ~args] - (with-open [~scope (mem/stack-scope)] - ~prim-call)) + scope (gensym "scope") + downcall-sym (gensym "downcall")] + `(let [~downcall-sym ~downcall] + ~(if-not (seqable? arg-types) + (let [args (gensym "args") + ret (gensym "ret") + serialized-args `(map (fn [arg# type#] (mem/serialize arg# type# ~scope)) ~args ~arg-types) + prim-call `(apply ~downcall-sym ~serialized-args) + non-prim-call `(apply ~downcall-sym (mem/scope-allocator ~scope) ~serialized-args)] + (cond + (and const-ret? + primitive-ret?) + `(fn ~'native-fn + [~'& ~args] + (with-open [~scope (mem/stack-scope)] + ~prim-call)) - const-ret? - `(let [~ret ~ret-type] - (fn ~'native-fn - [~'& ~args] - (with-open [~scope (mem/stack-scopee)] - ~(if (mem/primitive-type ret-type) - `(mem/deserialize* ~prim-call ~ret) - `(mem/deserialize-from ~non-prim-call ~ret))))) + const-ret? + `(let [~ret ~ret-type] + (fn ~'native-fn + [~'& ~args] + (with-open [~scope (mem/stack-scopee)] + ~(if (mem/primitive-type ret-type) + `(mem/deserialize* ~prim-call ~ret) + `(mem/deserialize-from ~non-prim-call ~ret))))) - :else - `(let [~ret ~ret-type] - (if (mem/primitive-type ~ret) - (fn ~'native-fn - [~'& ~args] - (with-open [~scope mem/stack-scope] - (mem/deserialize* ~prim-call ~ret))) - (fn ~'native-fn - [~'& ~args] - (with-open [~scope mem/stack-scope] - (mem/deserialize-from ~non-prim-call ~ret))))))) - (let [arg-syms (repeatedly (count arg-types) #(gensym "arg")) - ret (gensym "ret") - serialize-args (map (fn [sym type] - (if (s/valid? ::mem/type type) - (if-not (mem/primitive? type) - (list sym - (if (mem/primitive-type type) - `(mem/serialize* ~sym ~type ~scope) - `(let [alloc# (mem/alloc-instance ~type ~scope)] - (mem/serialize-into ~sym ~type alloc# ~scope) - alloc#))) - (if (primitive-cast-sym type) - (list sym (list (primitive-cast-sym type) sym)) - nil)) - (list sym `(mem/serialize ~sym ~type ~scope)))) - arg-syms arg-types) - wrap-serialize (fn [expr] - `(with-open [~scope (mem/stack-scope)] - (let [~@(mapcat identity serialize-args)] - ~expr))) - native-fn (fn [expr] - `(fn ~'native-fn [~@arg-syms] - ~expr)) - none-to-serialize? (zero? (count (filter some? serialize-args)))] - (cond - (and none-to-serialize? - primitive-ret?) - downcall + :else + `(let [~ret ~ret-type] + (if (mem/primitive-type ~ret) + (fn ~'native-fn + [~'& ~args] + (with-open [~scope (mem/stack-scope)] + (mem/deserialize* ~prim-call ~ret))) + (fn ~'native-fn + [~'& ~args] + (with-open [~scope (mem/stack-scope)] + (mem/deserialize-from ~non-prim-call ~ret))))))) + (let [arg-syms (repeatedly (count arg-types) #(gensym "arg")) + ret (gensym "ret") + serialize-args (map (fn [sym type] + (if (s/valid? ::mem/type type) + (if-not (mem/primitive? type) + (list sym + (if (mem/primitive-type type) + `(mem/serialize* ~sym ~type ~scope) + `(let [alloc# (mem/alloc-instance ~type ~scope)] + (mem/serialize-into ~sym ~type alloc# ~scope) + alloc#))) + (if (primitive-cast-sym type) + (list sym (list (primitive-cast-sym type) sym)) + nil)) + (list sym `(mem/serialize ~sym ~type ~scope)))) + arg-syms arg-types) + wrap-serialize (fn [expr] + `(with-open [~scope (mem/stack-scope)] + (let [~@(mapcat identity serialize-args)] + ~expr))) + native-fn (fn [expr] + `(fn ~'native-fn [~@arg-syms] + ~expr)) + none-to-serialize? (zero? (count (filter some? serialize-args)))] + (cond + (and none-to-serialize? + primitive-ret?) + downcall-sym - primitive-ret? - (-> (cons downcall arg-syms) - wrap-serialize - native-fn) + primitive-ret? + (-> (cons downcall-sym arg-syms) + wrap-serialize + native-fn) - :else - `(let [~ret ~ret-type] - ~(let [call (cons downcall arg-syms) - prim-call `(mem/deserialize* ~call ~ret) - non-prim-call `(mem/deserialize-from ~(list* (first call) - `(mem/scope-allocator ~scope) - (rest call)) - ~ret)] - (cond - (and none-to-serialize? - const-ret?) - (native-fn (if (mem/primitive-type ret-type) - prim-call - non-prim-call)) + :else + `(let [~ret ~ret-type] + ~(let [call (cons downcall-sym arg-syms) + prim-call `(mem/deserialize* ~call ~ret) + non-prim-call `(mem/deserialize-from ~(list* (first call) + `(mem/scope-allocator ~scope) + (rest call)) + ~ret)] + (cond + (and none-to-serialize? + const-ret?) + (native-fn (if (mem/primitive-type ret-type) + prim-call + non-prim-call)) - none-to-serialize? - (if (mem/primitive-type ~ret) - ~(native-fn prim-call) - ~(native-fn non-prim-call)) + none-to-serialize? + (if (mem/primitive-type ~ret) + ~(native-fn prim-call) + ~(native-fn non-prim-call)) - const-ret? - (native-fn (wrap-serialize - (if (mem/primitive-type ret-type) - prim-call - non-prim-call))) + const-ret? + (native-fn (wrap-serialize + (if (mem/primitive-type ret-type) + prim-call + non-prim-call))) - :else - `(if (mem/primitive-type ~ret) - ~(native-fn (wrap-serialize prim-call)) - ~(native-fn (wrap-serialize non-prim-call))))))))))) + :else + `(if (mem/primitive-type ~ret) + ~(native-fn (wrap-serialize prim-call)) + ~(native-fn (wrap-serialize non-prim-call)))))))))))) (defn make-serde-wrapper "Constructs a wrapper function for the `downcall` which serializes the arguments From 5fa1f157d3d3b222a9f259c38a4b070603111568 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Wed, 13 Oct 2021 15:51:49 -0500 Subject: [PATCH 18/19] Fix broken defcfn with native-sym --- src/clj/coffi/ffi.clj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/clj/coffi/ffi.clj b/src/clj/coffi/ffi.clj index 5910ea1..ebb8094 100644 --- a/src/clj/coffi/ffi.clj +++ b/src/clj/coffi/ffi.clj @@ -685,8 +685,10 @@ :multi-arity fn-tail nil))] `(let [~address (find-symbol ~(name (:symbol args))) - ~native-sym (-> (make-downcall ~address ~(:native-arglist args) ~(:return-type args)) - (make-serde-wrapper ~(:native-arglist args) ~(:return-type args))) + ~(or (-> args :wrapper :native-fn) + native-sym) + (-> (make-downcall ~address ~(:native-arglist args) ~(:return-type args)) + (make-serde-wrapper ~(:native-arglist args) ~(:return-type args))) fun# ~(if (:wrapper args) `(fn ~(:name args) ~@fn-tail) From 433145ca0b1a94a1b157416fb0d72b1ca0797798 Mon Sep 17 00:00:00 2001 From: Joshua Suskalo Date: Thu, 14 Oct 2021 10:23:59 -0500 Subject: [PATCH 19/19] Update version in changelog and readme --- CHANGELOG.md | 4 ++-- README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca45c56..02c4bdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). -## [Unreleased] +## [0.1.241] - 2021-10-14 ### Performance - Added an `:inline` function to `make-serde-wrapper` to remove serialization overhead on primitives - Added multimethod implementations for primitives in (de)serialization functions, rather than using the default @@ -55,7 +55,7 @@ All notable changes to this project will be documented in this file. This change - Support for serializing and deserializing arbitrary Clojure functions - Support for serializing and deserializing arbitrary Clojure data structures -[Unreleased]: https://github.com/IGJoshua/coffi/compare/v0.1.220...HEAD +[0.1.241]: https://github.com/IGJoshua/coffi/compare/v0.1.220...v0.1.241 [0.1.220]: https://github.com/IGJoshua/coffi/compare/v0.1.205...v0.1.220 [0.1.205]: https://github.com/IGJoshua/coffi/compare/v0.1.192...v0.1.205 [0.1.192]: https://github.com/IGJoshua/coffi/compare/v0.1.184...v0.1.192 diff --git a/README.md b/README.md index c896dce..b57713c 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ This library is available on Clojars. Add one of the following entries to the `:deps` key of your `deps.edn`: ```clojure -org.suskalo/coffi {:mvn/version "0.1.220"} -io.github.IGJoshua/coffi {:git/tag "v0.1.220" :git/sha "abcbf0f"} +org.suskalo/coffi {:mvn/version "0.1.241"} +io.github.IGJoshua/coffi {:git/tag "v0.1.241" :git/sha "5fa1f15"} ``` If you use this library as a git dependency, you will need to prepare the