diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cfbdb1..a5cfe8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ 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] +### Added +- New `coffi.layout` namespace with support for forcing C layout rules on structs + ### Fixed - C-characters were being read as UTF-16 rather than ASCII code points diff --git a/src/clj/coffi/layout.clj b/src/clj/coffi/layout.clj new file mode 100644 index 0000000..047bd62 --- /dev/null +++ b/src/clj/coffi/layout.clj @@ -0,0 +1,30 @@ +(ns coffi.layout + "Functions for adjusting the layout of structs." + (:require + [coffi.mem :as mem])) + +(defn with-c-layout + "Forces a struct specification to C layout rules. + + This will add padding fields between fields to match C alignment + requirements." + [struct-spec] + (let [aligned-fields + (loop [offset 0 + aligned-fields [] + fields (nth struct-spec 1)] + (if (seq fields) + (let [[[_ type :as field] & fields] fields + size (mem/size-of type) + r (rem offset (mem/align-of type))] + (recur (cond-> (+ offset size) + (pos? r) (+ (- size r))) + (cond-> aligned-fields + (pos? r) (conj [::padding [::mem/padding (- size r)]]) + :always (conj field)) + fields)) + (let [strongest-alignment (mem/align-of struct-spec) + r (rem offset strongest-alignment)] + (cond-> aligned-fields + (pos? r) (conj [::padding [::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 cf8bf38..e647268 100644 --- a/src/clj/coffi/mem.clj +++ b/src/clj/coffi/mem.clj @@ -349,6 +349,11 @@ [type] (.byteSize ^MemoryLayout (c-layout type))) +(defn align-of + "The alignment in bytes of the given `type`." + [type] + (.byteAlignment ^MemoryLayout (c-layout type))) + (defn alloc-instance "Allocates a memory segment for the given `type`." ([type] (alloc-instance type (connected-scope))) @@ -776,7 +781,8 @@ aliased type." {:style/indent [:defn]} [new-type aliased-type] - (if (primitive-type aliased-type) + (if (and (s/valid? ::type aliased-type) + (primitive-type aliased-type)) `(let [aliased# ~aliased-type] (defmethod primitive-type ~new-type [_type#] @@ -799,4 +805,4 @@ (deserialize-from segment# aliased#))))) (s/fdef defalias :args (s/cat :new-type qualified-keyword? - :aliased-type ::type)) + :aliased-type any?)) diff --git a/test/c/ffi_test.c b/test/c/ffi_test.c index 4d9d520..8235c1b 100644 --- a/test/c/ffi_test.c +++ b/test/c/ffi_test.c @@ -48,3 +48,18 @@ StringFactory get_downcall(int whichString) { return 0; } } + +typedef struct alignment_test { + char a; + double x; + float y; +} AlignmentTest; + +AlignmentTest get_struct() { + AlignmentTest ret = {}; + ret.a = 'x'; + ret.x = 3.14; + ret.y = 42.0; + + return ret; +} diff --git a/test/clj/coffi/ffi_test.clj b/test/clj/coffi/ffi_test.clj index 122e075..d7be985 100644 --- a/test/clj/coffi/ffi_test.clj +++ b/test/clj/coffi/ffi_test.clj @@ -1,8 +1,9 @@ (ns coffi.ffi-test (:require [clojure.test :as t] - [coffi.mem :as mem] - [coffi.ffi :as ffi])) + [coffi.ffi :as ffi] + [coffi.layout :as layout] + [coffi.mem :as mem])) (ffi/load-library "target/ffi_test.so") @@ -30,3 +31,16 @@ (t/is (= ((ffi/cfn "upcall_test" [[::ffi/fn [] ::mem/c-string]] ::mem/c-string) (fn [] "hello")) "hello"))) + +(mem/defalias ::alignment-test + (layout/with-c-layout + [::mem/struct + [[:a ::mem/char] + [:x ::mem/double] + [:y ::mem/float]]])) + +(t/deftest padding-matches + (t/is (= (dissoc ((ffi/cfn "get_struct" [] ::alignment-test)) ::layout/padding) + {:a \x + :x 3.14 + :y 42.0})))