diff --git a/src/clj/coffi/layout.clj b/src/clj/coffi/layout.clj index 810a41f..a59e4a0 100644 --- a/src/clj/coffi/layout.clj +++ b/src/clj/coffi/layout.clj @@ -21,11 +21,11 @@ (recur (cond-> (+ offset size) (pos? r) (+ (- align r))) (cond-> aligned-fields - (pos? r) (conj [::padding [::mem/padding (- align r)]]) + (pos? r) (conj [:coffi.layout/padding [:coffi.mem/padding (- align r)]]) :always (conj field)) fields)) (let [strongest-alignment (reduce max (map (comp mem/align-of second) (nth struct-spec 1))) r (rem offset strongest-alignment)] (cond-> aligned-fields - (pos? r) (conj [::padding [::mem/padding (- strongest-alignment r)]])))))] + (pos? r) (conj [:coffi.layout/padding [:coffi.mem/padding (- strongest-alignment r)]])))))] (assoc struct-spec 1 aligned-fields))) diff --git a/src/clj/coffi/mem.clj b/src/clj/coffi/mem.clj index 6170ef8..c9c6640 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -35,7 +35,8 @@ ValueLayout$OfDouble) (java.lang.ref Cleaner) (java.util.function Consumer) - (java.nio ByteOrder))) + (java.nio ByteOrder)) + (:refer-clojure :exclude [defstruct])) (set! *warn-on-reflection* true) @@ -284,6 +285,24 @@ "The alignment in bytes of a c-sized pointer." (.byteAlignment pointer-layout)) +(def ^:private primitive-tag? + '#{byte bytes short shorts int ints long longs + float floats double doubles + bool bools char chars}) + +(defmacro once-only + {:style/indent [:defn] + :private true} + [[& names] & body] + (let [gensyms (repeatedly (count names) gensym)] + `(let [~@(interleave gensyms (repeat (count names) `(gensym)))] + `(let [~~@(mapcat #(-> (if (primitive-tag? (:tag (meta %2))) + [%1 ``(~'~(:tag (meta %2)) ~~%2)] + [`(with-meta ~%1 {:tag '~(:tag (meta %2))}) %2])) + gensyms names)] + ~(let [~@(mapcat #(-> [(with-meta %1 {}) %2]) names gensyms)] + ~@body))))) + (defn read-byte "Reads a [[byte]] from the `segment`, at an optional `offset`." {:inline @@ -462,14 +481,11 @@ {:inline (fn write-byte-inline ([segment value] - `(let [segment# ~segment - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfByte byte-layout 0 value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^byte value] + `(.set ~segment ^ValueLayout$OfByte byte-layout 0 ~value))) ([segment offset value] - `(let [segment# ~segment - offset# ~offset - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfByte byte-layout offset# value#))))} + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^byte value] + `(.set ~segment ^ValueLayout$OfByte byte-layout ~offset ~value))))} ([^MemorySegment segment value] (.set segment ^ValueLayout$OfByte byte-layout 0 ^byte value)) ([^MemorySegment segment ^long offset value] @@ -482,20 +498,15 @@ {:inline (fn write-short-inline ([segment value] - `(let [segment# ~segment - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfShort short-layout 0 value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^short value] + `(.set ~segment ^ValueLayout$OfShort short-layout 0 ~value))) ([segment offset value] - `(let [segment# ~segment - offset# ~offset - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfShort short-layout offset# value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^short value] + `(.set ~segment ^ValueLayout$OfShort short-layout ~offset ~value))) ([segment offset byte-order value] - `(let [segment# ~segment - offset# ~offset - byte-order# ~byte-order - value# ~value] - (.set ^MemorySegment segment# (.withOrder ^ValueLayout$OfShort short-layout ^ByteOrder byte-order#) offset# value#))))} + (once-only [^java.lang.foreign.MemorySegment segment ^long offset + ^java.nio.ByteOrder byte-order ^short value] + `(.set ~segment (.withOrder ^ValueLayout$OfShort short-layout ~byte-order) ~offset ~value))))} ([^MemorySegment segment value] (.set segment ^ValueLayout$OfShort short-layout 0 ^short value)) ([^MemorySegment segment ^long offset value] @@ -510,20 +521,15 @@ {:inline (fn write-int-inline ([segment value] - `(let [segment# ~segment - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfInt int-layout 0 value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^int value] + `(.set ~segment ^ValueLayout$OfInt int-layout 0 ~value))) ([segment offset value] - `(let [segment# ~segment - offset# ~offset - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfInt int-layout offset# value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^int value] + `(.set ~segment ^ValueLayout$OfInt int-layout ~offset ~value))) ([segment offset byte-order value] - `(let [segment# ~segment - offset# ~offset - byte-order# ~byte-order - value# ~value] - (.set ^MemorySegment segment# (.withOrder ^ValueLayout$OfInt int-layout ^ByteOrder byte-order#) offset# value#))))} + (once-only [^java.lang.foreign.MemorySegment segment ^long offset + ^java.nio.ByteOrder byte-order ^int value] + `(.set ~segment (.withOrder ^ValueLayout$OfInt int-layout ~byte-order) ~offset ~value))))} ([^MemorySegment segment value] (.set segment ^ValueLayout$OfInt int-layout 0 ^int value)) ([^MemorySegment segment ^long offset value] @@ -538,20 +544,15 @@ {:inline (fn write-long-inline ([segment value] - `(let [segment# ~segment - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfLong long-layout 0 value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^long value] + `(.set ~segment ^ValueLayout$OfLong long-layout 0 ~value))) ([segment offset value] - `(let [segment# ~segment - offset# ~offset - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfLong long-layout offset# value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^long value] + `(.set ~segment ^ValueLayout$OfLong long-layout ~offset ~value))) ([segment offset byte-order value] - `(let [segment# ~segment - offset# ~offset - byte-order# ~byte-order - value# ~value] - (.set ^MemorySegment segment# (.withOrder ^ValueLayout$OfLong long-layout ^ByteOrder byte-order#) offset# value#))))} + (once-only [^java.lang.foreign.MemorySegment segment ^long offset + ^java.nio.ByteOrder byte-order ^long value] + `(.set ~segment (.withOrder ^ValueLayout$OfLong long-layout ~byte-order) ~offset ~value))))} (^long [^MemorySegment segment ^long value] (.set segment ^ValueLayout$OfLong long-layout 0 value)) (^long [^MemorySegment segment ^long offset ^long value] @@ -564,14 +565,11 @@ {:inline (fn write-char-inline ([segment value] - `(let [segment# ~segment - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfByte byte-layout 0 (unchecked-byte (unchecked-int value#))))) + (once-only [^java.lang.foreign.MemorySegment segment ^char value] + `(.set ~segment ^ValueLayout$OfByte byte-layout 0 (unchecked-byte (unchecked-int ~value))))) ([segment offset value] - `(let [segment# ~segment - offset# ~offset - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfByte byte-layout offset# (unchecked-byte (unchecked-int value#))))))} + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^char value] + `(.set ~segment ^ValueLayout$OfByte byte-layout ~offset (unchecked-byte (unchecked-int ~value))))))} ([^MemorySegment segment value] (.set segment @@ -590,20 +588,15 @@ {:inline (fn write-float-inline ([segment value] - `(let [segment# ~segment - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfFloat float-layout 0 value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^float value] + `(.set ~segment ^ValueLayout$OfFloat float-layout 0 ~value))) ([segment offset value] - `(let [segment# ~segment - offset# ~offset - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfFloat float-layout offset# value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^float value] + `(.set ~segment ^ValueLayout$OfFloat float-layout ~offset ~value))) ([segment offset byte-order value] - `(let [segment# ~segment - offset# ~offset - byte-order# ~byte-order - value# ~value] - (.set ^MemorySegment segment# (.withOrder ^ValueLayout$OfFloat float-layout ^ByteOrder byte-order#) offset# value#))))} + (once-only [^java.lang.foreign.MemorySegment segment ^long offset + ^java.nio.ByteOrder byte-order ^float value] + `(.set ~segment (.withOrder ^ValueLayout$OfFloat float-layout ~byte-order) ~offset ~value))))} ([^MemorySegment segment value] (.set segment ^ValueLayout$OfFloat float-layout 0 ^float value)) ([^MemorySegment segment ^long offset value] @@ -618,20 +611,15 @@ {:inline (fn write-double-inline ([segment value] - `(let [segment# ~segment - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfDouble double-layout 0 value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^double value] + `(.set ~segment ^ValueLayout$OfDouble double-layout 0 ~value))) ([segment offset value] - `(let [segment# ~segment - offset# ~offset - value# ~value] - (.set ^MemorySegment segment# ^ValueLayout$OfDouble double-layout offset# value#))) + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^double value] + `(.set ~segment ^ValueLayout$OfDouble double-layout ~offset ~value))) ([segment offset byte-order value] - `(let [segment# ~segment - offset# ~offset - byte-order# ~byte-order - value# ~value] - (.set ^MemorySegment segment# (.withOrder ^ValueLayout$OfDouble double-layout ^ByteOrder byte-order#) offset# value#))))} + (once-only [^java.lang.foreign.MemorySegment segment ^long offset + ^java.nio.ByteOrder byte-order ^double value] + `(.set ~segment (.withOrder ^ValueLayout$OfDouble double-layout ~byte-order) ~offset ~value))))} (^double [^MemorySegment segment ^double value] (.set segment ^ValueLayout$OfDouble double-layout 0 value)) (^double [^MemorySegment segment ^long offset ^double value] @@ -644,19 +632,402 @@ {:inline (fn write-address-inline ([segment value] - `(let [segment# ~segment - value# ~value] - (.set ^MemorySegment segment# ^AddressLayout pointer-layout 0 ^MemorySegment value#))) + (once-only [^java.lang.foreign.MemorySegment segment + ^java.lang.foreign.MemorySegment value] + `(.set ~segment ^AddressLayout pointer-layout 0 ~value))) ([segment offset value] - `(let [segment# ~segment - offset# ~offset - value# ~value] - (.set ^MemorySegment segment# ^AddressLayout pointer-layout offset# ^MemorySegment value#))))} + (once-only [^java.lang.foreign.MemorySegment segment ^long offset + ^java.lang.foreign.MemorySegment value] + `(.set ~segment ^AddressLayout pointer-layout ~offset ~value))))} ([^MemorySegment segment ^MemorySegment value] (.set segment ^AddressLayout pointer-layout 0 value)) ([^MemorySegment segment ^long offset ^MemorySegment value] (.set segment ^AddressLayout pointer-layout offset value))) +(defn write-bytes + "Writes n elements from a [[byte]] array to the `segment`, at an optional `offset`." + {:inline + (fn write-bytes-inline + ([segment n value] + (once-only [^java.lang.foreign.MemorySegment segment ^bytes value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfByte byte-layout 0 ~n))) + ([segment n offset value] + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^bytes value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfByte byte-layout ~offset ~n))))} + ([^MemorySegment segment n ^bytes value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfByte byte-layout 0 ^int n)) + ([^MemorySegment segment n offset ^bytes value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfByte byte-layout ^long offset ^int n))) + +(defn write-shorts + "Writes n elements from a [[short]] array to the `segment`, at an optional `offset`. + + If `byte-order` is not provided, it defaults to [[native-endian]]." + {:inline + (fn write-shorts-inline + ([segment n value] + (once-only [^java.lang.foreign.MemorySegment segment ^shorts value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfShort short-layout 0 ~n))) + ([segment n offset value] + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^shorts value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfShort short-layout ~offset ~n))))} + ([^MemorySegment segment n ^shorts value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfShort short-layout 0 ^int n)) + ([^MemorySegment segment n ^long offset ^shorts value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfShort short-layout ^long offset ^int n))) + +(defn write-ints + "Writes n elements from an [[int]] array to the `segment`, at an optional `offset`. + + If `byte-order` is not provided, it defaults to [[native-endian]]." + {:inline + (fn write-ints-inline + ([segment n value] + (once-only [^java.lang.foreign.MemorySegment segment ^ints value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfInt int-layout 0 ~n))) + ([segment n offset value] + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^ints value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfInt int-layout ~offset ~n))))} + ([^MemorySegment segment n ^ints value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfInt int-layout 0 ^int n)) + ([^MemorySegment segment n ^long offset ^ints value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfInt int-layout ^long offset ^int n))) + +(defn write-longs + "Writes n elements from a [[long]] array to the `segment`, at an optional `offset`. + + If `byte-order` is not provided, it defaults to [[native-endian]]." + {:inline + (fn write-longs-inline + ([segment n value] + (once-only [^java.lang.foreign.MemorySegment segment ^longs value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfLong long-layout 0 ~n))) + ([segment n offset value] + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^longs value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfLong long-layout ~offset ~n))))} + ([^MemorySegment segment n ^longs value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfLong long-layout 0 ^int n)) + ([^MemorySegment segment n ^long offset ^longs value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfLong long-layout ^long offset ^int n))) + +(defn write-chars + "Writes n elements from a [[char]] array to the `segment`, at an optional `offset`. + + If `byte-order` is not provided, it defaults to [[native-endian]]." + {:inline + (fn write-chars-inline + ([segment n value] + (once-only [^java.lang.foreign.MemorySegment segment ^chars value] + `(MemorySegment/copy (bytes (byte-array (map unchecked-int ~value))) 0 ~segment ^ValueLayout$OfChar char-layout 0 ~n))) + ([segment n offset value] + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^chars value] + `(MemorySegment/copy (bytes (byte-array (map unchecked-int ~value))) 0 ~segment ^ValueLayout$OfChar char-layout ~offset ~n))))} + ([^MemorySegment segment n ^chars value] + (MemorySegment/copy (bytes (byte-array (map unchecked-int value))) 0 segment ^ValueLayout$OfChar char-layout 0 ^int n)) + ([^MemorySegment segment n ^long offset ^chars value] + (MemorySegment/copy (bytes (byte-array (map unchecked-int value))) 0 segment ^ValueLayout$OfChar char-layout ^long offset ^int n ))) + +(defn write-floats + "Writes n elements from a [[float]] array to the `segment`, at an optional `offset`. + + If `byte-order` is not provided, it defaults to [[native-endian]]." + {:inline + (fn write-floats-inline + ([segment n value] + (once-only [^java.lang.foreign.MemorySegment segment ^floats value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfFloat float-layout 0 ~n))) + ([segment n offset value] + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^floats value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfFloat float-layout ~offset ~n))))} + ([^MemorySegment segment n ^floats value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfFloat float-layout 0 ^int n)) + ([^MemorySegment segment n ^long offset ^floats value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfFloat float-layout ^long offset ^int n))) + +(defn write-doubles + "Writes n elements from a [[double]] array to the `segment`, at an optional `offset`. + + If `byte-order` is not provided, it defaults to [[native-endian]]." + {:inline + (fn write-doubles-inline + ([segment n value] + (once-only [^java.lang.foreign.MemorySegment segment ^doubles value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfDouble double-layout 0 ~n))) + ([segment n offset value] + (once-only [^java.lang.foreign.MemorySegment segment ^long offset ^doubles value] + `(MemorySegment/copy ~value 0 ~segment ^ValueLayout$OfDouble double-layout ~offset ~n))))} + ([^MemorySegment segment n ^doubles value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfDouble double-layout 0 ^int n)) + ([^MemorySegment segment n ^long offset ^doubles value] + (MemorySegment/copy value 0 segment ^ValueLayout$OfDouble double-layout ^long offset ^int n))) + + +(defn read-bytes + "reads `n` elements from a `segment` to a [[byte]] array, at an optional `offset`." + {:inline + (fn read-bytes-inline + ([segment n] + `(let [n# ~n + segment# ~segment + arr# (byte-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfByte byte-layout 0 arr# 0 n#) + arr#)) + ([segment n offset] + `(let [n# ~n + segment# ~segment + offset# ~offset + arr# (byte-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfByte byte-layout offset# arr# 0 n#) + arr#)) + ([segment n offset byte-order] + `(let [n# ~n + segment# ~segment + offset# ~offset + byte-order# ~byte-order + arr# (byte-array ~n)] + (MemorySegment/copy ^MemorySegment segment# (.withOrder ^ValueLayout$OfByte byte-layout ^ByteOrder byte-order#) offset# arr# 0 n#) + arr#)))} + ([^MemorySegment segment n] + (let [arr (byte-array n)] + (MemorySegment/copy segment ^ValueLayout$OfByte byte-layout 0 arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset] + (let [arr (byte-array n)] + (MemorySegment/copy segment ^ValueLayout$OfByte byte-layout offset arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset ^ByteOrder byte-order] + (let [arr (byte-array n)] + (MemorySegment/copy segment (.withOrder ^ValueLayout$OfByte byte-layout byte-order) offset arr 0 ^int n) + arr))) + +(defn read-shorts + "reads `n` elements from a `segment` to a [[short]] array, at an optional `offset`." + {:inline + (fn read-shorts-inline + ([segment n] + `(let [n# ~n + segment# ~segment + arr# (short-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfShort short-layout 0 arr# 0 n#) + arr#)) + ([segment n offset] + `(let [n# ~n + segment# ~segment + offset# ~offset + arr# (short-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfShort short-layout offset# arr# 0 n#) + arr#)) + ([segment n offset byte-order] + `(let [n# ~n + segment# ~segment + offset# ~offset + byte-order# ~byte-order + arr# (short-array ~n)] + (MemorySegment/copy ^MemorySegment segment# (.withOrder ^ValueLayout$OfShort short-layout ^ByteOrder byte-order#) offset# arr# 0 n#) + arr#)))} + ([^MemorySegment segment n] + (let [arr (short-array n)] + (MemorySegment/copy segment ^ValueLayout$OfShort short-layout 0 arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset] + (let [arr (short-array n)] + (MemorySegment/copy segment ^ValueLayout$OfShort short-layout offset arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset ^ByteOrder byte-order] + (let [arr (short-array n)] + (MemorySegment/copy segment (.withOrder ^ValueLayout$OfShort short-layout byte-order) offset arr 0 ^int n) + arr))) + +(defn read-longs + "reads `n` elements from a `segment` to a [[long]] array, at an optional `offset`." + {:inline + (fn read-longs-inline + ([segment n] + `(let [n# ~n + segment# ~segment + arr# (long-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfLong long-layout 0 arr# 0 n#) + arr#)) + ([segment n offset] + `(let [n# ~n + segment# ~segment + offset# ~offset + arr# (long-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfLong long-layout offset# arr# 0 n#) + arr#)) + ([segment n offset byte-order] + `(let [n# ~n + segment# ~segment + offset# ~offset + byte-order# ~byte-order + arr# (long-array ~n)] + (MemorySegment/copy ^MemorySegment segment# (.withOrder ^ValueLayout$OfLong long-layout ^ByteOrder byte-order#) offset# arr# 0 n#) + arr#)))} + ([^MemorySegment segment n] + (let [arr (long-array n)] + (MemorySegment/copy segment ^ValueLayout$OfLong long-layout 0 arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset] + (let [arr (long-array n)] + (MemorySegment/copy segment ^ValueLayout$OfLong long-layout offset arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset ^ByteOrder byte-order] + (let [arr (long-array n)] + (MemorySegment/copy segment (.withOrder ^ValueLayout$OfLong long-layout byte-order) offset arr 0 ^int n) + arr))) + +(defn read-ints + "reads `n` elements from a `segment` to a [[int]] array, at an optional `offset`." + {:inline + (fn read-ints-inline + ([segment n] + `(let [n# ~n + segment# ~segment + arr# (int-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfInt int-layout 0 arr# 0 n#) + arr#)) + ([segment n offset] + `(let [n# ~n + segment# ~segment + offset# ~offset + arr# (int-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfInt int-layout offset# arr# 0 n#) + arr#)) + ([segment n offset byte-order] + `(let [n# ~n + segment# ~segment + offset# ~offset + byte-order# ~byte-order + arr# (int-array ~n)] + (MemorySegment/copy ^MemorySegment segment# (.withOrder ^ValueLayout$OfInt int-layout ^ByteOrder byte-order#) offset# arr# 0 n#) + arr#)))} + ([^MemorySegment segment n] + (let [arr (int-array n)] + (MemorySegment/copy segment ^ValueLayout$OfInt int-layout 0 arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset] + (let [arr (int-array n)] + (MemorySegment/copy segment ^ValueLayout$OfInt int-layout offset arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset ^ByteOrder byte-order] + (let [arr (int-array n)] + (MemorySegment/copy segment (.withOrder ^ValueLayout$OfInt int-layout byte-order) offset arr 0 ^int n) + arr))) + +(defn read-chars + "reads `n` elements from a `segment` to a [[char]] array, at an optional `offset`." + {:inline + (fn read-chars-inline + ([segment n] + `(let [n# ~n + segment# ~segment + arr# (char-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfChar char-layout 0 arr# 0 n#) + arr#)) + ([segment n offset] + `(let [n# ~n + segment# ~segment + offset# ~offset + arr# (char-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfChar char-layout offset# arr# 0 n#) + arr#)) + ([segment n offset byte-order] + `(let [n# ~n + segment# ~segment + offset# ~offset + byte-order# ~byte-order + arr# (char-array ~n)] + (MemorySegment/copy ^MemorySegment segment# (.withOrder ^ValueLayout$OfChar char-layout ^ByteOrder byte-order#) offset# arr# 0 n#) + arr#)))} + ([^MemorySegment segment n] + (let [arr (char-array n)] + (MemorySegment/copy segment ^ValueLayout$OfChar char-layout 0 arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset] + (let [arr (char-array n)] + (MemorySegment/copy segment ^ValueLayout$OfChar char-layout offset arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset ^ByteOrder byte-order] + (let [arr (char-array n)] + (MemorySegment/copy segment (.withOrder ^ValueLayout$OfChar char-layout byte-order) offset arr 0 ^int n) + arr))) + +(defn read-floats + "reads `n` elements from a `segment` to a [[float]] array, at an optional `offset`." + {:inline + (fn read-floats-inline + ([segment n] + `(let [n# ~n + segment# ~segment + arr# (float-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfFloat float-layout 0 arr# 0 n#) + arr#)) + ([segment n offset] + `(let [n# ~n + segment# ~segment + offset# ~offset + arr# (float-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfFloat float-layout offset# arr# 0 n#) + arr#)) + ([segment n offset byte-order] + `(let [n# ~n + segment# ~segment + offset# ~offset + byte-order# ~byte-order + arr# (float-array ~n)] + (MemorySegment/copy ^MemorySegment segment# (.withOrder ^ValueLayout$OfFloat float-layout ^ByteOrder byte-order#) offset# arr# 0 n#) + arr#)))} + ([^MemorySegment segment n] + (let [arr (float-array n)] + (MemorySegment/copy segment ^ValueLayout$OfFloat float-layout 0 arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset] + (let [arr (float-array n)] + (MemorySegment/copy segment ^ValueLayout$OfFloat float-layout offset arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset ^ByteOrder byte-order] + (let [arr (float-array n)] + (MemorySegment/copy segment (.withOrder ^ValueLayout$OfFloat float-layout byte-order) offset arr 0 ^int n) + arr))) + +(defn read-doubles + "reads `n` elements from a `segment` to a [[double]] array, at an optional `offset`." + {:inline + (fn read-doubles-inline + ([segment n] + `(let [n# ~n + segment# ~segment + arr# (double-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfDouble double-layout 0 arr# 0 n#) + arr#)) + ([segment n offset] + `(let [n# ~n + segment# ~segment + offset# ~offset + arr# (double-array ~n)] + (MemorySegment/copy ^MemorySegment segment# ^ValueLayout$OfDouble double-layout offset# arr# 0 n#) + arr#)) + ([segment n offset byte-order] + `(let [n# ~n + segment# ~segment + offset# ~offset + byte-order# ~byte-order + arr# (double-array ~n)] + (MemorySegment/copy ^MemorySegment segment# (.withOrder ^ValueLayout$OfDouble double-layout ^ByteOrder byte-order#) offset# arr# 0 n#) + arr#)))} + ([^MemorySegment segment n] + (let [arr (double-array n)] + (MemorySegment/copy segment ^ValueLayout$OfDouble double-layout 0 arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset] + (let [arr (double-array n)] + (MemorySegment/copy segment ^ValueLayout$OfDouble double-layout offset arr 0 ^int n) + arr)) + ([^MemorySegment segment n ^long offset ^ByteOrder byte-order] + (let [arr (double-array n)] + (MemorySegment/copy segment (.withOrder ^ValueLayout$OfDouble double-layout byte-order) offset arr 0 ^int n) + arr))) + (defn- type-dispatch "Gets a type dispatch value from a (potentially composite) type." [type] @@ -911,7 +1282,7 @@ (defmethod serialize-into ::byte [obj _type segment _arena] - (write-byte segment (byte obj))) + (write-byte segment obj)) (defmethod serialize-into ::short [obj type segment _arena] @@ -1357,3 +1728,504 @@ (s/fdef defalias :args (s/cat :new-type qualified-keyword? :aliased-type any?)) + +(defn- coffitype->typename [in] + (let [[indirect-type type n & {:keys [raw?] :as opts}] (if (vector? in) in [:- in]) + arr? (= indirect-type ::array) + ptr? (= indirect-type ::pointer) + array-types {::byte 'bytes + ::short 'shorts + ::int 'ints + ::long 'longs + ::char 'chars + ::float 'floats + ::double 'doubles} + single-types {::byte 'byte + ::short 'short + ::int 'int + ::long 'long + ::char 'char + ::float 'float + ::double 'double + ::c-string 'String}] + (cond (and arr? raw?) (get array-types type 'objects) + (and arr?) `clojure.lang.IPersistentVector + (and ptr?) `java.lang.foreign.MemorySegment + :default (get single-types type type)))) + +(defn- coffitype->array-fn [type] + (get + {:coffi.mem/byte `byte-array + :coffi.mem/short `short-array + :coffi.mem/int `int-array + :coffi.mem/long `long-array + :coffi.mem/char `char-array + :coffi.mem/float `float-array + :coffi.mem/double `double-array} + type + `object-array)) + +(defn- coffitype->array-write-fn [type] + ({:coffi.mem/byte `write-bytes + :coffi.mem/short `write-shorts + :coffi.mem/int `write-ints + :coffi.mem/long `write-longs + :coffi.mem/char `write-chars + :coffi.mem/float `write-floats + :coffi.mem/double `write-doubles} type)) + +(defn- coffitype->array-read-fn [type] + ({:coffi.mem/byte `read-bytes + :coffi.mem/short `read-shorts + :coffi.mem/int `read-ints + :coffi.mem/long `read-longs + :coffi.mem/char `read-chars + :coffi.mem/float `read-floats + :coffi.mem/double `read-doubles} type)) + +(defmulti generate-deserialize (fn [& xs] (if (vector? (first xs)) (ffirst xs) (first xs)))) + +(defmethod generate-deserialize :coffi.mem/byte [_type offset segment-source-form] `(read-byte ~segment-source-form ~offset)) +(defmethod generate-deserialize :coffi.mem/short [_type offset segment-source-form] `(read-short ~segment-source-form ~offset)) +(defmethod generate-deserialize :coffi.mem/int [_type offset segment-source-form] `(read-int ~segment-source-form ~offset)) +(defmethod generate-deserialize :coffi.mem/long [_type offset segment-source-form] `(read-long ~segment-source-form ~offset)) +(defmethod generate-deserialize :coffi.mem/char [_type offset segment-source-form] `(read-char ~segment-source-form ~offset)) +(defmethod generate-deserialize :coffi.mem/float [_type offset segment-source-form] `(read-float ~segment-source-form ~offset)) +(defmethod generate-deserialize :coffi.mem/double [_type offset segment-source-form] `(read-double ~segment-source-form ~offset)) +(defmethod generate-deserialize :coffi.mem/pointer [_type offset segment-source-form] `(read-address ~segment-source-form ~offset)) +(defmethod generate-deserialize :coffi.mem/c-string [_type offset segment-source-form] + `(.getString (.reinterpret (.get ~(with-meta segment-source-form {:tag 'java.lang.foreign.MemorySegment}) pointer-layout ~offset) Integer/MAX_VALUE) 0)) + +(defn- generate-deserialize-array-as-array-bulk [array-type n offset segment-source-form] + (list (coffitype->array-read-fn array-type) segment-source-form n offset)) + +(defn- generate-deserialize-array-as-array-inline [array-type n offset segment-source-form] + (let [a (gensym 'array)] + (concat + `(let [~a (~(coffitype->array-fn array-type) ~n)]) + (map + #(list `aset a % (generate-deserialize array-type (+ offset (* (size-of array-type) %)) segment-source-form)) + (range n)) + [a]))) + +(defn- generate-deserialize-array-as-array-loop [array-type n offset segment-source-form] + (let [a (gensym 'array)] + (concat + `(let [~a (~(coffitype->array-fn array-type) ~n)]) + [(list `dotimes ['m n] + (list `aset a 'm (generate-deserialize array-type `(+ ~offset (* ~(size-of array-type) ~'m)) segment-source-form)))] + [a]))) + +(defn- generate-deserialize-array-as-array [array-type n offset segment-source-form] + (if (coffitype->array-read-fn array-type) + (generate-deserialize-array-as-array-bulk array-type n offset segment-source-form) ;bulk-copy available + (let [inline-cutoff 32] ;this magic value has been benchmarked, but it may need adjusting for specific architectures + (if (< n inline-cutoff) + (generate-deserialize-array-as-array-inline array-type n offset segment-source-form) + (generate-deserialize-array-as-array-loop array-type n offset segment-source-form))))) + +(defn- generate-deserialize-array-as-vector-loop [array-type n offset segment-source-form] + (let [loop-deserialize (generate-deserialize array-type `(+ ~offset (* ~(size-of array-type) ~'i)) segment-source-form)] + `(loop [~'i 0 ~'v (transient [])] + (if (< ~'i ~n) + (recur (unchecked-inc ~'i) (conj! ~'v ~loop-deserialize)) + (persistent! ~'v))))) + +(defn- generate-deserialize-array-as-vector-inline [array-type n offset segment-source-form] + (vec (map #(generate-deserialize array-type (+ offset (* (size-of array-type) %)) segment-source-form) (range n)))) + +(defn- generate-deserialize-array-as-vector [array-type n offset segment-source-form] + (let [inline-cutoff 64] ;this magic value has been benchmarked, but it may need adjusting for specific architectures + (if (< n inline-cutoff) + (generate-deserialize-array-as-vector-inline array-type n offset segment-source-form) + (generate-deserialize-array-as-vector-loop array-type n offset segment-source-form)))) + +(defmethod generate-deserialize :coffi.mem/array [[_ array-type n & {:keys [raw?]}] offset segment-source-form] + (if raw? + (generate-deserialize-array-as-array array-type n offset segment-source-form) + (generate-deserialize-array-as-vector array-type n offset segment-source-form))) + +(defn- typelist [fields] + (->> + (partition 2 2 (interleave (reductions + 0 (map (comp size-of second) fields)) fields)) + (filter (fn [[_ [_ field-type]]] (not (and (vector? field-type) (= "padding" (name (first field-type))))))))) + +(defn register-new-struct-deserialization [typename [_struct fields]] + (defmethod generate-deserialize typename [_type global-offset segment-source-form] + (->> (typelist fields) + (map-indexed + (fn [index [offset [_ field-type]]] + (generate-deserialize field-type (if (number? global-offset) (+ global-offset offset) `(+ ~global-offset ~offset)) segment-source-form))) + (cons (symbol (str (name typename) ".")))))) + +(defmulti generate-serialize (fn [& xs] (if (vector? (first xs)) (ffirst xs) (first xs)))) + +(defmethod generate-serialize :coffi.mem/byte [_type source-form offset segment-source-form] `(write-byte ~segment-source-form ~offset ~source-form)) +(defmethod generate-serialize :coffi.mem/short [_type source-form offset segment-source-form] `(write-short ~segment-source-form ~offset ~source-form)) +(defmethod generate-serialize :coffi.mem/int [_type source-form offset segment-source-form] `(write-int ~segment-source-form ~offset ~source-form)) +(defmethod generate-serialize :coffi.mem/long [_type source-form offset segment-source-form] `(write-long ~segment-source-form ~offset ~source-form)) +(defmethod generate-serialize :coffi.mem/char [_type source-form offset segment-source-form] `(write-char ~segment-source-form ~offset ~source-form)) +(defmethod generate-serialize :coffi.mem/float [_type source-form offset segment-source-form] `(write-float ~segment-source-form ~offset ~source-form)) +(defmethod generate-serialize :coffi.mem/double [_type source-form offset segment-source-form] `(write-double ~segment-source-form ~offset ~source-form)) +(defmethod generate-serialize :coffi.mem/pointer [_type source-form offset segment-source-form] `(write-address ~segment-source-form ~offset ~source-form)) +(defmethod generate-serialize :coffi.mem/c-string [_type source-form offset segment-source-form] `(write-address ~segment-source-form ~offset (.allocateFrom (Arena/ofAuto) ~source-form))) + +(defn- generate-serialize-array-as-array-bulk [member-type length source-form offset segment-source-form] + (list (coffitype->array-write-fn member-type) segment-source-form length offset source-form)) + +(defn- generate-serialize-array-as-array-loop [member-type length source-form offset segment-source-form] + (let [obj (with-meta (gensym 'src-array) {:tag (coffitype->typename [::array member-type length :raw? true])})] + (list `let [obj source-form] + (list `dotimes ['n length] + (generate-serialize member-type `(aget ~obj ~'n) `(+ ~offset (* ~(size-of member-type) ~'n)) segment-source-form))))) + +(defn- generate-serialize-array-as-array-inline [member-type length source-form offset segment-source-form] + (let [obj (with-meta (gensym 'src-array) {:tag (coffitype->typename [::array member-type length :raw? true])})] + (concat + (list `let [obj source-form]) + (map + #(generate-serialize member-type (list `aget obj %) (+ offset (* (size-of member-type) %)) segment-source-form) + (range length))))) + +(defn- generate-serialize-array-as-array [member-type length source-form offset segment-source-form] + (if (coffitype->array-write-fn member-type) + (generate-serialize-array-as-array-bulk member-type length source-form offset segment-source-form) + (let [inline-cutoff 32] ;this magic value has been benchmarked, but it may need adjusting for specific architectures + (if (< length inline-cutoff) + (generate-serialize-array-as-array-inline member-type length source-form offset segment-source-form) + (generate-serialize-array-as-array-loop member-type length source-form offset segment-source-form))))) + +(defn- generate-serialize-vector-as-array-bulk [member-type length source-form offset segment-source-form] + (list (coffitype->array-write-fn member-type) segment-source-form length offset (list (coffitype->array-fn member-type) length source-form))) + +(defn- generate-serialize-vector-as-array-loop [member-type length source-form offset segment-source-form] + (let [obj (with-meta (gensym 'src-array) {:tag (coffitype->typename [::array member-type length :raw? false])})] + (list `let [obj source-form] + (list `dotimes ['n length] + (generate-serialize member-type `(nth ~obj ~'n) `(+ ~offset (* ~(size-of member-type) ~'n)) segment-source-form))))) + +(defn- generate-serialize-vector-as-array-inline [member-type length source-form offset segment-source-form] + (let [obj (with-meta (gensym 'src-array) {:tag (coffitype->typename [::array member-type length :raw? false])})] + (concat + (list `let [obj source-form]) + (map + #(generate-serialize member-type (list `aget obj %) (+ offset (* (size-of member-type) %)) segment-source-form) + (range length))))) + +(defn generate-serialize-vector-as-array [member-type length source-form offset segment-source-form] + (let [cutoff 1024 ;this magic value has been benchmarked, but it may need adjusting for specific architectures + obj (with-meta (gensym 'src-array) {:tag (coffitype->typename [::array member-type length :raw? false])})] + (if (or (<= length cutoff) (not (coffitype->array-write-fn member-type))) + (generate-serialize-vector-as-array-loop member-type length source-form offset segment-source-form) + (generate-serialize-vector-as-array-bulk member-type length source-form offset segment-source-form)))) + +(defmethod generate-serialize :coffi.mem/array [[_arr member-type length & {:keys [raw?]}] source-form offset segment-source-form] + (if raw? + (generate-serialize-array-as-array member-type length source-form offset segment-source-form) + (generate-serialize-vector-as-array member-type length source-form offset segment-source-form))) + +(defn register-new-struct-serialization [typename [_struct fields]] + (let [fieldnames (filter #(not= "padding" (name %)) (map first fields))] + (defmethod generate-serialize typename [_type source-form global-offset segment-source-form] + (->> (typelist fields) + (map-indexed + (fn [index [offset [_ field-type]]] + (generate-serialize field-type (list (symbol (str "." (name (nth fieldnames index)))) 'source-obj) (if (number? global-offset) (+ global-offset offset) `(+ ~global-offset ~offset)) segment-source-form))) + (concat [`let ['source-obj source-form]]))))) + +(gen-interface + :name coffi.mem.IStructImpl :methods + [[vec_length [] int] + [vec_assoc [Object Object] clojure.lang.Associative] + [vec_assocN [int Object] clojure.lang.IPersistentVector] + [vec_peek [] Object] + [vec_pop [] clojure.lang.IPersistentVector] + [vec_nth [int] Object] + [vec_nth [int Object] Object] + [vec_cons [Object] clojure.lang.IPersistentCollection] + [vec_equiv [Object] boolean] + [vec_empty [] clojure.lang.IPersistentVector] + [vec_iterator [] java.util.Iterator] + [vec_forEach [java.util.function.Consumer] void] + [vec_seq [] clojure.lang.ISeq] + [vec_rseq [] clojure.lang.ISeq] + + [struct_count [] int] + [struct_containsKey [Object] boolean] + [struct_valAt [Object] Object] + [struct_valAt [Object Object] Object] + [struct_entryAt [Object] clojure.lang.IMapEntry] + [nthKey [int] clojure.lang.Keyword] + + [map_assoc [Object Object] clojure.lang.Associative] + [map_assocEx [Object Object] clojure.lang.IPersistentMap] + [map_without [Object] clojure.lang.IPersistentMap] + [map_cons [Object] clojure.lang.IPersistentCollection] + [map_equiv [Object] boolean] + [map_empty [] clojure.lang.IPersistentMap] + [map_iterator [] java.util.Iterator] + [map_forEach [java.util.function.Consumer] void] + [map_seq [] clojure.lang.ISeq] + ;java.util.map fns + [map_containsValue [Object] boolean] + [map_entrySet [] java.util.Set] + [map_get [Object] Object] + [map_isEmpty [] boolean] + [map_keySet [] java.util.Set] + [map_size [] int] + [map_values [] java.util.Collection] + [map_forEach [java.util.function.BiConsumer] void]]) + + +(defmacro for-each-fixed-length [n] + `(defn ~(symbol (str "for-each-fixed-" n)) ~[(with-meta 'offset {:tag int}) (with-meta 'action {:tag 'java.util.function.Consumer}) (with-meta 's {:tag 'coffi.mem.IStructImpl})] + ~(cons `do (map (fn [i] (list '.accept (with-meta 'action {:tag 'java.util.function.Consumer}) (list '.vec_nth (with-meta 's {:tag 'coffi.mem.IStructImpl}) i))) (range n))))) + +(for-each-fixed-length 1) +(for-each-fixed-length 2) +(for-each-fixed-length 3) +(for-each-fixed-length 4) +(for-each-fixed-length 5) +(for-each-fixed-length 6) +(for-each-fixed-length 7) +(for-each-fixed-length 8) +(for-each-fixed-length 9) +(for-each-fixed-length 10) +(for-each-fixed-length 11) +(for-each-fixed-length 12) +(for-each-fixed-length 13) +(for-each-fixed-length 14) +(for-each-fixed-length 15) +(for-each-fixed-length 16) + +(deftype StructVecIterator [^coffi.mem.IStructImpl struct-obj ^int size ^{:volatile-mutable true :tag int} i] + java.util.Iterator + (forEachRemaining [this action] + (case (- size i) + 1 (for-each-fixed-1 i action struct-obj) + 2 (for-each-fixed-2 i action struct-obj) + 3 (for-each-fixed-3 i action struct-obj) + 4 (for-each-fixed-4 i action struct-obj) + 5 (for-each-fixed-5 i action struct-obj) + 6 (for-each-fixed-6 i action struct-obj) + 7 (for-each-fixed-7 i action struct-obj) + 8 (for-each-fixed-8 i action struct-obj) + 9 (for-each-fixed-9 i action struct-obj) + 10 (for-each-fixed-10 i action struct-obj) + 11 (for-each-fixed-11 i action struct-obj) + 12 (for-each-fixed-12 i action struct-obj) + 13 (for-each-fixed-13 i action struct-obj) + 14 (for-each-fixed-14 i action struct-obj) + 15 (for-each-fixed-15 i action struct-obj) + 16 (for-each-fixed-16 i action struct-obj) + (loop [index i] (if (< index size) (do (.accept action (.vec_nth struct-obj index)) (recur (inc index))) nil)))) + (hasNext [this] (< i size)) + (next [this] (let [ret (.vec_nth struct-obj i) _ (set! i (unchecked-add-int 1 i))] ret))) + +(gen-interface :name coffi.mem.IStruct :methods [[asVec [] clojure.lang.IPersistentVector] [asMap [] clojure.lang.IPersistentMap]]) + +(deftype StructVecSeq [^clojure.lang.IPersistentVector v ^int i] + clojure.lang.ISeq clojure.lang.Indexed clojure.lang.Sequential + (first [this] (.nth v i)) + (next [this] (if (< i (dec (.count v))) (StructVecSeq. v (inc i)) nil)) + (more [this] (if (< i (dec (.count v))) (StructVecSeq. v (inc i)) '())) + (cons [this o] (clojure.lang.Cons. o this)) + (count [this] (- (.count v) i)) + (empty [this] nil) + (equiv [this o] (= (subvec v i) o)) + (nth [this j] (.nth v (+ i j))) + (nth [this j o] (.nth v (+ i j) o)) + (seq [this] this)) + +(deftype StructMapSeq [^coffi.mem.IStructImpl s ^int i] + clojure.lang.ISeq clojure.lang.Indexed clojure.lang.Sequential + (first [this] (clojure.lang.MapEntry/create (.nthKey s i) (.vec_nth s i))) + (next [this] (if (< i (dec (.struct_count s))) (StructMapSeq. s (inc i)) nil)) + (more [this] (if (< i (dec (.struct_count s))) (StructMapSeq. s (inc i)) '())) + (cons [this o] (clojure.lang.Cons. o this)) + (count [this] (- (.struct_count s) i)) + (empty [this] nil) + (equiv [this o] (if (not= (count o) (.struct_count s)) false (loop [os (seq o) index i] (if (< index (- (.struct_count s) i)) (if (= [(.nthKey s index) (.vec_nth s index)] (first os)) (recur (next os) (inc index)) false) true)))) + (nth [this j] (clojure.lang.MapEntry/create (.nthKey s (+ i j)) (.vec_nth s (+ i j)))) + (nth [this j o] (if (< (+ i j) (.struct_count s)) (clojure.lang.MapEntry/create (.nthKey s (+ i j)) (.vec_nth s (+ i j))) o)) + (seq [this] this)) + +(deftype VecWrap [^coffi.mem.IStructImpl org] + coffi.mem.IStruct clojure.lang.IPersistentVector Iterable + (length [this] (.vec_length org)) + (assoc [this k v] (.vec_assoc org k v)) + (assocN [this i v] (.vec_assocN org i v)) + (peek [this] (.vec_peek org)) + (pop [this] (.vec_pop org)) + (nth [this i] (.vec_nth org i)) + (nth [this i o] (.vec_nth org i o)) + (cons [this o] (.vec_cons org o)) + (equiv [this o] (.vec_equiv org o)) + (empty [this] (.vec_empty org)) + (iterator [this] (.vec_iterator org)) + (forEach [this c] (.vec_forEach org c)) + (seq [this] (StructVecSeq. this 0)) + (rseq [this] (.vec_rseq org)) + (count [this] (.struct_count org)) + (containsKey [this k] (.struct_containsKey org k)) + (valAt [this k] (.struct_valAt org k)) + (valAt [this k o] (.struct_valAt org k o)) + (entryAt [this k] (.struct_entryAt org k)) + (asMap [this] org) + (asVec [this] this)) + +(deftype MapWrap [^coffi.mem.IStructImpl org] + coffi.mem.IStruct clojure.lang.IPersistentMap clojure.lang.MapEquivalence java.util.Map + (cons [this o] (.map_cons org o)) + (equiv [this o] (.map_equiv org o)) + (empty [this] (.map_empty org)) + (iterator [this] (.map_iterator org)) + (^void forEach [this ^java.util.function.Consumer c] (.map_forEach org c)) + (^void forEach [this ^java.util.function.BiConsumer c] (.map_forEach org c)) + (seq [this] (StructMapSeq. org 0)) + (assoc [this k v] (.map_assoc org k v)) + (count [this] (.struct_count org)) + (containsKey [this k] (.struct_containsKey org k)) + (valAt [this k] (.struct_valAt org k)) + (valAt [this k o] (.struct_valAt org k o)) + (entryAt [this k] (.struct_entryAt org k)) + (assocEx [this k v] (.map_assocEx org k v)) + (without [this k] (.map_without org k)) + ;java.util.map implementations + (containsValue [this k] (.map_containsValue org k)) + (entrySet [this] (.map_entrySet org)) + (get [this k] (.map_get org k)) + (isEmpty [this] false) + (keySet [this] (.map_keySet org)) + (size [this] (.map_size org)) + (values [this] (.map_values org)) + ;conversion methods + (asMap [this] this) + (asVec [this] org) + ) + +(defn as-vec [^coffi.mem.IStruct struct] (.asVec struct)) +(defn as-map [^coffi.mem.IStruct struct] (.asMap struct)) + + +(defn- generate-struct-type [typename typed-member-symbols] + (let [members (map (comp keyword str) typed-member-symbols) + as-vec (vec (map (comp symbol name) members)) + as-map (into {} (map (fn [m] [m (symbol (name m))]) members))] + (letfn [(vec-length [] (list 'length ['this] (count members))) + (vec-assoc [] (list 'assoc ['this 'i 'value] (list `assoc as-vec 'i 'value))) + (vec-assocN [] (list 'assocN ['this 'i 'value] (list `assoc 'i as-vec 'value))) + (vec-peek [] (list 'peek ['this] (first as-vec))) + (vec-pop [] (list 'pop ['this] (vec (rest as-vec)))) + (vec-nth [] (list 'nth ['this 'i] (concat [`case 'i] (interleave (range) as-vec)))) + (vec-nth-2 [] (list 'nth ['this 'i 'o] (concat [`case 'i] (interleave (range) as-vec) ['o]))) + (vec-cons [] (list 'cons ['this 'o] (vec (cons 'o as-vec)))) + (vec-equiv [] (list 'equiv ['this 'o] (list `= as-vec 'o))) + (vec-empty [] (list 'empty ['this] [])) + (vec-iterator [] (list 'iterator ['this] (list `StructVecIterator. 'this (count members) 0))) + (vec-foreach [] (concat ['forEach ['this 'action]] (partition 2 (interleave (repeat 'action) as-vec)))) + (vec-seq [] (list 'seq ['this] (list `StructVecSeq. 'this 0))) + (vec-rseq [] (list 'rseq ['this] (list `seq (vec (reverse as-vec))))) + + (s-count [] (list 'count ['this] (count members))) + (s-containsKey [] (list 'containsKey ['this 'k] (list `if (list `number? 'k) (list `and (list `>= 'k 0) (list `< 'k (count members)) true) (list `case 'k (seq members) true false)))) + (s-valAt [] (list 'valAt ['this 'k] (concat [`case 'k] (interleave (range) as-vec) (interleave members as-vec) [nil]))) + (s-valAt-2 [] (list 'valAt ['this 'k 'o] (concat [`case 'k] (interleave (range) as-vec) (interleave members as-vec) ['o]))) + (s-entryAt [] (list 'entryAt ['this 'k] (list `let ['val-or-nil (concat [`case 'k] (interleave (range) as-vec) (interleave members as-vec) [nil])] (list `if 'val-or-nil (list `clojure.lang.MapEntry/create 'k 'val-or-nil) nil)))) + + (map-assoc [] (list 'assoc ['this 'i 'value] (list `assoc as-map 'i 'value))) + (map-assocEx [] (list 'assocEx ['this 'i 'value] (list `if (list (set members) 'i) (list `throw (list `Exception. "key already exists")) (assoc as-map 'i 'value)))) + (map-without [] (list 'without ['this 'k] (list `dissoc as-map (list `if (list `number? 'k) (list (vec members) 'k) 'k)))) + (map-cons [] (list 'cons ['this 'o] `(if (instance? clojure.lang.MapEntry ~'o) ~(conj as-map [`(.key ~(with-meta 'o {:tag 'clojure.lang.MapEntry})) `(.val ~(with-meta 'o {:tag 'clojure.lang.MapEntry}))]) (if (instance? clojure.lang.IPersistentVector ~'o) ~(conj as-map [`(.nth ~(with-meta 'o {:tag 'clojure.lang.IPersistentVector}) 0) `(.nth ~(with-meta 'o {:tag 'clojure.lang.IPersistentVector}) 1)]) (.cons ~(with-meta 'o {:tag 'clojure.lang.IPersistentMap}) ~as-map))))) + (map-equiv [] (list 'equiv ['this 'o] (list `= as-map 'o))) + (map-empty [] (list 'empty ['this] {})) + (map-iterator [] (list 'iterator ['this] (list '.iterator as-map))) + (map-foreachConsumer [] (concat [(with-meta 'forEach {:tag 'void}) ['this (with-meta 'action {:tag 'java.util.function.Consumer}) ]] (partition 2 (interleave (repeat 'action) as-map)))) + (map-foreachBiConsumer [] (concat [(with-meta 'forEach {:tag 'void}) ['this (with-meta 'action {:tag 'java.util.function.BiConsumer})]] (partition 3 (flatten (interleave (repeat 'action) (seq as-map)))))) + (map-seq [] (list 'seq ['this] (list `StructMapSeq. 'this 0))) + (invoke1 [] (list 'invoke ['this 'arg1] (concat [`case 'arg1] (interleave (range) as-vec) (interleave members as-vec) [nil]))) + (invoke2 [] (list 'invoke ['this 'arg1 'arg2] (concat [`case 'arg1] (interleave (range) as-vec) (interleave members as-vec) ['arg2]))) + (applyTo [] (list 'applyTo ['this 'arglist] (concat [`case (list `first 'arglist)] (interleave (range) as-vec) (interleave members as-vec) [(list `if (list `.next 'arglist) (list `.first (list `.next 'arglist)) nil)]))) + ;structimpl utility function + (s-nth-key [] (list 'nthKey ['this 'i] (concat [`case 'i] (interleave (range) members)))) + ;java.util.Map implementations + (map-contains-value [] (list 'containsValue ['this 'val] (list `some (set as-vec) 'val))) + (map-entrySet [] (list 'entrySet ['this] (set (map (fn [[k v]] (list `clojure.lang.MapEntry/create k v)) (partition 2 (interleave members as-vec)))))) + (map-get [] (cons 'get (rest (s-valAt)))) + (map-isEmpty [] (list 'isEmpty ['this] false)) + (map-keySet [] (list 'keySet ['this] (set members))) + (map-size [] (list 'size ['this] (count members))) + (map-values [] (list 'values ['this] as-vec)) + + (map-methods [] [(map-without) (map-cons) (map-equiv) (map-empty) (map-iterator) (map-foreachConsumer) #_(map-foreachBiConsumer) (map-seq) (map-assoc) (map-assocEx) (map-contains-value) (map-entrySet) (map-get) (map-isEmpty) (map-keySet) (map-size) (map-values)]) + (vec-methods [] [(vec-length) (vec-assoc) (vec-assocN) (vec-peek) (vec-pop) (vec-nth) (vec-nth-2) (vec-cons) (vec-equiv) (vec-empty) (vec-iterator) (vec-foreach) (vec-seq) (vec-rseq)]) + (struct-methods [] [(s-count) (s-containsKey) (s-valAt) (s-valAt-2) (s-entryAt)]) + (prefix-methods [prefix ms] (map (fn [[method-name & tail]] (cons (with-meta (symbol (str prefix method-name)) (meta method-name)) tail)) ms)) + (impl-methods [] (concat (prefix-methods "map_" (map-methods)) (prefix-methods "vec_" (vec-methods)) (prefix-methods "struct_" (struct-methods))))] + `(deftype ~(symbol (name typename)) ~(vec typed-member-symbols) + coffi.mem.IStruct + ~@(struct-methods) + coffi.mem.IStructImpl + ~@(impl-methods) + clojure.lang.IPersistentMap + clojure.lang.MapEquivalence + java.util.Map + ~@(map-methods) + clojure.lang.IFn + ~(s-nth-key) + ~(invoke1) + ~(invoke2) + + (~'asMap [~'this] ~'this) + (~'asVec [~'this] (VecWrap. ~'this)))))) + +(load "layout") + +(defmacro defstruct + "Defines a struct type. all members need to be supplied in pairs of `member-name coffi-type`. + + This creates needed serialization and deserialization implementations for the new type. + + The typenames have to be coffi typenames, such as `:coffi.mem/int` or `[:coffi.mem/array :coffi.mem/byte 3]`. + Arrays are wrapped with vectors by default. If you want to use raw java arrays the array type has to be supplied with the option `:raw? true`, for example like this `[:coffi.mem/array :coffi.mem/byte 3 :raw? true]` + " + {:style/indent [:defn]} + [typename members] + (let [invalid-typenames (filter #(try (c-layout (second %)) nil (catch Exception e (second %))) (partition 2 members))] + (cond + (odd? (count members)) (throw (Exception. "uneven amount of members supplied. members have to be typed and are required to be supplied in the form of `member-name typename`. the typename has to be coffi typename, like `:coffi.mem/int` or `[:coffi.mem/array :coffi.mem/byte 3]`")) + (seq invalid-typenames) (throw (Exception. (str "invalid typename/s " (print-str invalid-typenames) ". typename has to be coffi typename, like `:coffi.mem/int` or `[:coffi.mem/array :coffi.mem/byte 3]`. The type/s you referenced also might not be defined. In case of a custom type, ensure that you use the correctly namespaced keyword to refer to it."))) + :else + (let [coffi-typename (keyword (str *ns*) (str typename)) + typed-symbols (->> + members + (partition 2 2) + (map (fn [[sym type]] (with-meta sym {:tag (coffitype->typename type)}))) + (vec)) + struct-layout-raw [::struct + (->> + members + (partition 2 2) + (map vec) + (map #(update % 0 keyword)) + (map vec) + (vec))] + struct-layout (coffi.layout/with-c-layout struct-layout-raw) + segment-form (with-meta 'segment {:tag 'java.lang.foreign.MemorySegment})] + (if (resolve typename) (ns-unmap *ns* typename)) + (defmethod c-layout coffi-typename [_] (c-layout struct-layout)) + (register-new-struct-deserialization coffi-typename struct-layout) + (register-new-struct-serialization coffi-typename struct-layout) + `(do + ~(generate-struct-type typename typed-symbols) + (defmethod c-layout ~coffi-typename [~'_] (c-layout (coffi.layout/with-c-layout ~struct-layout-raw))) + (register-new-struct-deserialization ~coffi-typename (coffi.layout/with-c-layout ~struct-layout-raw)) + (register-new-struct-serialization ~coffi-typename (coffi.layout/with-c-layout ~struct-layout-raw)) + (defmethod deserialize-from ~coffi-typename ~[segment-form '_type] + ~(generate-deserialize coffi-typename 0 segment-form)) + (defmethod serialize-into ~coffi-typename ~[(with-meta 'source-obj {:tag typename}) '_type segment-form '_] + ~(generate-serialize coffi-typename (with-meta 'source-obj {:tag typename}) 0 segment-form)) + (defmethod clojure.pprint/simple-dispatch ~typename [~'obj] (clojure.pprint/simple-dispatch (into {} ~'obj))) + (defmethod clojure.core/print-method ~typename [~'obj ~'writer] (print-simple (into {} ~'obj) ~'writer))))))) + + diff --git a/test/c/ffi_test.c b/test/c/ffi_test.c index c8e3784..7551724 100644 --- a/test/c/ffi_test.c +++ b/test/c/ffi_test.c @@ -100,3 +100,24 @@ void free_variable_length_array(float *arr) { freed = 1; free(arr); } + +typedef struct complextype { + Point x; + char y; + int z[4]; + char *w; +} ComplexType; + +ComplexType complexTypeTest(ComplexType a) { + ComplexType ret = {}; + ret.x = a.x; + ret.x.x++; + ret.x.y++; + ret.y = a.y-1; + ret.z[0] = a.z[0]; + ret.z[1] = a.z[1]; + ret.z[2] = a.z[2]; + ret.z[3] = a.z[3]; + ret.w = "hello from c"; + return ret; +} diff --git a/test/clj/coffi/ffi_test.clj b/test/clj/coffi/ffi_test.clj index 45cc154..7fdc489 100644 --- a/test/clj/coffi/ffi_test.clj +++ b/test/clj/coffi/ffi_test.clj @@ -3,7 +3,8 @@ [clojure.test :as t] [coffi.ffi :as ffi] [coffi.layout :as layout] - [coffi.mem :as mem])) + [coffi.mem :as mem] + [clojure.pprint])) (ffi/load-library "target/ffi_test.so") @@ -103,3 +104,33 @@ (free-variable-length-array* floats-addr)))))] (t/is (not (zero? @freed?))) (t/is (= floats (mapv #(* (float 1.5) %) (range (count floats))))))) + +(mem/defstruct Point [x ::mem/float y ::mem/float]) + +(t/deftest can-call-with-defstruct + (t/is (= {:x 2.0 :y 2.0} + ((ffi/cfn "add_points" [::Point ::Point] ::Point) (Point. 1 2) (Point. 1 0))))) + +(mem/defstruct AlignmentTest [a ::mem/char x ::mem/double y ::mem/float]) + +(t/deftest padding-matches-defstruct + (t/is (= ((ffi/cfn "get_struct" [] ::AlignmentTest)) + {:a \x + :x 3.14 + :y 42.0}))) + +(mem/defstruct ComplexType [x ::Point y ::mem/byte z [::mem/array ::mem/int 4 :raw? true] w ::mem/c-string]) + +(t/deftest can-call-with-complex-defstruct + (t/are [x y] (= x (y ((ffi/cfn "complexTypeTest" [::ComplexType] ::ComplexType) + (ComplexType. (Point. 2 3) 4 (int-array [5 6 7 8]) "hello from clojure")))) + {:x {:x 3.0 :y 4.0} :y 3 :w "hello from c"} #(dissoc % :z) + [5 6 7 8] (comp vec :z))) + +(mem/defstruct ComplexTypeWrapped [x ::Point y ::mem/byte z [::mem/array ::mem/int 4] w ::mem/c-string]) + +(t/deftest can-call-with-wrapped-complex-defstruct + (t/are [x y] (= x (y ((ffi/cfn "complexTypeTest" [::ComplexTypeWrapped] ::ComplexTypeWrapped) + (ComplexTypeWrapped. (Point. 2 3) 4 (int-array [5 6 7 8]) "hello from clojure")))) + {:x {:x 3.0 :y 4.0} :y 3 :w "hello from c"} #(dissoc % :z) + [5 6 7 8] (comp vec :z))) diff --git a/test/clj/coffi/mem_test.clj b/test/clj/coffi/mem_test.clj index b52c1eb..d3d4736 100644 --- a/test/clj/coffi/mem_test.clj +++ b/test/clj/coffi/mem_test.clj @@ -2,7 +2,6 @@ (:require [clojure.test :as t] [coffi.ffi :as ffi] - [coffi.layout :as layout] [coffi.mem :as mem]) (:import (java.lang.foreign @@ -29,4 +28,108 @@ (t/is (instance? MemorySegment (mem/serialize "this is a string" ::mem/c-string)))) +(t/deftest can-define-struct + (t/is + (eval + `(mem/defstruct ~'TestType [~'a ::mem/int ~'b ::mem/byte])))) + +(mem/defstruct TestType [a ::mem/int b ::mem/byte c ::mem/short]) + +(t/deftest can-initialize-struct + (t/is (TestType. 5 10 15))) + +(t/deftest can-use-common-map-functions + (let [v1 (TestType. 5 10 15) + v2 (TestType. 6 11 16)] + (t/are [x y] (= x (y v1)) + 5 :a + 10 :b + 15 :c + 5 (fn [v] (v :a)) + 10 (fn [v] (v :b)) + 15 (fn [v] (v :c)) + 5 #(get % :a) + 10 #(get % :b) + 15 #(get % :c) + 20 #(get % :d 20) + nil #(get % :d) + [:a :b :c] keys + [5 10 15] vals + {:a 5 :c 15} #(dissoc % :b) + {:a 5 :b 10 :c 0} #(assoc % :c 0) + {:a 5 :b 10 :c 15 :d 20} #(assoc % :d 20) + [[:a 5] [:b 10] [:c 15]] seq + {:a 5 :b 10 :c 15 :d 20} #(merge % {:d 20}) + {:a [5 6] :b [10 11] :c [15 16]} #(merge-with vector % {:a 6 :b 11 :c 16}) + {:a [5 6] :b [10 11] :c [15 16]} #(merge-with vector % v2) + [:a 5] #(find % :a) + nil #(find % :d) + {:a 5 :b 10 :c 15} identity + v1 identity + v1 (fn [s] {:a 5 :b 10 :c 15})))) + +(t/deftest can-serialize-struct-type + (t/is + (instance? MemorySegment (mem/serialize (TestType. 5 10 15) ::TestType)))) + +(t/deftest can-deserialize-struct-type + (t/is + (= {:a 5 :b 10 :c 15} + (mem/deserialize (mem/serialize (TestType. 5 10 15) ::TestType) ::TestType)))) + +(mem/defstruct NestedTestType [x ::mem/int y ::mem/byte z ::TestType]) + +(t/deftest can-instantiated-nested-structs + (t/is + (= {:x 5 :y 6 :z {:a 5 :b 10 :c 15}} + (NestedTestType. 5 6 (TestType. 5 10 15))))) + +(t/deftest can-define-structs-with-array-members + (t/is + (eval + `(mem/defstruct ~'ArrayTestType [~'x ::mem/int ~'y ::mem/byte ~'z [::mem/array ::mem/int 4 :raw? true]])))) + +(mem/defstruct ArrayTestType [x ::mem/int y ::mem/byte z [::mem/array ::mem/int 4 :raw? true]]) + +(t/deftest can-instantiated-array-member-structs + (t/are [x y z] (z x (y (ArrayTestType. 5 6 (int-array [1 2 3 4])))) + {:x 5 :y 6} #(dissoc % :z) = + (int-array [1 2 3 4]) :z java.util.Arrays/equals)) + +(t/deftest can-serialize-array-struct + (t/is + (= [5 6 1 2 3 4] + (vec (filter #(not= 0 %) (vec (.toArray (mem/serialize (ArrayTestType. 5 6 (int-array [1 2 3 4])) ::ArrayTestType) mem/byte-layout))))))) + +(t/deftest can-serialize-deserialize-array-struct + (t/is + (java.util.Arrays/equals + (int-array [1 2 3 4]) + (.z (mem/deserialize (mem/serialize (ArrayTestType. 5 6 (int-array [1 2 3 4])) ::ArrayTestType) ::ArrayTestType))))) + +(mem/defstruct ComplexTestType [x [::mem/array ::ArrayTestType 4 :raw? true] y ::mem/byte z [::mem/array ::mem/int 4 :raw? true] w ::NestedTestType]) + +(t/deftest can-serialize-deserialize-complex-struct-type + (t/is + (let [x (object-array (map #(ArrayTestType. % % (int-array (range 4))) (range 4))) + y 12 + z (int-array (range 4)) + w (NestedTestType. 5 6 (TestType. 5 10 15))] + (-> + (ComplexTestType. x y z w) + (mem/serialize ::ComplexTestType) + (mem/deserialize ::ComplexTestType))))) + +(mem/defstruct ComplexTestTypeWrapped [x [::mem/array ::ArrayTestType 4] y ::mem/byte z [::mem/array ::mem/int 4] w ::NestedTestType]) + +(t/deftest can-serialize-deserialize-complex-wrapped-struct-type + (t/is + (let [x (vec (map #(ArrayTestType. % % (int-array (range 4))) (range 4))) + y 12 + z (vec (range 4)) + w (NestedTestType. 5 6 (TestType. 5 10 15))] + (-> + (ComplexTestTypeWrapped. x y z w) + (mem/serialize ::ComplexTestTypeWrapped) + (mem/deserialize ::ComplexTestTypeWrapped)))))