mirror of
git://git.code.sf.net/p/sbcl/sbcl
synced 2026-09-10 07:26:40 -04:00
Support struct-by-value for x86-64 and ARM64 foreign calls
Add the ability to pass and return C structs by value in alien function calls and callbacks on x86-64 linux and ARM64 darwin. Note - x86-64: eightbyte classification with INTEGER/SSE register classes - ARM64: HFA detection for float aggregates, GPR pairs for small structs Large structs (>16 bytes) use hidden pointer passing. Small structs are unpacked from registers into heap on return. Callbacks preserve the hidden return pointer across the Lisp call and copy results to the caller-provided destination.
This commit is contained in:
parent
6b7b3b5524
commit
e9baa62bff
|
|
@ -738,17 +738,11 @@ The type of @code{alien-function} must be @code{(alien (function
|
||||||
...))} or @code{(alien (* (function ...)))}. The function type is
|
...))} or @code{(alien (* (function ...)))}. The function type is
|
||||||
used to determine how to call the function (as though it was declared
|
used to determine how to call the function (as though it was declared
|
||||||
with a prototype.) The type need not be known at compile time, but
|
with a prototype.) The type need not be known at compile time, but
|
||||||
only known-type calls are efficiently compiled. Limitations:
|
only known-type calls are efficiently compiled.
|
||||||
|
|
||||||
@itemize
|
On unix-like x86-64 and ARM64 systems, structures may be passed and
|
||||||
|
returned by value. The implementation follows the System V AMD64 ABI
|
||||||
@item
|
and AAPCS64 specifications respectively.
|
||||||
Structure type return values are not implemented.
|
|
||||||
|
|
||||||
@item
|
|
||||||
Passing of structures by value is not implemented.
|
|
||||||
|
|
||||||
@end itemize
|
|
||||||
|
|
||||||
@end defun
|
@end defun
|
||||||
|
|
||||||
|
|
@ -1034,6 +1028,8 @@ an interface for calling into Lisp as a shared library directly from C.
|
||||||
|
|
||||||
The @code{define-alien-callable} macro wraps Lisp code and creates a C
|
The @code{define-alien-callable} macro wraps Lisp code and creates a C
|
||||||
foreign function which can be called with the C calling convention.
|
foreign function which can be called with the C calling convention.
|
||||||
|
On x86-64 and ARM64, callbacks may receive and return structures by
|
||||||
|
value.
|
||||||
|
|
||||||
@include macro-sb-alien-define-alien-callable.texinfo
|
@include macro-sb-alien-define-alien-callable.texinfo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,12 +100,16 @@
|
||||||
|
|
||||||
(defun alien-callback-argument-bytes (spec env)
|
(defun alien-callback-argument-bytes (spec env)
|
||||||
(let ((type (parse-alien-type spec env)))
|
(let ((type (parse-alien-type spec env)))
|
||||||
(if (or (alien-integer-type-p type)
|
(cond ((or (alien-integer-type-p type)
|
||||||
(alien-float-type-p type)
|
(alien-float-type-p type)
|
||||||
(alien-pointer-type-p type)
|
(alien-pointer-type-p type)
|
||||||
(alien-system-area-pointer-type-p type))
|
(alien-system-area-pointer-type-p type))
|
||||||
(ceiling (alien-type-word-aligned-bits type) sb-vm:n-byte-bits)
|
(ceiling (alien-type-word-aligned-bits type) sb-vm:n-byte-bits))
|
||||||
(error "Unsupported callback argument type: ~A" type))))
|
;; Struct types: return the struct size rounded up to word alignment
|
||||||
|
((alien-record-type-p type)
|
||||||
|
(ceiling (alien-type-word-aligned-bits type) sb-vm:n-byte-bits))
|
||||||
|
(t
|
||||||
|
(error "Unsupported callback argument type: ~A" type)))))
|
||||||
|
|
||||||
(defun enter-alien-callback (index arguments return)
|
(defun enter-alien-callback (index arguments return)
|
||||||
(declare (optimize speed (safety 0)))
|
(declare (optimize speed (safety 0)))
|
||||||
|
|
@ -201,6 +205,12 @@
|
||||||
`(unsigned
|
`(unsigned
|
||||||
,(alien-type-word-aligned-bits result-type))
|
,(alien-type-word-aligned-bits result-type))
|
||||||
`(unsigned-byte ,(alien-type-bits result-type)))))
|
`(unsigned-byte ,(alien-type-bits result-type)))))
|
||||||
|
;; For struct return types, wrap in WITH-OUTER-ALIEN-STACK-CLEANUP
|
||||||
|
;; so inner WITH-ALIEN forms defer cleanup to this outer binding.
|
||||||
|
;; This ensures allocations survive until the struct is copied.
|
||||||
|
((alien-record-type-p result-type)
|
||||||
|
`(with-outer-alien-stack-cleanup
|
||||||
|
,(store (unparse-alien-type result-type) nil)))
|
||||||
(t
|
(t
|
||||||
(store (unparse-alien-type result-type) nil))))))
|
(store (unparse-alien-type result-type) nil))))))
|
||||||
0))))
|
0))))
|
||||||
|
|
|
||||||
|
|
@ -135,14 +135,83 @@
|
||||||
(simple-string
|
(simple-string
|
||||||
(string-to-c-string ,value
|
(string-to-c-string ,value
|
||||||
(c-string-external-format ,type)))))))
|
(c-string-external-format ,type)))))))
|
||||||
;;;; Struct Support (or the lack thereof)
|
|
||||||
;; NOTE: RECORD follows the hierarchy of RECORD -> MEM-BLOCK -> ALIEN-VALUE -> SAP.
|
;;;; Struct Return-by-Value Support
|
||||||
;; All platforms have passing SAP defined, which causes passing record by value
|
|
||||||
;; to silently corrupt.
|
;;; Classification categories for struct fields/eightbytes per ABI.
|
||||||
;; -- Rongcui
|
;;; These values have architecture-specific semantics:
|
||||||
(define-alien-type-method (record :arg-tn) (type state)
|
;;;
|
||||||
(declare (ignore type state))
|
;;; :integer - Pass/return in general-purpose registers (RAX/RDX on x86-64,
|
||||||
(error "Passing structs by value is unsupported on this platform."))
|
;;; x0/x1 on ARM64). Used for integer, pointer, and mixed types.
|
||||||
(define-alien-type-method (record :result-tn) (type state)
|
;;;
|
||||||
(declare (ignore type state))
|
;;; :single - ARM64 HFA (Homogeneous Floating-point Aggregate) only.
|
||||||
(error "Returning structs by value is unsupported on this platform."))
|
;;; Pass/return in single-precision FP registers (s0-s3).
|
||||||
|
;;; x86-64 never uses this; single-floats become :double (SSE class).
|
||||||
|
;;;
|
||||||
|
;;; :double - Pass/return in floating-point/SSE registers.
|
||||||
|
;;; On x86-64: SSE class (XMM0/XMM1) for both single and double floats.
|
||||||
|
;;; On ARM64: HFA double-precision (d0-d3).
|
||||||
|
;;;
|
||||||
|
;;; :memory - Struct too large for registers; pass/return via hidden pointer.
|
||||||
|
;;; x86-64: hidden pointer in RDI, returned in RAX.
|
||||||
|
;;; ARM64: hidden pointer in x8.
|
||||||
|
(deftype struct-class () '(member :integer :single :double :memory))
|
||||||
|
|
||||||
|
(defstruct (struct-classification (:copier nil))
|
||||||
|
;; List of register slot classifications
|
||||||
|
;; Each element represents one register's worth of data
|
||||||
|
(register-slots nil :type list)
|
||||||
|
;; Total size in bytes
|
||||||
|
(size 0 :type (unsigned-byte 32))
|
||||||
|
;; Required alignment
|
||||||
|
(alignment 1 :type (unsigned-byte 16))
|
||||||
|
;; Whether this struct must be returned via hidden pointer
|
||||||
|
(memory-p nil :type boolean))
|
||||||
|
|
||||||
|
;;; Main entry point: classify a struct type for ABI compliance
|
||||||
|
;;; Returns: (values in-registers-p register-slots size)
|
||||||
|
;;; in-registers-p - T if struct can be returned in registers
|
||||||
|
;;; register-slots - list of slot classes for each register
|
||||||
|
;;; size - total size in bytes (NIL if not a struct)
|
||||||
|
(defun struct-return-info (alien-type)
|
||||||
|
"Classify how a struct should be returned according to platform ABI.
|
||||||
|
Returns (values in-registers-p register-slots size) or (values nil nil nil) for non-structs."
|
||||||
|
(declare (ignorable alien-type))
|
||||||
|
#+(and arm64 (not sb-xc-host))
|
||||||
|
(progn
|
||||||
|
(unless (alien-record-type-p alien-type)
|
||||||
|
(return-from struct-return-info (values nil nil nil)))
|
||||||
|
(let ((classification (sb-vm::classify-struct-aapcs64 alien-type)))
|
||||||
|
(when classification
|
||||||
|
(values (not (struct-classification-memory-p classification))
|
||||||
|
(struct-classification-register-slots classification)
|
||||||
|
(struct-classification-size classification)))))
|
||||||
|
#+(and x86-64 (not sb-xc-host))
|
||||||
|
(progn
|
||||||
|
(unless (alien-record-type-p alien-type)
|
||||||
|
(return-from struct-return-info (values nil nil nil)))
|
||||||
|
(let ((classification (sb-vm::classify-struct-sysv-amd64 alien-type)))
|
||||||
|
(when classification
|
||||||
|
(values (not (struct-classification-memory-p classification))
|
||||||
|
(struct-classification-register-slots classification)
|
||||||
|
(struct-classification-size classification))))))
|
||||||
|
|
||||||
|
;;; Methods for struct by value - platform-specific implementations in
|
||||||
|
;;; compiler/{arch}/c-call.lisp define record-arg-tn and record-result-tn.
|
||||||
|
#+(and (or x86-64 arm64) (not sb-xc-host))
|
||||||
|
(progn
|
||||||
|
(declaim (ftype (function (t t) t) sb-vm::record-arg-tn sb-vm::record-result-tn))
|
||||||
|
(define-alien-type-method (record :arg-tn) (type state)
|
||||||
|
(sb-vm::record-arg-tn type state))
|
||||||
|
(define-alien-type-method (record :result-tn) (type state)
|
||||||
|
(sb-vm::record-result-tn type state)))
|
||||||
|
|
||||||
|
#-(and (or x86-64 arm64) (not sb-xc-host))
|
||||||
|
(progn
|
||||||
|
(define-alien-type-method (record :arg-tn) (type state)
|
||||||
|
(declare (ignore type state))
|
||||||
|
(error "Passing structs by value is unsupported on this platform."))
|
||||||
|
(define-alien-type-method (record :result-tn) (type state)
|
||||||
|
(declare (ignore type state))
|
||||||
|
(error "Returning structs by value is unsupported on this platform.")))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,23 @@ This is SETFable."
|
||||||
(datap (not (alien-fun-type-p alien-type))))
|
(datap (not (alien-fun-type-p alien-type))))
|
||||||
`(%alien-value (foreign-symbol-sap ,alien-name ,datap) 0 ',alien-type)))
|
`(%alien-value (foreign-symbol-sap ,alien-name ,datap) 0 ',alien-type)))
|
||||||
|
|
||||||
|
;;; Allow callback struct returns to signal that inner WITH-ALIEN
|
||||||
|
;;; forms should defer *alien-stack-pointer* cleanup. When a callback
|
||||||
|
;;; returns a struct by value, the struct is typically constructed
|
||||||
|
;;; using WITH-ALIEN. Without this mechanism, the WITH-ALIEN cleanup
|
||||||
|
;;; would run before the struct data is copied to the return area,
|
||||||
|
;;; causing corruption.
|
||||||
|
;;;
|
||||||
|
;;; WITH-OUTER-ALIEN-STACK-CLEANUP establishes a lexical marker (via
|
||||||
|
;;; symbol-macrolet) that WITH-ALIEN detects during its macroexpansion
|
||||||
|
;;; using macroexpand-1.
|
||||||
|
(defmacro with-outer-alien-stack-cleanup (&body body)
|
||||||
|
"Establish an outer *alien-stack-pointer* binding and signal to inner WITH-ALIEN
|
||||||
|
forms that they should skip their own cleanup. Used by callback struct returns."
|
||||||
|
`(symbol-macrolet ((%in-outer-alien-stack-cleanup-context% t))
|
||||||
|
(let ((sb-c:*alien-stack-pointer* sb-c:*alien-stack-pointer*))
|
||||||
|
,@body)))
|
||||||
|
|
||||||
(defmacro with-alien (bindings &body body &environment env)
|
(defmacro with-alien (bindings &body body &environment env)
|
||||||
"Establish some local alien variables. Each BINDING is of the form:
|
"Establish some local alien variables. Each BINDING is of the form:
|
||||||
VAR TYPE [ ALLOCATION ] [ INITIAL-VALUE | EXTERNAL-NAME ]
|
VAR TYPE [ ALLOCATION ] [ INITIAL-VALUE | EXTERNAL-NAME ]
|
||||||
|
|
@ -178,6 +195,15 @@ This is SETFable."
|
||||||
,(append *new-auxiliary-types*
|
,(append *new-auxiliary-types*
|
||||||
(auxiliary-type-definitions env))))
|
(auxiliary-type-definitions env))))
|
||||||
,@(cond
|
,@(cond
|
||||||
|
;; When in callback struct return context, skip the binding.
|
||||||
|
;; The outer WITH-OUTER-ALIEN-STACK-CLEANUP already established
|
||||||
|
;; a single *alien-stack-pointer* binding that will clean up all
|
||||||
|
;; allocations after the struct is copied to the result area.
|
||||||
|
;; Detect this by checking for the %in-outer-alien-stack-cleanup-context%
|
||||||
|
;; symbol-macrolet marker in the lexical environment.
|
||||||
|
((and bind-alien-stack-pointer
|
||||||
|
(nth-value 1 (macroexpand-1 '%in-outer-alien-stack-cleanup-context% env)))
|
||||||
|
body)
|
||||||
(bind-alien-stack-pointer
|
(bind-alien-stack-pointer
|
||||||
;; The LET IR1-translator will actually turn this into
|
;; The LET IR1-translator will actually turn this into
|
||||||
;; RESTORING-NSP on #-c-stack-is-control-stack to avoid
|
;; RESTORING-NSP on #-c-stack-is-control-stack to avoid
|
||||||
|
|
|
||||||
|
|
@ -548,6 +548,30 @@
|
||||||
|
|
||||||
;;;; ALIEN-FUNCALL support
|
;;;; ALIEN-FUNCALL support
|
||||||
|
|
||||||
|
;;; Generate code to store struct register values to memory
|
||||||
|
#-sb-xc-host
|
||||||
|
(defun generate-struct-store-code (temps register-slots result-sap)
|
||||||
|
"Generate SETF forms to store register values to struct memory."
|
||||||
|
(let ((offset 0)
|
||||||
|
(stores nil)
|
||||||
|
(temp-idx 0))
|
||||||
|
(dolist (class register-slots)
|
||||||
|
(case class
|
||||||
|
(:integer
|
||||||
|
(push `(setf (sb-sys:sap-ref-64 ,result-sap ,offset) ,(nth temp-idx temps)) stores)
|
||||||
|
(incf offset 8)
|
||||||
|
(incf temp-idx))
|
||||||
|
(:double
|
||||||
|
(push `(setf (sb-sys:sap-ref-double ,result-sap ,offset) ,(nth temp-idx temps)) stores)
|
||||||
|
(incf offset 8)
|
||||||
|
(incf temp-idx))
|
||||||
|
;; :single is ARM64 HFA only - x86-64 classifies all floats as :double
|
||||||
|
(:single
|
||||||
|
(push `(setf (sb-sys:sap-ref-single ,result-sap ,offset) ,(nth temp-idx temps)) stores)
|
||||||
|
(incf offset 4)
|
||||||
|
(incf temp-idx))))
|
||||||
|
(nreverse stores)))
|
||||||
|
|
||||||
(deftransform alien-funcall ((function &rest args)
|
(deftransform alien-funcall ((function &rest args)
|
||||||
((alien (* t)) &rest t) *)
|
((alien (* t)) &rest t) *)
|
||||||
(let ((names (make-gensym-list (length args))))
|
(let ((names (make-gensym-list (length args))))
|
||||||
|
|
@ -576,29 +600,45 @@
|
||||||
(params param)
|
(params param)
|
||||||
(deports `(deport ,param ',arg-type))))
|
(deports `(deport ,param ',arg-type))))
|
||||||
;; Build BODY from the inside out.
|
;; Build BODY from the inside out.
|
||||||
(let ((return-type (alien-fun-type-result-type alien-type))
|
;; First, detect if this is a large struct return (hidden pointer)
|
||||||
;; Innermost, we DEPORT the parameters (e.g. by taking SAPs
|
(let* ((return-type (alien-fun-type-result-type alien-type))
|
||||||
;; to them) and do the call.
|
;; Check for large struct return (needs hidden pointer)
|
||||||
(body
|
#-sb-xc-host
|
||||||
;; If FUNCTION's source looks like
|
(large-struct-size
|
||||||
;; (%SAP-ALIEN (FOREIGN-SYMBOL-SAP "sym") #<anything>)
|
(multiple-value-bind (in-registers-p register-slots size)
|
||||||
;; then snarf out the string and use it as the funarg
|
(sb-alien::struct-return-info return-type)
|
||||||
;; unless the backend lacks the CALL-OUT-NAMED vop.
|
(declare (ignore register-slots))
|
||||||
`(%alien-funcall
|
(when (and size (not in-registers-p))
|
||||||
,(or (when-vop-existsp (:named call-out-named)
|
size)))
|
||||||
(when (lvar-matches function :fun-names '(%sap-alien)
|
#+sb-xc-host
|
||||||
:arg-count 2)
|
(large-struct-size nil)
|
||||||
(let ((sap (first (combination-args (lvar-use function)))))
|
;; For large struct returns, we need a gensym for the sret pointer
|
||||||
(when (lvar-matches sap :fun-names '(foreign-symbol-sap)
|
(sret-sap (when large-struct-size (gensym "SRET-SAP")))
|
||||||
:arg-count 1)
|
;; Innermost, we DEPORT the parameters (e.g. by taking SAPs
|
||||||
(let ((sym (first (combination-args (lvar-use sap)))))
|
;; to them) and do the call.
|
||||||
(when (and (constant-lvar-p sym)
|
(body
|
||||||
(stringp (lvar-value sym)))
|
;; If FUNCTION's source looks like
|
||||||
(setq ignore-fun t)
|
;; (%SAP-ALIEN (FOREIGN-SYMBOL-SAP "sym") #<anything>)
|
||||||
(lvar-value sym)))))))
|
;; then snarf out the string and use it as the funarg
|
||||||
`(deport function ',alien-type))
|
;; unless the backend lacks the CALL-OUT-NAMED vop.
|
||||||
',alien-type
|
`(%alien-funcall
|
||||||
,@(deports))))
|
,(or (when-vop-existsp (:named call-out-named)
|
||||||
|
(when (lvar-matches function :fun-names '(%sap-alien)
|
||||||
|
:arg-count 2)
|
||||||
|
(let ((sap (first (combination-args (lvar-use function)))))
|
||||||
|
(when (lvar-matches sap :fun-names '(foreign-symbol-sap)
|
||||||
|
:arg-count 1)
|
||||||
|
(let ((sym (first (combination-args (lvar-use sap)))))
|
||||||
|
(when (and (constant-lvar-p sym)
|
||||||
|
(stringp (lvar-value sym)))
|
||||||
|
(setq ignore-fun t)
|
||||||
|
(lvar-value sym)))))))
|
||||||
|
`(deport function ',alien-type))
|
||||||
|
',alien-type
|
||||||
|
;; For large struct returns, prepend sret-sap as first arg
|
||||||
|
;; IR2 will put it in the hidden pointer register (x8 on ARM64, RDI on x86-64)
|
||||||
|
,@(when sret-sap (list sret-sap))
|
||||||
|
,@(deports))))
|
||||||
;; Wrap that in a WITH-PINNED-OBJECTS to ensure the values
|
;; Wrap that in a WITH-PINNED-OBJECTS to ensure the values
|
||||||
;; the SAPs are taken for won't be moved by the GC. (If
|
;; the SAPs are taken for won't be moved by the GC. (If
|
||||||
;; needed: some alien types won't need it).
|
;; needed: some alien types won't need it).
|
||||||
|
|
@ -617,16 +657,45 @@
|
||||||
do (setf body
|
do (setf body
|
||||||
`(let ((,param (deport-alloc ,param ',arg-type)))
|
`(let ((,param (deport-alloc ,param ',arg-type)))
|
||||||
,body)))
|
,body)))
|
||||||
(if (alien-values-type-p return-type)
|
(cond
|
||||||
(collect ((temps) (results))
|
((alien-values-type-p return-type)
|
||||||
(dolist (type (alien-values-type-values return-type))
|
(collect ((temps) (results))
|
||||||
(let ((temp (gensym)))
|
(dolist (type (alien-values-type-values return-type))
|
||||||
(temps temp)
|
(let ((temp (gensym)))
|
||||||
(results `(naturalize ,temp ',type))))
|
(temps temp)
|
||||||
(setf body
|
(results `(naturalize ,temp ',type))))
|
||||||
`(multiple-value-bind ,(temps) ,body
|
(setf body
|
||||||
(values ,@(results)))))
|
`(multiple-value-bind ,(temps) ,body
|
||||||
(setf body `(naturalize ,body ',return-type)))
|
(values ,@(results))))))
|
||||||
|
;; Struct-by-value return handling
|
||||||
|
#-sb-xc-host
|
||||||
|
((multiple-value-bind (in-registers-p register-slots size)
|
||||||
|
(sb-alien::struct-return-info return-type)
|
||||||
|
(cond
|
||||||
|
;; Small struct: returned in registers, store to heap memory
|
||||||
|
(in-registers-p
|
||||||
|
(let* ((num-values (length register-slots))
|
||||||
|
(temps (loop repeat num-values collect (gensym)))
|
||||||
|
(result-sap (gensym "RESULT-SAP")))
|
||||||
|
(setf body
|
||||||
|
`(multiple-value-bind ,temps ,body
|
||||||
|
(let ((,result-sap (sb-alien::%make-alien ,size)))
|
||||||
|
,@(generate-struct-store-code temps register-slots result-sap)
|
||||||
|
(sb-alien::%sap-alien ,result-sap ',return-type))))
|
||||||
|
t))
|
||||||
|
;; Large struct: C expects hidden pointer (x8/RDI), returns it (x0/RAX)
|
||||||
|
;; sret-sap was already added to %alien-funcall args at the top
|
||||||
|
;; Here we wrap with allocation and return the sap-alien
|
||||||
|
((and size (not in-registers-p))
|
||||||
|
;; sret-sap was defined at the top of this let*
|
||||||
|
(setf body
|
||||||
|
`(let ((,sret-sap (sb-alien::%make-alien ,size)))
|
||||||
|
,body ; %alien-funcall with sret-sap as first arg
|
||||||
|
;; The callee wrote to sret-sap, return it as alien
|
||||||
|
(sb-alien::%sap-alien ,sret-sap ',return-type)))
|
||||||
|
t)))) ; close inner cond clause, inner cond, m-v-b, outer cond clause
|
||||||
|
(t
|
||||||
|
(setf body `(naturalize ,body ',return-type))))
|
||||||
;; Remember this frame to make sure that we can get back
|
;; Remember this frame to make sure that we can get back
|
||||||
;; to it later regardless of how the foreign stack looks
|
;; to it later regardless of how the foreign stack looks
|
||||||
;; like.
|
;; like.
|
||||||
|
|
@ -643,10 +712,26 @@
|
||||||
(unless (and (constant-lvar-p type)
|
(unless (and (constant-lvar-p type)
|
||||||
(alien-fun-type-p (lvar-value type)))
|
(alien-fun-type-p (lvar-value type)))
|
||||||
(error "Something is broken."))
|
(error "Something is broken."))
|
||||||
(let ((spec (compute-alien-rep-type
|
(let* ((result-type (alien-fun-type-result-type (lvar-value type)))
|
||||||
(alien-fun-type-result-type (lvar-value type))
|
(spec (compute-alien-rep-type result-type :result)))
|
||||||
:result)))
|
(cond
|
||||||
(if (eq spec '*) *wild-type* (values-specifier-type spec))))
|
;; For struct-by-value returns, derive the multiple-values type
|
||||||
|
;; based on the register slot classification
|
||||||
|
#-sb-xc-host
|
||||||
|
((multiple-value-bind (in-registers-p register-slots)
|
||||||
|
(sb-alien::struct-return-info result-type)
|
||||||
|
(when in-registers-p
|
||||||
|
;; Return VALUES type for the register values
|
||||||
|
(make-values-type
|
||||||
|
(mapcar (lambda (class)
|
||||||
|
(case class
|
||||||
|
(:integer (specifier-type '(unsigned-byte 64)))
|
||||||
|
(:double (specifier-type 'double-float))
|
||||||
|
(:single! (specifier-type 'single-float))
|
||||||
|
(t *universal-type*)))
|
||||||
|
register-slots)))))
|
||||||
|
(t
|
||||||
|
(if (eq spec '*) *wild-type* (values-specifier-type spec))))))
|
||||||
|
|
||||||
(defoptimizer (%alien-funcall ltn-annotate)
|
(defoptimizer (%alien-funcall ltn-annotate)
|
||||||
((function type &rest args) node)
|
((function type &rest args) node)
|
||||||
|
|
@ -679,8 +764,18 @@
|
||||||
(args #-arm args #+arm (reverse args))
|
(args #-arm args #+arm (reverse args))
|
||||||
#+c-stack-is-control-stack
|
#+c-stack-is-control-stack
|
||||||
(stack-pointer (make-stack-pointer-tn)))
|
(stack-pointer (make-stack-pointer-tn)))
|
||||||
(multiple-value-bind (nsp stack-frame-size arg-tns result-tns)
|
(multiple-value-bind (nsp stack-frame-size arg-tns result-tns
|
||||||
|
#+(or arm64 x86-64) large-struct-return-p)
|
||||||
(make-call-out-tns type)
|
(make-call-out-tns type)
|
||||||
|
;; For large struct returns, the first arg is the sret pointer
|
||||||
|
;; Extract it from args so it's not processed as a regular arg
|
||||||
|
;; Emit the VOP to set x8 (ARM64) or RDI (x86-64) just before
|
||||||
|
;; the call. Watch out for the kludge above, if anyone comes
|
||||||
|
;; along and writes sret for arm32.
|
||||||
|
(let ((sret-tn #+(or arm64 x86-64) (when large-struct-return-p
|
||||||
|
(lvar-tn call block (pop args)))
|
||||||
|
#-(or arm64 x86-64) nil))
|
||||||
|
(declare (ignorable sret-tn))
|
||||||
#+x86
|
#+x86
|
||||||
(vop set-fpu-word-for-c call block)
|
(vop set-fpu-word-for-c call block)
|
||||||
;; Save the stack pointer, it will get aligned and subtracting
|
;; Save the stack pointer, it will get aligned and subtracting
|
||||||
|
|
@ -762,6 +857,11 @@
|
||||||
(reference-tn-list (remove-if-not #'tn-p (flatten-list arg-tns)) nil))
|
(reference-tn-list (remove-if-not #'tn-p (flatten-list arg-tns)) nil))
|
||||||
(result-operands
|
(result-operands
|
||||||
(reference-tn-list (remove-if-not #'tn-p result-tns) t)))
|
(reference-tn-list (remove-if-not #'tn-p result-tns) t)))
|
||||||
|
;; For large struct returns, set the sret pointer register
|
||||||
|
;; (x8 on ARM64, RDI on x86-64) right before making the call
|
||||||
|
(when sret-tn
|
||||||
|
(when-vop-existsp (:named sb-vm::set-struct-return-pointer)
|
||||||
|
(vop sb-vm::set-struct-return-pointer call block sret-tn)))
|
||||||
(cond #+#.(cl:if (sb-c::vop-existsp :named sb-vm::call-out-named) '(and) '(or))
|
(cond #+#.(cl:if (sb-c::vop-existsp :named sb-vm::call-out-named) '(and) '(or))
|
||||||
((and (constant-lvar-p function) (stringp (lvar-value function)))
|
((and (constant-lvar-p function) (stringp (lvar-value function)))
|
||||||
(vop* call-out-named call block (arg-operands) (result-operands)
|
(vop* call-out-named call block (arg-operands) (result-operands)
|
||||||
|
|
@ -787,7 +887,7 @@
|
||||||
(reference-tn (car (last result-tns 2)) t))
|
(reference-tn (car (last result-tns 2)) t))
|
||||||
(move-lvar-result call block (list (car (last result-tns 2))) lvar))
|
(move-lvar-result call block (list (car (last result-tns 2))) lvar))
|
||||||
(t
|
(t
|
||||||
(move-lvar-result call block result-tns lvar)))))))
|
(move-lvar-result call block result-tns lvar))))))))
|
||||||
|
|
||||||
(deftransform sb-alien::c-string-external-format ((type)
|
(deftransform sb-alien::c-string-external-format ((type)
|
||||||
((constant-arg sb-alien::alien-c-string-type)))
|
((constant-arg sb-alien::alien-c-string-type)))
|
||||||
|
|
|
||||||
|
|
@ -183,8 +183,262 @@
|
||||||
(invoke-alien-type-method :result-tn type state))
|
(invoke-alien-type-method :result-tn type state))
|
||||||
values)))
|
values)))
|
||||||
|
|
||||||
|
;;;; Struct Return-by-Value Support for ARM64 (AAPCS64)
|
||||||
|
|
||||||
|
;;; Check if a record type is a Homogeneous Floating-point Aggregate (HFA)
|
||||||
|
;;; An HFA is a struct with 1-4 floating-point members of the same type.
|
||||||
|
;;; Members can be scalar floats, arrays of floats, or nested HFA structs.
|
||||||
|
(defun hfa-member-info (alien-type)
|
||||||
|
"Return (values base-type count) for a potential HFA member, or NIL if not HFA-compatible.
|
||||||
|
BASE-TYPE is 'single-float or 'double-float, COUNT is the number of elements."
|
||||||
|
(cond
|
||||||
|
;; Single-float scalar
|
||||||
|
((sb-alien::alien-single-float-type-p alien-type)
|
||||||
|
(values 'single-float 1))
|
||||||
|
;; Double-float scalar
|
||||||
|
((sb-alien::alien-double-float-type-p alien-type)
|
||||||
|
(values 'double-float 1))
|
||||||
|
;; Array type - check if element type is float
|
||||||
|
((sb-alien::alien-array-type-p alien-type)
|
||||||
|
(let ((element-type (sb-alien::alien-array-type-element-type alien-type))
|
||||||
|
(dims (sb-alien::alien-array-type-dimensions alien-type)))
|
||||||
|
;; Only 1-D arrays for HFA
|
||||||
|
(when (and (= (length dims) 1)
|
||||||
|
(integerp (first dims)))
|
||||||
|
(let ((len (first dims)))
|
||||||
|
(cond
|
||||||
|
((sb-alien::alien-single-float-type-p element-type)
|
||||||
|
(values 'single-float len))
|
||||||
|
((sb-alien::alien-double-float-type-p element-type)
|
||||||
|
(values 'double-float len))
|
||||||
|
;; Could also be an array of HFA structs
|
||||||
|
((sb-alien::alien-record-type-p element-type)
|
||||||
|
(multiple-value-bind (nested-base nested-count)
|
||||||
|
(hfa-base-type element-type)
|
||||||
|
(when nested-base
|
||||||
|
(values nested-base (* len nested-count)))))
|
||||||
|
(t nil))))))
|
||||||
|
;; Nested record - recursively check HFA
|
||||||
|
((sb-alien::alien-record-type-p alien-type)
|
||||||
|
(hfa-base-type alien-type))
|
||||||
|
;; Non-float field
|
||||||
|
(t nil)))
|
||||||
|
|
||||||
|
(defun hfa-base-type (record-type)
|
||||||
|
"Check if record is an HFA. Returns (values base-type member-count) where
|
||||||
|
base-type is 'single-float or 'double-float, or NIL if not an HFA."
|
||||||
|
(let ((fields (sb-alien::alien-record-type-fields record-type))
|
||||||
|
(base-type nil)
|
||||||
|
(count 0))
|
||||||
|
(dolist (field fields)
|
||||||
|
(let ((field-type (sb-alien::alien-record-field-type field)))
|
||||||
|
(multiple-value-bind (member-base member-count)
|
||||||
|
(hfa-member-info field-type)
|
||||||
|
(cond
|
||||||
|
;; Not HFA-compatible member
|
||||||
|
((null member-base)
|
||||||
|
(return-from hfa-base-type nil))
|
||||||
|
;; Compatible with existing base type (or first member)
|
||||||
|
((or (null base-type) (eq base-type member-base))
|
||||||
|
(setf base-type member-base)
|
||||||
|
(incf count member-count))
|
||||||
|
;; Mixed float types - not an HFA
|
||||||
|
(t (return-from hfa-base-type nil))))))
|
||||||
|
;; HFA must have 1-4 members
|
||||||
|
(when (and base-type (<= 1 count 4))
|
||||||
|
(values base-type count))))
|
||||||
|
|
||||||
|
;;; Main classification function for ARM64 AAPCS64.
|
||||||
|
(defun classify-struct-aapcs64 (record-type)
|
||||||
|
"Classify struct for ARM64 AAPCS64 return."
|
||||||
|
(let* ((bits (sb-alien::alien-type-bits record-type))
|
||||||
|
(byte-size (ceiling bits 8))
|
||||||
|
(alignment (sb-alien::alien-type-alignment record-type)))
|
||||||
|
(multiple-value-bind (hfa-type hfa-count) (hfa-base-type record-type)
|
||||||
|
(cond
|
||||||
|
;; HFA: return in floating-point registers
|
||||||
|
(hfa-type
|
||||||
|
(sb-alien::make-struct-classification
|
||||||
|
:register-slots (make-list hfa-count :initial-element
|
||||||
|
(if (eq hfa-type 'single-float) :single :double))
|
||||||
|
:size byte-size
|
||||||
|
:alignment alignment
|
||||||
|
:memory-p nil))
|
||||||
|
;; Small non-HFA: return in x0 (and x1 if 9-16 bytes)
|
||||||
|
((<= byte-size 16)
|
||||||
|
(sb-alien::make-struct-classification
|
||||||
|
:register-slots (make-list (max 1 (ceiling byte-size 8)) :initial-element :integer)
|
||||||
|
:size byte-size
|
||||||
|
:alignment alignment
|
||||||
|
:memory-p nil))
|
||||||
|
;; Large struct: use x8 indirect result
|
||||||
|
(t
|
||||||
|
(sb-alien::make-struct-classification
|
||||||
|
:register-slots '(:memory)
|
||||||
|
:size byte-size
|
||||||
|
:alignment alignment
|
||||||
|
:memory-p t))))))
|
||||||
|
|
||||||
|
;;; Result TN generation for record types
|
||||||
|
;;; Called from src/code/c-call.lisp
|
||||||
|
(defun record-result-tn (type state)
|
||||||
|
"Handle struct return values."
|
||||||
|
(let ((classification (classify-struct-aapcs64 type)))
|
||||||
|
(if (sb-alien::struct-classification-memory-p classification)
|
||||||
|
;; Large struct: return via hidden pointer in x8
|
||||||
|
;; The caller allocates space and passes pointer in x8
|
||||||
|
(progn
|
||||||
|
(setf (result-state-num-results state) 1)
|
||||||
|
(make-wired-tn* 'system-area-pointer sap-reg-sc-number (result-reg-offset 0)))
|
||||||
|
;; Small struct: return in registers
|
||||||
|
(let ((result-tns nil)
|
||||||
|
(int-results 0)
|
||||||
|
(fp-results 0))
|
||||||
|
(dolist (class (sb-alien::struct-classification-register-slots classification))
|
||||||
|
(ecase class
|
||||||
|
(:integer
|
||||||
|
(push (make-wired-tn* 'unsigned-byte-64
|
||||||
|
unsigned-reg-sc-number
|
||||||
|
(result-reg-offset int-results))
|
||||||
|
result-tns)
|
||||||
|
(incf int-results))
|
||||||
|
(:single
|
||||||
|
(push (make-wired-tn* 'single-float
|
||||||
|
single-reg-sc-number
|
||||||
|
fp-results)
|
||||||
|
result-tns)
|
||||||
|
(incf fp-results))
|
||||||
|
(:double
|
||||||
|
(push (make-wired-tn* 'double-float
|
||||||
|
double-reg-sc-number
|
||||||
|
fp-results)
|
||||||
|
result-tns)
|
||||||
|
(incf fp-results))))
|
||||||
|
(setf (result-state-num-results state) (+ int-results fp-results))
|
||||||
|
(nreverse result-tns)))))
|
||||||
|
|
||||||
|
;;; VOPs for struct argument passing
|
||||||
|
;;; These VOPs load register slots from a struct SAP into target registers
|
||||||
|
|
||||||
|
(define-vop (load-struct-int-arg)
|
||||||
|
(:args (sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:results (target :scs (unsigned-reg signed-reg)))
|
||||||
|
(:generator 5
|
||||||
|
(inst ldr target (@ sap offset))))
|
||||||
|
|
||||||
|
(define-vop (load-struct-single-arg)
|
||||||
|
(:args (sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:results (target :scs (single-reg)))
|
||||||
|
(:generator 5
|
||||||
|
(inst ldr target (@ sap offset))))
|
||||||
|
|
||||||
|
(define-vop (load-struct-double-arg)
|
||||||
|
(:args (sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:results (target :scs (double-reg)))
|
||||||
|
(:generator 5
|
||||||
|
(inst ldr target (@ sap offset))))
|
||||||
|
|
||||||
|
;;; VOPs for storing struct result registers to memory
|
||||||
|
;;; These VOPs store result register values back to memory for struct-by-value returns
|
||||||
|
|
||||||
|
(define-vop (store-struct-int-result)
|
||||||
|
(:args (value :scs (unsigned-reg signed-reg))
|
||||||
|
(sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:generator 5
|
||||||
|
(inst str value (@ sap offset))))
|
||||||
|
|
||||||
|
(define-vop (store-struct-single-result)
|
||||||
|
(:args (value :scs (single-reg))
|
||||||
|
(sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:generator 5
|
||||||
|
(inst str value (@ sap offset))))
|
||||||
|
|
||||||
|
(define-vop (store-struct-double-result)
|
||||||
|
(:args (value :scs (double-reg))
|
||||||
|
(sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:generator 5
|
||||||
|
(inst str value (@ sap offset))))
|
||||||
|
|
||||||
|
;;; Arg TN generation for record types
|
||||||
|
;;; Called from src/code/c-call.lisp
|
||||||
|
(defun record-arg-tn (type state)
|
||||||
|
"Handle struct arguments.
|
||||||
|
For large structs (>16 bytes), returns a SAP TN for pointer passing.
|
||||||
|
For small structs, returns a function that emits load VOPs."
|
||||||
|
(let ((classification (classify-struct-aapcs64 type)))
|
||||||
|
(if (sb-alien::struct-classification-memory-p classification)
|
||||||
|
;; Large struct: pass by pointer
|
||||||
|
(int-arg state 'system-area-pointer sap-reg-sc-number sap-stack-sc-number)
|
||||||
|
;; Small struct: allocate target TNs and return a function to load into them
|
||||||
|
(let ((arg-tns nil)
|
||||||
|
(offsets nil)
|
||||||
|
(offset 0))
|
||||||
|
(dolist (class (sb-alien::struct-classification-register-slots classification))
|
||||||
|
(ecase class
|
||||||
|
(:integer
|
||||||
|
(push (int-arg state 'unsigned-byte-64
|
||||||
|
unsigned-reg-sc-number
|
||||||
|
unsigned-stack-sc-number)
|
||||||
|
arg-tns)
|
||||||
|
(push (cons offset :integer) offsets)
|
||||||
|
(incf offset 8))
|
||||||
|
(:single
|
||||||
|
(push (float-arg state 'single-float
|
||||||
|
single-reg-sc-number
|
||||||
|
single-stack-sc-number #+darwin 4)
|
||||||
|
arg-tns)
|
||||||
|
(push (cons offset :single) offsets)
|
||||||
|
(incf offset 4))
|
||||||
|
(:double
|
||||||
|
(push (float-arg state 'double-float
|
||||||
|
double-reg-sc-number
|
||||||
|
double-stack-sc-number)
|
||||||
|
arg-tns)
|
||||||
|
(push (cons offset :double) offsets)
|
||||||
|
(incf offset 8))))
|
||||||
|
(setf arg-tns (nreverse arg-tns))
|
||||||
|
(setf offsets (nreverse offsets))
|
||||||
|
;; Return a function that emits the load VOPs
|
||||||
|
(lambda (arg call block nsp)
|
||||||
|
(declare (ignore nsp))
|
||||||
|
(let ((sap-tn (sb-c::lvar-tn call block arg)))
|
||||||
|
(loop for target-tn in arg-tns
|
||||||
|
for (off . class) in offsets
|
||||||
|
do (ecase class
|
||||||
|
(:integer
|
||||||
|
(sb-c::emit-and-insert-vop
|
||||||
|
call block
|
||||||
|
(sb-c::template-or-lose 'load-struct-int-arg)
|
||||||
|
(sb-c::reference-tn sap-tn nil)
|
||||||
|
(sb-c::reference-tn target-tn t)
|
||||||
|
nil
|
||||||
|
(list off)))
|
||||||
|
(:single
|
||||||
|
(sb-c::emit-and-insert-vop
|
||||||
|
call block
|
||||||
|
(sb-c::template-or-lose 'load-struct-single-arg)
|
||||||
|
(sb-c::reference-tn sap-tn nil)
|
||||||
|
(sb-c::reference-tn target-tn t)
|
||||||
|
nil
|
||||||
|
(list off)))
|
||||||
|
(:double
|
||||||
|
(sb-c::emit-and-insert-vop
|
||||||
|
call block
|
||||||
|
(sb-c::template-or-lose 'load-struct-double-arg)
|
||||||
|
(sb-c::reference-tn sap-tn nil)
|
||||||
|
(sb-c::reference-tn target-tn t)
|
||||||
|
nil
|
||||||
|
(list off)))))))))))
|
||||||
|
|
||||||
(defun make-call-out-tns (type)
|
(defun make-call-out-tns (type)
|
||||||
(let ((arg-state (make-arg-state)))
|
(let ((arg-state (make-arg-state))
|
||||||
|
(result-type (alien-fun-type-result-type type)))
|
||||||
(collect ((arg-tns))
|
(collect ((arg-tns))
|
||||||
(let (#+darwin (variadic (sb-alien::alien-fun-type-varargs type)))
|
(let (#+darwin (variadic (sb-alien::alien-fun-type-varargs type)))
|
||||||
(loop for i from 0
|
(loop for i from 0
|
||||||
|
|
@ -195,13 +449,31 @@
|
||||||
(setf (arg-state-num-register-args arg-state) +max-register-args+
|
(setf (arg-state-num-register-args arg-state) +max-register-args+
|
||||||
(arg-state-fp-registers arg-state) +max-register-args+))
|
(arg-state-fp-registers arg-state) +max-register-args+))
|
||||||
(arg-tns (invoke-alien-type-method :arg-tn arg-type arg-state))))
|
(arg-tns (invoke-alien-type-method :arg-tn arg-type arg-state))))
|
||||||
(values (make-normal-tn *fixnum-primitive-type*)
|
;; Check if result is a large struct that needs hidden pointer
|
||||||
(arg-state-stack-frame-size arg-state)
|
(let* ((stack-frame-size (arg-state-stack-frame-size arg-state))
|
||||||
(arg-tns)
|
;; For large struct returns, we don't allocate stack space here
|
||||||
(invoke-alien-type-method :result-tn
|
;; The IR1 transform allocates heap memory and passes it as first arg
|
||||||
(alien-fun-type-result-type type)
|
;; We just return a flag indicating this is a large struct return
|
||||||
(make-result-state))))))
|
(large-struct-return-p
|
||||||
|
(when (sb-alien::alien-record-type-p result-type)
|
||||||
|
(let ((classification (classify-struct-aapcs64 result-type)))
|
||||||
|
(sb-alien::struct-classification-memory-p classification)))))
|
||||||
|
(values (make-normal-tn *fixnum-primitive-type*)
|
||||||
|
stack-frame-size
|
||||||
|
(arg-tns)
|
||||||
|
(invoke-alien-type-method :result-tn result-type (make-result-state))
|
||||||
|
;; 5th value: T if large struct return (sret pointer passed as first arg)
|
||||||
|
large-struct-return-p)))))
|
||||||
|
|
||||||
|
;;; VOP to set up for return of large structs (>16 bytes) via a
|
||||||
|
;;; hidden pointer: caller allocates memory and passes the address in
|
||||||
|
;;; x8.
|
||||||
|
(define-vop (set-struct-return-pointer)
|
||||||
|
(:args (sap :scs (sap-reg) :target x8))
|
||||||
|
(:temporary (:sc sap-reg :offset 8) x8) ; x8 is the indirect result register
|
||||||
|
(:generator 1
|
||||||
|
(move x8 sap)))
|
||||||
|
|
||||||
(define-vop (foreign-symbol-sap)
|
(define-vop (foreign-symbol-sap)
|
||||||
(:translate foreign-symbol-sap)
|
(:translate foreign-symbol-sap)
|
||||||
(:policy :fast-safe)
|
(:policy :fast-safe)
|
||||||
|
|
@ -348,35 +620,66 @@
|
||||||
|
|
||||||
#-sb-xc-host
|
#-sb-xc-host
|
||||||
(defun alien-callback-assembler-wrapper (index result-type argument-types)
|
(defun alien-callback-assembler-wrapper (index result-type argument-types)
|
||||||
(flet ((make-tn (offset &optional (sc-name 'any-reg))
|
(labels ((make-tn (offset &optional (sc-name 'any-reg))
|
||||||
(make-random-tn (sc-or-lose sc-name) offset)))
|
(make-random-tn (sc-or-lose sc-name) offset))
|
||||||
(let* ((segment (make-segment))
|
(argument-byte-size (type)
|
||||||
;; How many arguments have been copied
|
"Return the number of bytes this argument occupies in the callback vector."
|
||||||
(arg-count 0)
|
(ceiling (sb-alien::alien-type-bits type) n-byte-bits))
|
||||||
;; How many arguments have been copied from the stack
|
(round-up-to-word (bytes)
|
||||||
(stack-argument-bytes 0)
|
(* n-word-bytes (ceiling bytes n-word-bytes))))
|
||||||
(r0-tn (make-tn 0))
|
;; Check for struct return type and classify it
|
||||||
(r1-tn (make-tn 1))
|
(let* ((result-classification
|
||||||
(r2-tn (make-tn 2))
|
(when (alien-record-type-p result-type)
|
||||||
(r3-tn (make-tn 3))
|
(classify-struct-aapcs64 result-type)))
|
||||||
(temp-tn (make-tn 9))
|
(large-struct-return-p
|
||||||
(nsp-save-tn (make-tn 10))
|
(and result-classification
|
||||||
(gprs (loop for i below 8
|
(sb-alien::struct-classification-memory-p result-classification))))
|
||||||
collect (make-tn i)))
|
;; Calculate frame size: sum of all argument sizes
|
||||||
(fp-registers 0)
|
(let* ((segment (make-segment))
|
||||||
(frame-size (* (length argument-types) n-word-bytes)))
|
;; Current byte offset in the argument frame
|
||||||
|
(frame-offset 0)
|
||||||
|
;; How many bytes have been read from the stack argument area
|
||||||
|
(stack-argument-bytes 0)
|
||||||
|
(r0-tn (make-tn 0))
|
||||||
|
(r1-tn (make-tn 1))
|
||||||
|
(r2-tn (make-tn 2))
|
||||||
|
(r3-tn (make-tn 3))
|
||||||
|
(temp-tn (make-tn 9))
|
||||||
|
(nsp-save-tn (make-tn 10))
|
||||||
|
;; x8 is used for large struct return pointer
|
||||||
|
(x8-tn (make-tn 8))
|
||||||
|
;; x12 used to save x8 across the call (x11 is used for ptr-tn in struct arg processing)
|
||||||
|
(x8-save-tn (make-tn 12))
|
||||||
|
(gprs (loop for i below 8
|
||||||
|
collect (make-tn i)))
|
||||||
|
(fp-registers 0)
|
||||||
|
;; Calculate frame size from argument types (word-aligned)
|
||||||
|
(frame-size (loop for type in argument-types
|
||||||
|
sum (round-up-to-word (argument-byte-size type))))
|
||||||
|
;; Return value slot count - enough for large struct if needed
|
||||||
|
(return-slot-count
|
||||||
|
(if large-struct-return-p
|
||||||
|
(ceiling (sb-alien::struct-classification-size result-classification) n-word-bytes)
|
||||||
|
2)))
|
||||||
(setf frame-size (logandc2 (+ frame-size +number-stack-alignment-mask+)
|
(setf frame-size (logandc2 (+ frame-size +number-stack-alignment-mask+)
|
||||||
+number-stack-alignment-mask+))
|
+number-stack-alignment-mask+))
|
||||||
|
;; Return value allocation size - must be 16-byte aligned for stack alignment
|
||||||
|
(let ((return-bytes (logandc2 (+ (* n-word-bytes return-slot-count) 15) 15)))
|
||||||
(assemble (segment 'nil)
|
(assemble (segment 'nil)
|
||||||
(inst mov-sp nsp-save-tn nsp-tn)
|
(inst mov-sp nsp-save-tn nsp-tn)
|
||||||
(inst str lr-tn (@ nsp-tn -16 :pre-index))
|
(inst str lr-tn (@ nsp-tn -16 :pre-index))
|
||||||
|
;; Save x8 (hidden struct return pointer) to stack if returning large struct
|
||||||
|
;; We save to stack because x8-15 are caller-saved and would be clobbered by the call
|
||||||
|
;; After the str above, nsp points to saved LR, and [nsp+8] is free space
|
||||||
|
(when large-struct-return-p
|
||||||
|
(inst str x8-tn (@ nsp-tn 8)))
|
||||||
;; Make room on the stack for arguments.
|
;; Make room on the stack for arguments.
|
||||||
(when (plusp frame-size)
|
(when (plusp frame-size)
|
||||||
(inst sub nsp-tn nsp-tn frame-size))
|
(inst sub nsp-tn nsp-tn frame-size))
|
||||||
;; Copy arguments
|
;; Copy arguments
|
||||||
(dolist (type argument-types)
|
(dolist (type argument-types)
|
||||||
(let ((target-tn (@ nsp-tn (* arg-count n-word-bytes)))
|
(let ((target-tn (@ nsp-tn frame-offset))
|
||||||
(size #+darwin (truncate (alien-type-bits type) n-byte-bits)
|
(size #+darwin (truncate (sb-alien::alien-type-bits type) n-byte-bits)
|
||||||
#-darwin n-word-bytes))
|
#-darwin n-word-bytes))
|
||||||
(cond ((or (alien-integer-type-p type)
|
(cond ((or (alien-integer-type-p type)
|
||||||
(alien-pointer-type-p type)
|
(alien-pointer-type-p type)
|
||||||
|
|
@ -410,7 +713,7 @@
|
||||||
(inst ldr temp-tn addr)))
|
(inst ldr temp-tn addr)))
|
||||||
(inst str temp-tn target-tn))
|
(inst str temp-tn target-tn))
|
||||||
(incf stack-argument-bytes size))))
|
(incf stack-argument-bytes size))))
|
||||||
(incf arg-count))
|
(incf frame-offset n-word-bytes))
|
||||||
((alien-float-type-p type)
|
((alien-float-type-p type)
|
||||||
(cond ((< fp-registers 8)
|
(cond ((< fp-registers 8)
|
||||||
(inst str (make-tn fp-registers
|
(inst str (make-tn fp-registers
|
||||||
|
|
@ -432,7 +735,64 @@
|
||||||
(inst str temp-tn target-tn)))
|
(inst str temp-tn target-tn)))
|
||||||
(incf stack-argument-bytes size)))
|
(incf stack-argument-bytes size)))
|
||||||
(incf fp-registers)
|
(incf fp-registers)
|
||||||
(incf arg-count))
|
(incf frame-offset n-word-bytes))
|
||||||
|
;; Handle struct-by-value arguments
|
||||||
|
((sb-alien::alien-record-type-p type)
|
||||||
|
(let* ((struct-bytes (argument-byte-size type))
|
||||||
|
(struct-bytes-aligned (round-up-to-word struct-bytes))
|
||||||
|
(classification (classify-struct-aapcs64 type))
|
||||||
|
;; Use r11 as additional temp for struct pointer
|
||||||
|
(ptr-tn (make-tn 11)))
|
||||||
|
(cond
|
||||||
|
;; Large struct (>16 bytes): passed by pointer in register
|
||||||
|
((sb-alien::struct-classification-memory-p classification)
|
||||||
|
;; The struct pointer is in a GPR; copy struct data to frame
|
||||||
|
(let ((gpr (pop gprs)))
|
||||||
|
(cond (gpr
|
||||||
|
;; Move pointer from argument register to ptr-tn
|
||||||
|
(inst mov ptr-tn gpr))
|
||||||
|
(t
|
||||||
|
;; Pointer is on stack
|
||||||
|
(setf stack-argument-bytes (align-up stack-argument-bytes 8))
|
||||||
|
(inst ldr ptr-tn (@ nsp-save-tn stack-argument-bytes))
|
||||||
|
(incf stack-argument-bytes 8)))
|
||||||
|
;; Copy struct data from pointer to frame
|
||||||
|
;; Use temp-tn (r9) for copying, ptr-tn (r11) has source address
|
||||||
|
(loop for off from 0 below struct-bytes by 8
|
||||||
|
for remaining = (- struct-bytes off)
|
||||||
|
do (cond ((>= remaining 8)
|
||||||
|
(inst ldr temp-tn (@ ptr-tn off))
|
||||||
|
(inst str temp-tn (@ nsp-tn (+ frame-offset off))))
|
||||||
|
((>= remaining 4)
|
||||||
|
(inst ldr (32-bit-reg temp-tn) (@ ptr-tn off))
|
||||||
|
(inst str (32-bit-reg temp-tn) (@ nsp-tn (+ frame-offset off))))
|
||||||
|
(t
|
||||||
|
;; Copy remaining bytes one by one
|
||||||
|
(loop for b from 0 below remaining
|
||||||
|
do (inst ldrb (32-bit-reg temp-tn) (@ ptr-tn (+ off b)))
|
||||||
|
(inst strb (32-bit-reg temp-tn) (@ nsp-tn (+ frame-offset off b)))))))))
|
||||||
|
;; HFA: passed in floating-point registers
|
||||||
|
((multiple-value-bind (hfa-type hfa-count) (hfa-base-type type)
|
||||||
|
(when hfa-type
|
||||||
|
(let ((fp-size (if (eq hfa-type 'single-float) 4 8)))
|
||||||
|
(dotimes (i hfa-count)
|
||||||
|
(when (< fp-registers 8)
|
||||||
|
(inst str (make-tn fp-registers
|
||||||
|
(if (eq hfa-type 'single-float)
|
||||||
|
'single-reg
|
||||||
|
'double-reg))
|
||||||
|
(@ nsp-tn (+ frame-offset (* i fp-size))))
|
||||||
|
(incf fp-registers))))
|
||||||
|
t)))
|
||||||
|
;; Small non-HFA struct (<=16 bytes): passed in GPRs
|
||||||
|
(t
|
||||||
|
(let ((num-regs (ceiling struct-bytes 8)))
|
||||||
|
(dotimes (i num-regs)
|
||||||
|
(let ((gpr (pop gprs)))
|
||||||
|
(when gpr
|
||||||
|
(inst str gpr (@ nsp-tn (+ frame-offset (* i 8))))))))))
|
||||||
|
;; Use word-aligned size for frame offset to match Lisp side
|
||||||
|
(incf frame-offset struct-bytes-aligned)))
|
||||||
(t
|
(t
|
||||||
(bug "Unknown alien type: ~S" type)))))
|
(bug "Unknown alien type: ~S" type)))))
|
||||||
;; arg0 to ENTER-ALIEN-CALLBACK (trampoline index)
|
;; arg0 to ENTER-ALIEN-CALLBACK (trampoline index)
|
||||||
|
|
@ -440,7 +800,7 @@
|
||||||
;; arg1 to ENTER-ALIEN-CALLBACK (pointer to argument vector)
|
;; arg1 to ENTER-ALIEN-CALLBACK (pointer to argument vector)
|
||||||
(inst mov-sp r1-tn nsp-tn)
|
(inst mov-sp r1-tn nsp-tn)
|
||||||
;; add room on stack for return value
|
;; add room on stack for return value
|
||||||
(inst sub nsp-tn nsp-tn (* n-word-bytes 2))
|
(inst sub nsp-tn nsp-tn return-bytes)
|
||||||
;; arg2 to ENTER-ALIEN-CALLBACK (pointer to return value)
|
;; arg2 to ENTER-ALIEN-CALLBACK (pointer to return value)
|
||||||
(inst mov-sp r2-tn nsp-tn)
|
(inst mov-sp r2-tn nsp-tn)
|
||||||
|
|
||||||
|
|
@ -462,9 +822,52 @@
|
||||||
'double-reg))
|
'double-reg))
|
||||||
nsp-tn))
|
nsp-tn))
|
||||||
((alien-void-type-p result-type))
|
((alien-void-type-p result-type))
|
||||||
|
;; Struct return types
|
||||||
|
((alien-record-type-p result-type)
|
||||||
|
(cond
|
||||||
|
;; Large struct: copy result to x8 pointer location, return pointer in x0
|
||||||
|
(large-struct-return-p
|
||||||
|
(let ((struct-size (sb-alien::struct-classification-size result-classification))
|
||||||
|
;; x8 was saved at [original - 8]
|
||||||
|
;; After call: nsp = original - 16 - frame-size - return-bytes
|
||||||
|
;; So x8 is at [nsp + 8 + frame-size + return-bytes]
|
||||||
|
(x8-offset (+ 8 frame-size return-bytes)))
|
||||||
|
;; Load saved x8 from stack into x8-save-tn (x12)
|
||||||
|
;; We can't use nsp-save-tn as it may have been clobbered by the call
|
||||||
|
(inst ldr x8-save-tn (@ nsp-tn x8-offset))
|
||||||
|
(loop for off from 0 below struct-size by 8
|
||||||
|
for remaining = (- struct-size off)
|
||||||
|
do (cond ((>= remaining 8)
|
||||||
|
(inst ldr temp-tn (@ nsp-tn off))
|
||||||
|
(inst str temp-tn (@ x8-save-tn off)))
|
||||||
|
((>= remaining 4)
|
||||||
|
(inst ldr (32-bit-reg temp-tn) (@ nsp-tn off))
|
||||||
|
(inst str (32-bit-reg temp-tn) (@ x8-save-tn off)))
|
||||||
|
(t
|
||||||
|
(loop for b from 0 below remaining
|
||||||
|
do (inst ldrb (32-bit-reg temp-tn) (@ nsp-tn (+ off b)))
|
||||||
|
(inst strb (32-bit-reg temp-tn) (@ x8-save-tn (+ off b)))))))
|
||||||
|
;; Return the pointer in x0
|
||||||
|
(inst mov r0-tn x8-save-tn)))
|
||||||
|
;; HFA: load into floating-point registers
|
||||||
|
((multiple-value-bind (hfa-type hfa-count) (hfa-base-type result-type)
|
||||||
|
(when hfa-type
|
||||||
|
(let ((fp-size (if (eq hfa-type 'single-float) 4 8))
|
||||||
|
(sc-name (if (eq hfa-type 'single-float) 'single-reg 'double-reg)))
|
||||||
|
(dotimes (i hfa-count)
|
||||||
|
(inst ldr (make-tn i sc-name) (@ nsp-tn (* i fp-size)))))
|
||||||
|
t)))
|
||||||
|
;; Small non-HFA struct (<=16 bytes): load into x0/x1
|
||||||
|
(t
|
||||||
|
(let* ((struct-size (sb-alien::struct-classification-size result-classification))
|
||||||
|
(num-regs (ceiling struct-size 8)))
|
||||||
|
(when (>= num-regs 1)
|
||||||
|
(inst ldr r0-tn (@ nsp-tn 0)))
|
||||||
|
(when (>= num-regs 2)
|
||||||
|
(inst ldr r1-tn (@ nsp-tn 8)))))))
|
||||||
(t
|
(t
|
||||||
(error "Unrecognized alien type: ~A" result-type)))
|
(error "Unrecognized alien type: ~A" result-type)))
|
||||||
(inst add nsp-tn nsp-tn (+ frame-size (* n-word-bytes 2)))
|
(inst add nsp-tn nsp-tn (+ frame-size return-bytes))
|
||||||
(inst ldr lr-tn (@ nsp-tn 16 :post-index))
|
(inst ldr lr-tn (@ nsp-tn 16 :post-index))
|
||||||
(inst ret))
|
(inst ret))
|
||||||
(finalize-segment segment)
|
(finalize-segment segment)
|
||||||
|
|
@ -485,4 +888,4 @@
|
||||||
system-area-pointer
|
system-area-pointer
|
||||||
unsigned-long))
|
unsigned-long))
|
||||||
sap (length buffer))
|
sap (length buffer))
|
||||||
vector))))
|
vector))))))
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@
|
||||||
(stack-frame-size 0))
|
(stack-frame-size 0))
|
||||||
(declaim (freeze-type arg-state))
|
(declaim (freeze-type arg-state))
|
||||||
|
|
||||||
|
;;; Cache for struct classification to avoid redundant computation.
|
||||||
|
;;; Bound in make-call-out-tns when processing struct return types.
|
||||||
|
(defvar *cached-struct-classification* nil)
|
||||||
|
|
||||||
(defconstant max-int-args #.(length *c-call-register-arg-offsets*))
|
(defconstant max-int-args #.(length *c-call-register-arg-offsets*))
|
||||||
(defconstant max-xmm-args #+win32 4 #-win32 8)
|
(defconstant max-xmm-args #+win32 4 #-win32 8)
|
||||||
|
|
||||||
|
|
@ -119,17 +123,298 @@
|
||||||
(invoke-alien-type-method :result-tn type state))
|
(invoke-alien-type-method :result-tn type state))
|
||||||
values)))
|
values)))
|
||||||
|
|
||||||
|
;;;; Struct Return-by-Value Support (System V AMD64 ABI)
|
||||||
|
|
||||||
|
;;; Classify a single field
|
||||||
|
(defun classify-field-x86-64 (type)
|
||||||
|
"Classify a single field type for x86-64 ABI.
|
||||||
|
Returns :INTEGER, :DOUBLE, or :MEMORY."
|
||||||
|
(cond
|
||||||
|
;; Check specific types first, before general type checks
|
||||||
|
((sb-alien::alien-integer-type-p type) :integer)
|
||||||
|
((sb-alien::alien-pointer-type-p type) :integer)
|
||||||
|
((sb-alien::alien-single-float-type-p type) :double)
|
||||||
|
((sb-alien::alien-double-float-type-p type) :double)
|
||||||
|
;; Arrays are classified by their element type
|
||||||
|
((sb-alien::alien-array-type-p type)
|
||||||
|
(let ((element-type (sb-alien::alien-array-type-element-type type)))
|
||||||
|
(classify-field-x86-64 element-type)))
|
||||||
|
;; Nested struct - recursively classify and inherit eightbyte classes
|
||||||
|
((sb-alien::alien-record-type-p type)
|
||||||
|
(let ((nested (classify-struct-sysv-amd64 type)))
|
||||||
|
(if (sb-alien::struct-classification-memory-p nested)
|
||||||
|
:memory
|
||||||
|
;; Merge all slots from nested struct to get dominant class
|
||||||
|
;; e.g., struct { double d; } should contribute :double, not :integer
|
||||||
|
(reduce #'merge-classes
|
||||||
|
(sb-alien::struct-classification-register-slots nested)
|
||||||
|
:initial-value :no-class))))
|
||||||
|
;; System-area-pointer (must come after array/record checks)
|
||||||
|
((typep type 'sb-alien::alien-system-area-pointer-type) :integer)
|
||||||
|
(t :memory)))
|
||||||
|
|
||||||
|
;;; Merge two classes within an eightbyte per ABI rules
|
||||||
|
(defun merge-classes (class1 class2)
|
||||||
|
"Merge two classes within an eightbyte per ABI rules.
|
||||||
|
INTEGER dominates SSE; MEMORY dominates everything."
|
||||||
|
(cond
|
||||||
|
((eq class1 class2) class1)
|
||||||
|
((eq class1 :no-class) class2)
|
||||||
|
((eq class2 :no-class) class1)
|
||||||
|
((or (eq class1 :memory) (eq class2 :memory)) :memory)
|
||||||
|
((or (eq class1 :integer) (eq class2 :integer)) :integer)
|
||||||
|
(t :double)))
|
||||||
|
|
||||||
|
;;; Main classification function for x86-64 System V AMD64 ABI
|
||||||
|
(defun classify-struct-sysv-amd64 (record-type)
|
||||||
|
"Classify struct for x86-64 System V ABI return.
|
||||||
|
Returns STRUCT-CLASSIFICATION."
|
||||||
|
(let* ((bits (sb-alien::alien-type-bits record-type))
|
||||||
|
(byte-size (ceiling bits 8))
|
||||||
|
(alignment (sb-alien::alien-type-alignment record-type)))
|
||||||
|
;; Rule: Structs > 16 bytes always use memory (hidden pointer)
|
||||||
|
(when (> byte-size 16)
|
||||||
|
(return-from classify-struct-sysv-amd64
|
||||||
|
(sb-alien::make-struct-classification
|
||||||
|
:register-slots '(:memory)
|
||||||
|
:size byte-size
|
||||||
|
:alignment alignment
|
||||||
|
:memory-p t)))
|
||||||
|
|
||||||
|
;; Classify each eightbyte
|
||||||
|
(let* ((num-eightbytes (max 1 (ceiling byte-size 8)))
|
||||||
|
(eightbytes (make-list num-eightbytes :initial-element :no-class)))
|
||||||
|
;; Iterate through fields and classify
|
||||||
|
(dolist (field (sb-alien::alien-record-type-fields record-type))
|
||||||
|
(let* ((field-offset-bits (sb-alien::alien-record-field-offset field))
|
||||||
|
(field-type (sb-alien::alien-record-field-type field))
|
||||||
|
(field-bits (sb-alien::alien-type-bits field-type))
|
||||||
|
(field-offset-bytes (floor field-offset-bits 8))
|
||||||
|
(field-size-bytes (ceiling field-bits 8))
|
||||||
|
(field-class (classify-field-x86-64 field-type)))
|
||||||
|
;; Apply class to all eightbytes this field spans
|
||||||
|
(loop for byte-offset from field-offset-bytes below (+ field-offset-bytes field-size-bytes) by 8
|
||||||
|
for eightbyte-index = (floor byte-offset 8)
|
||||||
|
when (< eightbyte-index num-eightbytes)
|
||||||
|
do (setf (nth eightbyte-index eightbytes)
|
||||||
|
(merge-classes (nth eightbyte-index eightbytes)
|
||||||
|
field-class)))))
|
||||||
|
|
||||||
|
;; Post-merge cleanup per ABI: if second eightbyte is MEMORY, first must be too
|
||||||
|
(when (and (> num-eightbytes 1)
|
||||||
|
(eq (second eightbytes) :memory))
|
||||||
|
(setf (first eightbytes) :memory))
|
||||||
|
|
||||||
|
;; Convert remaining :no-class to :integer (padding bytes are treated as integer)
|
||||||
|
(setf eightbytes
|
||||||
|
(mapcar (lambda (c) (if (eq c :no-class) :integer c)) eightbytes))
|
||||||
|
|
||||||
|
(sb-alien::make-struct-classification
|
||||||
|
:register-slots eightbytes
|
||||||
|
:size byte-size
|
||||||
|
:alignment alignment
|
||||||
|
:memory-p (member :memory eightbytes)))))
|
||||||
|
|
||||||
|
;;; Result TN generation for record types
|
||||||
|
;;; Called from src/code/c-call.lisp
|
||||||
|
(defun record-result-tn (type state)
|
||||||
|
"Handle struct return values."
|
||||||
|
;; Windows x64 uses Microsoft calling convention, not System V AMD64.
|
||||||
|
;; To add Windows support:
|
||||||
|
;; 1. Implement classify-struct-win64: structs of 1/2/4/8 bytes return in RAX,
|
||||||
|
;; larger structs use hidden pointer in RCX (not RDI)
|
||||||
|
;; 2. Adapt this function to use the Windows classification
|
||||||
|
;; 3. Update make-call-out-tns to reserve RCX instead of RDI for sret pointer
|
||||||
|
#+win32 (error "Struct-by-value return not implemented for Windows x64 ABI")
|
||||||
|
(let ((classification (or *cached-struct-classification*
|
||||||
|
(classify-struct-sysv-amd64 type))))
|
||||||
|
(if (sb-alien::struct-classification-memory-p classification)
|
||||||
|
;; Large struct: return via hidden pointer
|
||||||
|
;; Caller passes pointer in RDI, callee returns it in RAX
|
||||||
|
(progn
|
||||||
|
(setf (result-state-num-results state) 1)
|
||||||
|
(make-wired-tn* 'system-area-pointer sap-reg-sc-number rax-offset))
|
||||||
|
;; Small struct: return in registers
|
||||||
|
(let ((result-tns nil)
|
||||||
|
(int-results 0)
|
||||||
|
(sse-results 0))
|
||||||
|
(dolist (class (sb-alien::struct-classification-register-slots classification))
|
||||||
|
(case class
|
||||||
|
(:integer
|
||||||
|
(push (make-wired-tn* 'unsigned-byte-64
|
||||||
|
unsigned-reg-sc-number
|
||||||
|
(result-reg-offset int-results))
|
||||||
|
result-tns)
|
||||||
|
(incf int-results))
|
||||||
|
(:double
|
||||||
|
(push (make-wired-tn* 'double-float
|
||||||
|
double-reg-sc-number
|
||||||
|
sse-results)
|
||||||
|
result-tns)
|
||||||
|
(incf sse-results))))
|
||||||
|
(setf (result-state-num-results state) (+ int-results sse-results))
|
||||||
|
(nreverse result-tns)))))
|
||||||
|
|
||||||
|
;;; VOPs for struct argument passing
|
||||||
|
;;; These VOPs load eightbytes from a struct SAP into target registers
|
||||||
|
|
||||||
|
(define-vop (load-struct-int-arg)
|
||||||
|
(:args (sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:results (target :scs (unsigned-reg signed-reg)))
|
||||||
|
(:generator 5
|
||||||
|
(inst mov :qword target (ea offset sap))))
|
||||||
|
|
||||||
|
(define-vop (load-struct-sse-arg)
|
||||||
|
(:args (sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:results (target :scs (double-reg single-reg)))
|
||||||
|
(:generator 5
|
||||||
|
(inst movsd target (ea offset sap))))
|
||||||
|
|
||||||
|
;;; VOPs for storing struct result registers to memory
|
||||||
|
;;; These VOPs store result register values back to memory for struct-by-value returns
|
||||||
|
|
||||||
|
(define-vop (store-struct-int-result)
|
||||||
|
(:args (value :scs (unsigned-reg signed-reg))
|
||||||
|
(sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:generator 5
|
||||||
|
(inst mov :qword (ea offset sap) value)))
|
||||||
|
|
||||||
|
(define-vop (store-struct-sse-result)
|
||||||
|
(:args (value :scs (double-reg single-reg))
|
||||||
|
(sap :scs (sap-reg)))
|
||||||
|
(:info offset)
|
||||||
|
(:generator 5
|
||||||
|
(inst movsd (ea offset sap) value)))
|
||||||
|
|
||||||
|
;;; VOP to copy a qword from struct SAP to the C argument stack
|
||||||
|
;;; Used for passing large structs (>16 bytes) by value
|
||||||
|
(define-vop (copy-struct-arg-to-stack)
|
||||||
|
(:args (sap :scs (sap-reg))
|
||||||
|
(nsp :scs (any-reg)))
|
||||||
|
(:info src-offset dst-offset)
|
||||||
|
(:temporary (:sc unsigned-reg) temp)
|
||||||
|
(:generator 5
|
||||||
|
(inst mov :qword temp (ea src-offset sap))
|
||||||
|
(inst mov :qword (ea dst-offset nsp) temp)))
|
||||||
|
|
||||||
|
;;; Arg TN generation for record types
|
||||||
|
;;; Called from src/code/c-call.lisp
|
||||||
|
(defun record-arg-tn (type state)
|
||||||
|
"Handle struct arguments.
|
||||||
|
For large structs (>16 bytes), copies to stack per System V AMD64 ABI.
|
||||||
|
For small structs, returns a function that emits load VOPs into registers."
|
||||||
|
;; Windows x64 uses Microsoft calling convention, not System V AMD64.
|
||||||
|
;; To add Windows support:
|
||||||
|
;; 1. Implement classify-struct-win64: structs >8 bytes are passed by pointer
|
||||||
|
;; (caller allocates, passes address in integer register)
|
||||||
|
;; 2. Structs of 1/2/4/8 bytes are passed in a single integer register
|
||||||
|
;; 3. Adapt this function to handle both cases
|
||||||
|
#+win32 (error "Struct-by-value arguments not implemented for Windows x64 ABI")
|
||||||
|
(let ((classification (classify-struct-sysv-amd64 type)))
|
||||||
|
(if (sb-alien::struct-classification-memory-p classification)
|
||||||
|
;; Large struct: copy to stack (System V AMD64 ABI)
|
||||||
|
;; The struct is passed by value on the stack, not by pointer
|
||||||
|
(let* ((size (sb-alien::struct-classification-size classification))
|
||||||
|
(words (ceiling size 8))
|
||||||
|
(stack-base (arg-state-stack-frame-size state)))
|
||||||
|
;; Reserve stack slots for the struct
|
||||||
|
(incf (arg-state-stack-frame-size state) words)
|
||||||
|
;; Return a function that copies the struct to the stack
|
||||||
|
(lambda (arg call block nsp)
|
||||||
|
(let ((sap-tn (sb-c::lvar-tn call block arg)))
|
||||||
|
(loop for i from 0 below words
|
||||||
|
for src-offset = (* i 8)
|
||||||
|
for dst-offset = (* (+ stack-base i) n-word-bytes)
|
||||||
|
do (sb-c::emit-and-insert-vop
|
||||||
|
call block
|
||||||
|
(sb-c::template-or-lose 'copy-struct-arg-to-stack)
|
||||||
|
(sb-c::reference-tn-list (list sap-tn nsp) nil)
|
||||||
|
nil ; no results
|
||||||
|
nil ; insert at end
|
||||||
|
(list src-offset dst-offset))))))
|
||||||
|
;; Small struct: allocate target TNs and return a function to load into them
|
||||||
|
(let ((arg-tns nil)
|
||||||
|
(offsets nil)
|
||||||
|
(offset 0))
|
||||||
|
(dolist (class (sb-alien::struct-classification-register-slots classification))
|
||||||
|
(case class
|
||||||
|
(:integer
|
||||||
|
(push (int-arg state 'unsigned-byte-64
|
||||||
|
unsigned-reg-sc-number
|
||||||
|
unsigned-stack-sc-number)
|
||||||
|
arg-tns)
|
||||||
|
(push (cons offset :integer) offsets))
|
||||||
|
(:double
|
||||||
|
(push (float-arg state 'double-float
|
||||||
|
double-reg-sc-number
|
||||||
|
double-stack-sc-number)
|
||||||
|
arg-tns)
|
||||||
|
(push (cons offset :double) offsets)))
|
||||||
|
(incf offset 8))
|
||||||
|
(setf arg-tns (nreverse arg-tns))
|
||||||
|
(setf offsets (nreverse offsets))
|
||||||
|
;; Return a function that emits the load VOPs
|
||||||
|
(lambda (arg call block nsp)
|
||||||
|
(declare (ignore nsp))
|
||||||
|
(let ((sap-tn (sb-c::lvar-tn call block arg)))
|
||||||
|
(loop for target-tn in arg-tns
|
||||||
|
for (off . class) in offsets
|
||||||
|
do (ecase class
|
||||||
|
(:integer
|
||||||
|
(sb-c::emit-and-insert-vop
|
||||||
|
call block
|
||||||
|
(sb-c::template-or-lose 'load-struct-int-arg)
|
||||||
|
(sb-c::reference-tn sap-tn nil)
|
||||||
|
(sb-c::reference-tn target-tn t)
|
||||||
|
nil ; insert at end
|
||||||
|
(list off)))
|
||||||
|
(:double
|
||||||
|
(sb-c::emit-and-insert-vop
|
||||||
|
call block
|
||||||
|
(sb-c::template-or-lose 'load-struct-sse-arg)
|
||||||
|
(sb-c::reference-tn sap-tn nil)
|
||||||
|
(sb-c::reference-tn target-tn t)
|
||||||
|
nil
|
||||||
|
(list off)))))))))))
|
||||||
|
|
||||||
|
;;; VOP to set up RDI: large structs (>16 bytes) are returned via a hidden pointer.
|
||||||
|
;;; The caller allocates memory and passes the address in RDI (first arg register)
|
||||||
|
(define-vop (set-struct-return-pointer)
|
||||||
|
(:args (sap :scs (sap-reg) :target rdi))
|
||||||
|
(:temporary (:sc sap-reg :offset rdi-offset) rdi) ; RDI is the first arg register
|
||||||
|
(:generator 1
|
||||||
|
(move rdi sap)))
|
||||||
|
|
||||||
(defun make-call-out-tns (type)
|
(defun make-call-out-tns (type)
|
||||||
(let ((arg-state (make-arg-state)))
|
(let ((arg-state (make-arg-state))
|
||||||
(collect ((arg-tns))
|
(result-type (alien-fun-type-result-type type)))
|
||||||
(dolist (arg-type (alien-fun-type-arg-types type))
|
;; Check for large struct return FIRST - we need to reserve RDI for sret pointer
|
||||||
(arg-tns (invoke-alien-type-method :arg-tn arg-type arg-state)))
|
;; Cache the classification to avoid recomputing it in record-result-tn
|
||||||
(values (make-wired-tn* 'positive-fixnum any-reg-sc-number rsp-offset)
|
(let* ((result-classification
|
||||||
(* (arg-state-stack-frame-size arg-state) n-word-bytes)
|
(when (alien-record-type-p result-type)
|
||||||
(arg-tns)
|
(classify-struct-sysv-amd64 result-type)))
|
||||||
(invoke-alien-type-method :result-tn
|
(large-struct-return-p
|
||||||
(alien-fun-type-result-type type)
|
(and result-classification
|
||||||
(make-result-state))))))
|
(sb-alien::struct-classification-memory-p result-classification))))
|
||||||
|
;; For large struct returns, consume RDI (first int arg register)
|
||||||
|
;; so regular arguments start from RSI
|
||||||
|
(when large-struct-return-p
|
||||||
|
(setf (arg-state-register-args arg-state) 1))
|
||||||
|
(collect ((arg-tns))
|
||||||
|
(dolist (arg-type (alien-fun-type-arg-types type))
|
||||||
|
(arg-tns (invoke-alien-type-method :arg-tn arg-type arg-state)))
|
||||||
|
(let ((stack-frame-size (* (arg-state-stack-frame-size arg-state) n-word-bytes))
|
||||||
|
;; Bind cached classification so record-result-tn doesn't recompute
|
||||||
|
(*cached-struct-classification* result-classification))
|
||||||
|
(values (make-wired-tn* 'positive-fixnum any-reg-sc-number rsp-offset)
|
||||||
|
stack-frame-size
|
||||||
|
(arg-tns)
|
||||||
|
(invoke-alien-type-method :result-tn result-type (make-result-state))
|
||||||
|
;; 5th value: T if large struct return (sret pointer passed as first arg)
|
||||||
|
large-struct-return-p))))))
|
||||||
|
|
||||||
|
|
||||||
(deftransform %alien-funcall ((function type &rest args) * * :node node)
|
(deftransform %alien-funcall ((function type &rest args) * * :node node)
|
||||||
|
|
@ -137,8 +422,18 @@
|
||||||
(let* ((type (sb-c:lvar-value type))
|
(let* ((type (sb-c:lvar-value type))
|
||||||
(env (sb-c::node-lexenv node))
|
(env (sb-c::node-lexenv node))
|
||||||
(arg-types (alien-fun-type-arg-types type))
|
(arg-types (alien-fun-type-arg-types type))
|
||||||
(result-type (alien-fun-type-result-type type)))
|
(result-type (alien-fun-type-result-type type))
|
||||||
(aver (= (length arg-types) (length args)))
|
;; Large struct returns have a hidden first arg (sret pointer) added by IR1
|
||||||
|
#-sb-xc-host
|
||||||
|
(large-struct-return-p
|
||||||
|
(multiple-value-bind (in-registers-p register-slots size)
|
||||||
|
(sb-alien::struct-return-info result-type)
|
||||||
|
(declare (ignore register-slots))
|
||||||
|
(and size (not in-registers-p))))
|
||||||
|
#+sb-xc-host
|
||||||
|
(large-struct-return-p nil))
|
||||||
|
(aver (= (length arg-types)
|
||||||
|
(- (length args) (if large-struct-return-p 1 0))))
|
||||||
(if (or (some #'(lambda (type)
|
(if (or (some #'(lambda (type)
|
||||||
(and (alien-integer-type-p type)
|
(and (alien-integer-type-p type)
|
||||||
(> (sb-alien::alien-integer-type-bits type) 64)))
|
(> (sb-alien::alien-integer-type-bits type) 64)))
|
||||||
|
|
@ -470,10 +765,32 @@
|
||||||
|
|
||||||
#-sb-xc-host
|
#-sb-xc-host
|
||||||
(defun alien-callback-assembler-wrapper (index result-type argument-types)
|
(defun alien-callback-assembler-wrapper (index result-type argument-types)
|
||||||
|
;; Windows x64 uses Microsoft calling convention, not System V AMD64.
|
||||||
|
;; To add Windows struct-by-value callback support:
|
||||||
|
;; 1. Struct arguments >8 bytes: caller passes pointer, not value
|
||||||
|
;; 2. Struct arguments 1/2/4/8 bytes: passed in integer register as if integer
|
||||||
|
;; 3. Struct returns >8 bytes: hidden pointer in RCX (first arg register)
|
||||||
|
;; 4. Struct returns 1/2/4/8 bytes: returned in RAX
|
||||||
|
#+win32
|
||||||
|
(when (or (alien-record-type-p result-type)
|
||||||
|
(some #'sb-alien::alien-record-type-p argument-types))
|
||||||
|
(error "Struct-by-value callbacks not implemented for Windows x64 ABI"))
|
||||||
(labels ((make-tn-maker (sc-name)
|
(labels ((make-tn-maker (sc-name)
|
||||||
(lambda (offset)
|
(lambda (offset)
|
||||||
(make-random-tn (sc-or-lose sc-name) offset))))
|
(make-random-tn (sc-or-lose sc-name) offset)))
|
||||||
(let* ((segment (make-segment))
|
(argument-byte-size (type)
|
||||||
|
"Return the number of bytes this argument occupies in the callback vector."
|
||||||
|
(ceiling (sb-alien::alien-type-bits type) n-byte-bits))
|
||||||
|
(round-up-to-word (bytes)
|
||||||
|
(* n-word-bytes (ceiling bytes n-word-bytes))))
|
||||||
|
;; Check for struct return type and classify it
|
||||||
|
(let* ((result-classification
|
||||||
|
(when (alien-record-type-p result-type)
|
||||||
|
(classify-struct-sysv-amd64 result-type)))
|
||||||
|
(large-struct-return-p
|
||||||
|
(and result-classification
|
||||||
|
(sb-alien::struct-classification-memory-p result-classification)))
|
||||||
|
(segment (make-segment))
|
||||||
(rax rax-tn)
|
(rax rax-tn)
|
||||||
#+win32 (rcx rcx-tn)
|
#+win32 (rcx rcx-tn)
|
||||||
#-(and win32 sb-thread) (rdi rdi-tn)
|
#-(and win32 sb-thread) (rdi rdi-tn)
|
||||||
|
|
@ -483,60 +800,143 @@
|
||||||
(rsp rsp-tn)
|
(rsp rsp-tn)
|
||||||
#+(and win32 sb-thread) (r8 r8-tn)
|
#+(and win32 sb-thread) (r8 r8-tn)
|
||||||
(xmm0 float0-tn)
|
(xmm0 float0-tn)
|
||||||
|
#-win32
|
||||||
|
(xmm1 float1-tn)
|
||||||
([rsp] (ea rsp))
|
([rsp] (ea rsp))
|
||||||
;; How many arguments have been copied
|
;; Calculate total argument vector size in bytes
|
||||||
(arg-count 0)
|
(total-arg-bytes
|
||||||
;; How many arguments have been copied from the stack
|
(loop for type in argument-types
|
||||||
|
sum (round-up-to-word (argument-byte-size type))))
|
||||||
|
;; How many arguments have been copied from the C stack
|
||||||
(stack-argument-count #-win32 0 #+win32 4)
|
(stack-argument-count #-win32 0 #+win32 4)
|
||||||
(gprs (mapcar (make-tn-maker 'any-reg) *c-call-register-arg-offsets*))
|
;; Byte offset into argument vector
|
||||||
|
(arg-offset 0)
|
||||||
|
;; Count of 8-byte slots consumed (for stack offset calculation)
|
||||||
|
(arg-slot-count (ceiling total-arg-bytes n-word-bytes))
|
||||||
|
;; For large struct returns, RDI contains the hidden pointer, not an argument
|
||||||
|
;; Skip it in the GPR list so arguments start at RSI
|
||||||
|
(gprs (let ((all-gprs (mapcar (make-tn-maker 'any-reg) *c-call-register-arg-offsets*)))
|
||||||
|
(if large-struct-return-p
|
||||||
|
(rest all-gprs) ; Skip RDI
|
||||||
|
all-gprs)))
|
||||||
(fprs (mapcar (make-tn-maker 'double-reg)
|
(fprs (mapcar (make-tn-maker 'double-reg)
|
||||||
;; Only 8 first XMM registers are used for
|
;; Only 8 first XMM registers are used for
|
||||||
;; passing arguments
|
;; passing arguments
|
||||||
(subseq *float-regs* 0 #-win32 8 #+win32 4))))
|
(subseq *float-regs* 0 #-win32 8 #+win32 4)))
|
||||||
|
;; R11 is caller-saved and not used for arguments - use it to save hidden ptr
|
||||||
|
#-win32
|
||||||
|
(r11 (make-random-tn (sc-or-lose 'any-reg) r11-offset))
|
||||||
|
;; Calculate return value slot count (in 8-byte words)
|
||||||
|
;; For large struct returns, we need enough space for the entire struct
|
||||||
|
;; For small structs and primitives, 2 slots (16 bytes) is enough
|
||||||
|
(return-slot-count
|
||||||
|
(if large-struct-return-p
|
||||||
|
(ceiling (sb-alien::struct-classification-size result-classification) n-word-bytes)
|
||||||
|
2))
|
||||||
|
;; Adjust for alignment (must be even for 16-byte stack alignment)
|
||||||
|
(return-slot-count-aligned
|
||||||
|
(if (evenp (+ arg-slot-count return-slot-count))
|
||||||
|
return-slot-count
|
||||||
|
(1+ return-slot-count))))
|
||||||
(assemble (segment 'nil)
|
(assemble (segment 'nil)
|
||||||
;; Make room on the stack for arguments.
|
;; For large struct returns, save the hidden pointer (in RDI) to R11
|
||||||
(when argument-types
|
;; before we use RDI for anything else
|
||||||
(inst sub rsp (* n-word-bytes (length argument-types))))
|
#-win32
|
||||||
;; Copy arguments from registers to stack
|
(when large-struct-return-p
|
||||||
|
(inst mov r11 rdi))
|
||||||
|
;; Make room on the stack for argument vector.
|
||||||
|
(when (plusp total-arg-bytes)
|
||||||
|
(inst sub rsp total-arg-bytes))
|
||||||
|
;; Copy arguments from registers/stack to argument vector
|
||||||
(dolist (type argument-types)
|
(dolist (type argument-types)
|
||||||
(let ((integerp (not (alien-float-type-p type)))
|
(let* ((arg-size (round-up-to-word (argument-byte-size type)))
|
||||||
;; A TN pointing to the stack location where the
|
;; A TN pointing to the stack location where the
|
||||||
;; current argument should be stored for the purposes
|
;; current argument should be stored for the purposes
|
||||||
;; of ENTER-ALIEN-CALLBACK.
|
;; of ENTER-ALIEN-CALLBACK.
|
||||||
(target-tn (ea (* arg-count n-word-bytes) rsp))
|
(target-tn (ea arg-offset rsp))
|
||||||
;; A TN pointing to the stack location that contains
|
;; Offset to C stack args (past return address and our arg vector)
|
||||||
;; the next argument passed on the stack.
|
(stack-arg-tn (ea (* (+ 1 arg-slot-count stack-argument-count)
|
||||||
(stack-arg-tn (ea (* (+ 1 (length argument-types) stack-argument-count)
|
n-word-bytes) rsp)))
|
||||||
n-word-bytes) rsp)))
|
(cond
|
||||||
(incf arg-count)
|
;; Struct types
|
||||||
(cond (integerp
|
((sb-alien::alien-record-type-p type)
|
||||||
(let ((gpr (pop gprs)))
|
(let* ((classification (classify-struct-sysv-amd64 type))
|
||||||
#+win32 (pop fprs)
|
(memory-p (sb-alien::struct-classification-memory-p classification))
|
||||||
;; Argument not in register, copy it from the old
|
(slots (sb-alien::struct-classification-register-slots classification))
|
||||||
;; stack location to a temporary register.
|
(struct-size (sb-alien::struct-classification-size classification)))
|
||||||
(unless gpr
|
(cond
|
||||||
(incf stack-argument-count)
|
;; Large struct (MEMORY class): passed directly on the C stack
|
||||||
(setf gpr rax)
|
;; The caller copies the struct to its stack frame
|
||||||
(inst mov gpr stack-arg-tn))
|
(memory-p
|
||||||
;; Copy from either argument register or temporary
|
(let ((num-words (ceiling struct-size n-word-bytes)))
|
||||||
;; register to target.
|
;; Copy struct data from C stack to our argument vector
|
||||||
(inst mov target-tn gpr)))
|
(loop for i from 0 below num-words
|
||||||
((or (alien-single-float-type-p type)
|
for src-off = (* (+ 1 arg-slot-count stack-argument-count i)
|
||||||
(alien-double-float-type-p type))
|
n-word-bytes)
|
||||||
(let ((fpr (pop fprs)))
|
for dst-off from arg-offset by n-word-bytes
|
||||||
#+win32 (pop gprs)
|
do (inst mov rax (ea src-off rsp))
|
||||||
(cond (fpr
|
(inst mov (ea dst-off rsp) rax))
|
||||||
;; Copy from float register to target location.
|
;; Account for the stack slots consumed
|
||||||
(inst movq target-tn fpr))
|
(incf stack-argument-count num-words)))
|
||||||
(t
|
;; Small struct: passed in up to 2 registers per eightbyte
|
||||||
;; Not in float register. Copy from stack to
|
(t
|
||||||
;; temporary (general purpose) register, and
|
(loop for class in slots
|
||||||
;; from there to the target location.
|
for slot-offset from arg-offset by n-word-bytes
|
||||||
(incf stack-argument-count)
|
do (ecase class
|
||||||
(inst mov rax stack-arg-tn)
|
(:integer
|
||||||
(inst mov target-tn rax)))))
|
(let ((gpr (pop gprs)))
|
||||||
(t
|
#+win32 (pop fprs)
|
||||||
(bug "Unknown alien floating point type: ~S" type)))))
|
(unless gpr
|
||||||
|
(incf stack-argument-count)
|
||||||
|
(setf gpr rax)
|
||||||
|
(inst mov gpr (ea (* (+ 1 arg-slot-count stack-argument-count -1)
|
||||||
|
n-word-bytes) rsp)))
|
||||||
|
(inst mov (ea slot-offset rsp) gpr)))
|
||||||
|
(:double
|
||||||
|
(let ((fpr (pop fprs)))
|
||||||
|
#+win32 (pop gprs)
|
||||||
|
(cond (fpr
|
||||||
|
(inst movq (ea slot-offset rsp) fpr))
|
||||||
|
(t
|
||||||
|
(incf stack-argument-count)
|
||||||
|
(inst mov rax (ea (* (+ 1 arg-slot-count stack-argument-count -1)
|
||||||
|
n-word-bytes) rsp))
|
||||||
|
(inst mov (ea slot-offset rsp) rax)))))))))))
|
||||||
|
|
||||||
|
;; Integer/pointer types
|
||||||
|
((not (alien-float-type-p type))
|
||||||
|
(let ((gpr (pop gprs)))
|
||||||
|
#+win32 (pop fprs)
|
||||||
|
;; Argument not in register, copy it from the old
|
||||||
|
;; stack location to a temporary register.
|
||||||
|
(unless gpr
|
||||||
|
(incf stack-argument-count)
|
||||||
|
(setf gpr rax)
|
||||||
|
(inst mov gpr stack-arg-tn))
|
||||||
|
;; Copy from either argument register or temporary
|
||||||
|
;; register to target.
|
||||||
|
(inst mov target-tn gpr)))
|
||||||
|
|
||||||
|
;; Float types
|
||||||
|
((or (alien-single-float-type-p type)
|
||||||
|
(alien-double-float-type-p type))
|
||||||
|
(let ((fpr (pop fprs)))
|
||||||
|
#+win32 (pop gprs)
|
||||||
|
(cond (fpr
|
||||||
|
;; Copy from float register to target location.
|
||||||
|
(inst movq target-tn fpr))
|
||||||
|
(t
|
||||||
|
;; Not in float register. Copy from stack to
|
||||||
|
;; temporary (general purpose) register, and
|
||||||
|
;; from there to the target location.
|
||||||
|
(incf stack-argument-count)
|
||||||
|
(inst mov rax stack-arg-tn)
|
||||||
|
(inst mov target-tn rax)))))
|
||||||
|
|
||||||
|
(t
|
||||||
|
(bug "Unknown alien callback argument type: ~S" type)))
|
||||||
|
;; Advance to next argument slot
|
||||||
|
(incf arg-offset arg-size)))
|
||||||
|
|
||||||
(macrolet
|
(macrolet
|
||||||
((call-wrapper ()
|
((call-wrapper ()
|
||||||
|
|
@ -553,9 +953,7 @@
|
||||||
;; arg1 to ENTER-ALIEN-CALLBACK (pointer to argument vector)
|
;; arg1 to ENTER-ALIEN-CALLBACK (pointer to argument vector)
|
||||||
(inst mov rdi rsp)
|
(inst mov rdi rsp)
|
||||||
;; add room on stack for return value
|
;; add room on stack for return value
|
||||||
(inst sub rsp (if (evenp arg-count)
|
(inst sub rsp (* return-slot-count-aligned n-word-bytes))
|
||||||
(* n-word-bytes 2)
|
|
||||||
n-word-bytes))
|
|
||||||
;; arg2 to ENTER-ALIEN-CALLBACK (pointer to return value)
|
;; arg2 to ENTER-ALIEN-CALLBACK (pointer to return value)
|
||||||
(inst mov rsi rsp)
|
(inst mov rsi rsp)
|
||||||
|
|
||||||
|
|
@ -576,9 +974,7 @@
|
||||||
;; arg1 to ENTER-ALIEN-CALLBACK (pointer to argument vector)
|
;; arg1 to ENTER-ALIEN-CALLBACK (pointer to argument vector)
|
||||||
(inst mov #-win32 rsi #+win32 rdx rsp)
|
(inst mov #-win32 rsi #+win32 rdx rsp)
|
||||||
;; add room on stack for return value
|
;; add room on stack for return value
|
||||||
(inst sub rsp (if (evenp arg-count)
|
(inst sub rsp (* return-slot-count-aligned n-word-bytes))
|
||||||
(* n-word-bytes 2)
|
|
||||||
n-word-bytes))
|
|
||||||
;; arg2 to ENTER-ALIEN-CALLBACK (pointer to return value)
|
;; arg2 to ENTER-ALIEN-CALLBACK (pointer to return value)
|
||||||
(inst mov #-win32 rdx #+win32 r8 rsp)
|
(inst mov #-win32 rdx #+win32 r8 rsp)
|
||||||
;; Make new frame
|
;; Make new frame
|
||||||
|
|
@ -603,18 +999,46 @@
|
||||||
(alien-double-float-type-p result-type))
|
(alien-double-float-type-p result-type))
|
||||||
(inst movq xmm0 [rsp]))
|
(inst movq xmm0 [rsp]))
|
||||||
((alien-void-type-p result-type))
|
((alien-void-type-p result-type))
|
||||||
|
;; Struct return types
|
||||||
|
((alien-record-type-p result-type)
|
||||||
|
#-win32
|
||||||
|
(cond
|
||||||
|
;; Large struct: copy result to hidden pointer location, return pointer
|
||||||
|
(large-struct-return-p
|
||||||
|
(let ((struct-size (sb-alien::struct-classification-size result-classification)))
|
||||||
|
;; Copy struct data from stack to hidden pointer destination
|
||||||
|
(loop for off from 0 below struct-size by 8
|
||||||
|
do (inst mov rax (ea off rsp))
|
||||||
|
(inst mov (ea off r11) rax))
|
||||||
|
;; Return the hidden pointer in RAX
|
||||||
|
(inst mov rax r11)))
|
||||||
|
;; Small struct: copy to registers based on classification
|
||||||
|
(t
|
||||||
|
(let ((slots (sb-alien::struct-classification-register-slots result-classification))
|
||||||
|
(int-reg-idx 0)
|
||||||
|
(sse-reg-idx 0))
|
||||||
|
(loop for slot in slots
|
||||||
|
for offset from 0 by 8
|
||||||
|
do (ecase slot
|
||||||
|
(:integer
|
||||||
|
(let ((target (case int-reg-idx
|
||||||
|
(0 rax)
|
||||||
|
(1 rdx))))
|
||||||
|
(inst mov target (ea offset rsp)))
|
||||||
|
(incf int-reg-idx))
|
||||||
|
(:double
|
||||||
|
(let ((target (case sse-reg-idx
|
||||||
|
(0 xmm0)
|
||||||
|
(1 xmm1))))
|
||||||
|
(inst movq target (ea offset rsp)))
|
||||||
|
(incf sse-reg-idx))))))))
|
||||||
(t
|
(t
|
||||||
(error "Unrecognized alien type: ~A" result-type)))
|
(error "Unrecognized alien type: ~A" result-type)))
|
||||||
|
|
||||||
;; Pop the arguments and the return value from the stack to get
|
;; Pop the arguments and the return value from the stack to get
|
||||||
;; the return address at top of stack.
|
;; the return address at top of stack.
|
||||||
|
|
||||||
(inst add rsp (* (+ arg-count
|
(inst add rsp (* (+ arg-slot-count return-slot-count-aligned) n-word-bytes))
|
||||||
;; Plus the return value and make sure it's aligned
|
|
||||||
(if (evenp arg-count)
|
|
||||||
2
|
|
||||||
1))
|
|
||||||
n-word-bytes))
|
|
||||||
;; Return
|
;; Return
|
||||||
(inst ret))
|
(inst ret))
|
||||||
(finalize-segment segment)
|
(finalize-segment segment)
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,16 @@ long long tiny_align_8_get_m0(struct tiny_align_8 m) { return m.m0; }
|
||||||
void tiny_align_8_mutate(volatile struct tiny_align_8 m) {
|
void tiny_align_8_mutate(volatile struct tiny_align_8 m) {
|
||||||
m.m0++;
|
m.m0++;
|
||||||
}
|
}
|
||||||
|
struct tiny_align_8 tiny_align_8_return(long long val) {
|
||||||
|
struct tiny_align_8 result;
|
||||||
|
result.m0 = val;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
struct tiny_align_8 tiny_align_8_identity(struct tiny_align_8 m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
/** A small structure with 8-byte alignment.
|
/* A small structure with 8-byte alignment.
|
||||||
SysV x86-64 and AAPCS64 will pass this by register.
|
SysV x86-64 and AAPCS64 will pass this by register.
|
||||||
*/
|
*/
|
||||||
struct small_align_8 {
|
struct small_align_8 {
|
||||||
|
|
@ -18,8 +26,17 @@ void small_align_8_mutate(volatile struct small_align_8 m) {
|
||||||
m.m0++;
|
m.m0++;
|
||||||
m.m1++;
|
m.m1++;
|
||||||
}
|
}
|
||||||
|
struct small_align_8 small_align_8_return(long long v0, long long v1) {
|
||||||
|
struct small_align_8 result;
|
||||||
|
result.m0 = v0;
|
||||||
|
result.m1 = v1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
struct small_align_8 small_align_8_identity(struct small_align_8 m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
/** A large structure with 8-byte alignment.
|
/* A large structure with 8-byte alignment.
|
||||||
This should be too big for any architecture to pass by registers.
|
This should be too big for any architecture to pass by registers.
|
||||||
*/
|
*/
|
||||||
struct large_align_8 {
|
struct large_align_8 {
|
||||||
|
|
@ -49,7 +66,7 @@ large_align_8_get(m13);
|
||||||
large_align_8_get(m14);
|
large_align_8_get(m14);
|
||||||
large_align_8_get(m15);
|
large_align_8_get(m15);
|
||||||
|
|
||||||
/** Mutates the input struct. Volatile to avoid compiler optimizing away the mutation.*/
|
/* Mutates the input struct. Volatile to avoid compiler optimizing away the mutation.*/
|
||||||
void large_align_8_mutate(volatile struct large_align_8 m) {
|
void large_align_8_mutate(volatile struct large_align_8 m) {
|
||||||
m.m0++;
|
m.m0++;
|
||||||
m.m1++;
|
m.m1++;
|
||||||
|
|
@ -68,3 +85,332 @@ void large_align_8_mutate(volatile struct large_align_8 m) {
|
||||||
m.m14++;
|
m.m14++;
|
||||||
m.m15++;
|
m.m15++;
|
||||||
}
|
}
|
||||||
|
struct large_align_8 large_align_8_return(long long v0, long long v1) {
|
||||||
|
struct large_align_8 result = {0};
|
||||||
|
result.m0 = v0;
|
||||||
|
result.m1 = v1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
struct large_align_8 large_align_8_identity(struct large_align_8 m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Structs with floating point members for SSE register testing */
|
||||||
|
struct two_doubles {
|
||||||
|
double d0, d1;
|
||||||
|
};
|
||||||
|
struct two_doubles two_doubles_return(double d0, double d1) {
|
||||||
|
struct two_doubles result;
|
||||||
|
result.d0 = d0;
|
||||||
|
result.d1 = d1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
double two_doubles_sum(struct two_doubles m) {
|
||||||
|
return m.d0 + m.d1;
|
||||||
|
}
|
||||||
|
struct two_doubles two_doubles_identity(struct two_doubles m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct two_floats {
|
||||||
|
float f0, f1;
|
||||||
|
};
|
||||||
|
struct two_floats two_floats_return(float f0, float f1) {
|
||||||
|
struct two_floats result;
|
||||||
|
result.f0 = f0;
|
||||||
|
result.f1 = f1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
float two_floats_sum(struct two_floats m) {
|
||||||
|
return m.f0 + m.f1;
|
||||||
|
}
|
||||||
|
struct two_floats two_floats_identity(struct two_floats m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mixed int and float struct - tests split register handling on x86-64 */
|
||||||
|
struct int_double {
|
||||||
|
long long i;
|
||||||
|
double d;
|
||||||
|
};
|
||||||
|
struct int_double int_double_return(long long i, double d) {
|
||||||
|
struct int_double result;
|
||||||
|
result.i = i;
|
||||||
|
result.d = d;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
long long int_double_get_int(struct int_double m) { return m.i; }
|
||||||
|
double int_double_get_double(struct int_double m) { return m.d; }
|
||||||
|
struct int_double int_double_identity(struct int_double m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Medium struct (24 bytes) - too large for ARM64 registers, tests boundary */
|
||||||
|
struct medium_align_8 {
|
||||||
|
long long m0, m1, m2;
|
||||||
|
};
|
||||||
|
struct medium_align_8 medium_align_8_return(long long v0, long long v1, long long v2) {
|
||||||
|
struct medium_align_8 result;
|
||||||
|
result.m0 = v0;
|
||||||
|
result.m1 = v1;
|
||||||
|
result.m2 = v2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
long long medium_align_8_get_m0(struct medium_align_8 m) { return m.m0; }
|
||||||
|
long long medium_align_8_get_m1(struct medium_align_8 m) { return m.m1; }
|
||||||
|
long long medium_align_8_get_m2(struct medium_align_8 m) { return m.m2; }
|
||||||
|
struct medium_align_8 medium_align_8_identity(struct medium_align_8 m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Four floats struct - tests HFA (Homogeneous Floating-point Aggregate) on ARM64 */
|
||||||
|
struct four_floats {
|
||||||
|
float f0, f1, f2, f3;
|
||||||
|
};
|
||||||
|
struct four_floats four_floats_return(float f0, float f1, float f2, float f3) {
|
||||||
|
struct four_floats result;
|
||||||
|
result.f0 = f0;
|
||||||
|
result.f1 = f1;
|
||||||
|
result.f2 = f2;
|
||||||
|
result.f3 = f3;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
float four_floats_sum(struct four_floats m) {
|
||||||
|
return m.f0 + m.f1 + m.f2 + m.f3;
|
||||||
|
}
|
||||||
|
struct four_floats four_floats_identity(struct four_floats m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Three doubles struct - tests HFA boundary (3 doubles = 24 bytes) */
|
||||||
|
struct three_doubles {
|
||||||
|
double d0, d1, d2;
|
||||||
|
};
|
||||||
|
struct three_doubles three_doubles_return(double d0, double d1, double d2) {
|
||||||
|
struct three_doubles result;
|
||||||
|
result.d0 = d0;
|
||||||
|
result.d1 = d1;
|
||||||
|
result.d2 = d2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
double three_doubles_sum(struct three_doubles m) {
|
||||||
|
return m.d0 + m.d1 + m.d2;
|
||||||
|
}
|
||||||
|
struct three_doubles three_doubles_identity(struct three_doubles m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HFA with array of 4 floats - tests array-based HFA detection on ARM64 */
|
||||||
|
struct float_array_4 {
|
||||||
|
float arr[4];
|
||||||
|
};
|
||||||
|
struct float_array_4 float_array_4_return(float f0, float f1, float f2, float f3) {
|
||||||
|
struct float_array_4 result;
|
||||||
|
result.arr[0] = f0;
|
||||||
|
result.arr[1] = f1;
|
||||||
|
result.arr[2] = f2;
|
||||||
|
result.arr[3] = f3;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
float float_array_4_sum(struct float_array_4 m) {
|
||||||
|
return m.arr[0] + m.arr[1] + m.arr[2] + m.arr[3];
|
||||||
|
}
|
||||||
|
float float_array_4_get(struct float_array_4 m, int index) {
|
||||||
|
return m.arr[index];
|
||||||
|
}
|
||||||
|
struct float_array_4 float_array_4_identity(struct float_array_4 m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HFA with array of 2 doubles - tests array-based HFA with doubles */
|
||||||
|
struct double_array_2 {
|
||||||
|
double arr[2];
|
||||||
|
};
|
||||||
|
struct double_array_2 double_array_2_return(double d0, double d1) {
|
||||||
|
struct double_array_2 result;
|
||||||
|
result.arr[0] = d0;
|
||||||
|
result.arr[1] = d1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
double double_array_2_sum(struct double_array_2 m) {
|
||||||
|
return m.arr[0] + m.arr[1];
|
||||||
|
}
|
||||||
|
double double_array_2_get(struct double_array_2 m, int index) {
|
||||||
|
return m.arr[index];
|
||||||
|
}
|
||||||
|
struct double_array_2 double_array_2_identity(struct double_array_2 m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HFA with array of 3 floats - tests odd-sized array HFA */
|
||||||
|
struct float_array_3 {
|
||||||
|
float arr[3];
|
||||||
|
};
|
||||||
|
struct float_array_3 float_array_3_return(float f0, float f1, float f2) {
|
||||||
|
struct float_array_3 result;
|
||||||
|
result.arr[0] = f0;
|
||||||
|
result.arr[1] = f1;
|
||||||
|
result.arr[2] = f2;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
float float_array_3_sum(struct float_array_3 m) {
|
||||||
|
return m.arr[0] + m.arr[1] + m.arr[2];
|
||||||
|
}
|
||||||
|
struct float_array_3 float_array_3_identity(struct float_array_3 m) {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Callback tests for struct-by-value parameters */
|
||||||
|
|
||||||
|
/* Callback type taking a small struct (16 bytes, passed in registers) */
|
||||||
|
typedef long long (*small_struct_callback)(struct small_align_8 s);
|
||||||
|
|
||||||
|
/* Callback type taking a large struct (32+ bytes, passed on stack) */
|
||||||
|
typedef long long (*large_struct_callback)(struct large_align_8 s);
|
||||||
|
|
||||||
|
/* Callback type taking two small structs (like CXCursor pattern) */
|
||||||
|
typedef long long (*two_structs_callback)(struct small_align_8 s1, struct small_align_8 s2);
|
||||||
|
|
||||||
|
/* Callback type taking a struct with floats */
|
||||||
|
typedef double (*float_struct_callback)(struct two_doubles s);
|
||||||
|
|
||||||
|
/* Function that calls a callback with a small struct */
|
||||||
|
long long call_with_small_struct(small_struct_callback cb, long long v0, long long v1) {
|
||||||
|
struct small_align_8 s;
|
||||||
|
s.m0 = v0;
|
||||||
|
s.m1 = v1;
|
||||||
|
return cb(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Function that calls a callback with a large struct */
|
||||||
|
long long call_with_large_struct(large_struct_callback cb,
|
||||||
|
long long v0, long long v1, long long v2, long long v3) {
|
||||||
|
struct large_align_8 s = {0};
|
||||||
|
s.m0 = v0;
|
||||||
|
s.m1 = v1;
|
||||||
|
s.m2 = v2;
|
||||||
|
s.m3 = v3;
|
||||||
|
return cb(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Function that calls a callback with two small structs */
|
||||||
|
long long call_with_two_structs(two_structs_callback cb,
|
||||||
|
long long a0, long long a1,
|
||||||
|
long long b0, long long b1) {
|
||||||
|
struct small_align_8 s1, s2;
|
||||||
|
s1.m0 = a0;
|
||||||
|
s1.m1 = a1;
|
||||||
|
s2.m0 = b0;
|
||||||
|
s2.m1 = b1;
|
||||||
|
return cb(s1, s2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Function that calls a callback with a float struct */
|
||||||
|
double call_with_float_struct(float_struct_callback cb, double d0, double d1) {
|
||||||
|
struct two_doubles s;
|
||||||
|
s.d0 = d0;
|
||||||
|
s.d1 = d1;
|
||||||
|
return cb(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Callback type returning a small struct (16 bytes, in registers) */
|
||||||
|
typedef struct small_align_8 (*small_struct_return_callback)(long long v0, long long v1);
|
||||||
|
|
||||||
|
/* Callback type returning a struct with doubles (SSE registers) */
|
||||||
|
typedef struct two_doubles (*double_struct_return_callback)(double d0, double d1);
|
||||||
|
|
||||||
|
/* Callback type returning a large struct (>16 bytes, via hidden pointer) */
|
||||||
|
typedef struct medium_align_8 (*medium_struct_return_callback)(long long v0, long long v1, long long v2);
|
||||||
|
|
||||||
|
/* Function that calls a callback returning a small struct */
|
||||||
|
struct small_align_8 call_returning_small_struct(small_struct_return_callback cb,
|
||||||
|
long long v0, long long v1) {
|
||||||
|
return cb(v0, v1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Function that calls a callback returning a struct with doubles */
|
||||||
|
struct two_doubles call_returning_double_struct(double_struct_return_callback cb,
|
||||||
|
double d0, double d1) {
|
||||||
|
return cb(d0, d1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Function that calls a callback returning a large struct (hidden pointer) */
|
||||||
|
struct medium_align_8 call_returning_medium_struct(medium_struct_return_callback cb,
|
||||||
|
long long v0, long long v1, long long v2) {
|
||||||
|
return cb(v0, v1, v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Union tests - unions are a special case of records where all members share memory */
|
||||||
|
|
||||||
|
/* Small union (8 bytes) - fits in one register */
|
||||||
|
union small_union {
|
||||||
|
long long as_int;
|
||||||
|
double as_double;
|
||||||
|
};
|
||||||
|
union small_union small_union_from_int(long long val) {
|
||||||
|
union small_union u;
|
||||||
|
u.as_int = val;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
union small_union small_union_from_double(double val) {
|
||||||
|
union small_union u;
|
||||||
|
u.as_double = val;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
long long small_union_get_int(union small_union u) { return u.as_int; }
|
||||||
|
double small_union_get_double(union small_union u) { return u.as_double; }
|
||||||
|
union small_union small_union_identity(union small_union u) { return u; }
|
||||||
|
|
||||||
|
/* Medium union (16 bytes) - fits in two registers */
|
||||||
|
union medium_union {
|
||||||
|
struct { long long lo, hi; } as_pair;
|
||||||
|
struct { double d0, d1; } as_doubles;
|
||||||
|
};
|
||||||
|
union medium_union medium_union_from_pair(long long lo, long long hi) {
|
||||||
|
union medium_union u;
|
||||||
|
u.as_pair.lo = lo;
|
||||||
|
u.as_pair.hi = hi;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
union medium_union medium_union_from_doubles(double d0, double d1) {
|
||||||
|
union medium_union u;
|
||||||
|
u.as_doubles.d0 = d0;
|
||||||
|
u.as_doubles.d1 = d1;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
long long medium_union_get_lo(union medium_union u) { return u.as_pair.lo; }
|
||||||
|
long long medium_union_get_hi(union medium_union u) { return u.as_pair.hi; }
|
||||||
|
double medium_union_get_d0(union medium_union u) { return u.as_doubles.d0; }
|
||||||
|
double medium_union_get_d1(union medium_union u) { return u.as_doubles.d1; }
|
||||||
|
union medium_union medium_union_identity(union medium_union u) { return u; }
|
||||||
|
|
||||||
|
/* Large union (>16 bytes) - uses hidden pointer for return */
|
||||||
|
union large_union {
|
||||||
|
long long arr_int[4];
|
||||||
|
double arr_double[4];
|
||||||
|
};
|
||||||
|
union large_union large_union_from_ints(long long v0, long long v1, long long v2, long long v3) {
|
||||||
|
union large_union u;
|
||||||
|
u.arr_int[0] = v0;
|
||||||
|
u.arr_int[1] = v1;
|
||||||
|
u.arr_int[2] = v2;
|
||||||
|
u.arr_int[3] = v3;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
long long large_union_get_int(union large_union u, int index) { return u.arr_int[index]; }
|
||||||
|
double large_union_get_double(union large_union u, int index) { return u.arr_double[index]; }
|
||||||
|
union large_union large_union_identity(union large_union u) { return u; }
|
||||||
|
|
||||||
|
/* Callback tests for unions */
|
||||||
|
typedef long long (*small_union_callback)(union small_union u);
|
||||||
|
typedef union small_union (*small_union_return_callback)(long long val);
|
||||||
|
|
||||||
|
long long call_with_small_union(small_union_callback cb, long long val) {
|
||||||
|
union small_union u;
|
||||||
|
u.as_int = val;
|
||||||
|
return cb(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
union small_union call_returning_small_union(small_union_return_callback cb, long long val) {
|
||||||
|
return cb(val);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@
|
||||||
;;;; This software is in the public domain and is provided with
|
;;;; This software is in the public domain and is provided with
|
||||||
;;;; absolutely no warranty. See the COPYING and CREDITS files for
|
;;;; absolutely no warranty. See the COPYING and CREDITS files for
|
||||||
;;;; more information.
|
;;;; more information.
|
||||||
;(in-package :cl-user)
|
|
||||||
;;;; Bug 313202: C struct pass/return by value
|
#-(or (and x86-64 (not win32)) arm64) (invoke-restart 'run-tests::skip-file)
|
||||||
|
|
||||||
;;; Compile and load shared library
|
;;; Compile and load shared library
|
||||||
|
|
||||||
(defvar *soname*)
|
(defvar *soname*)
|
||||||
|
|
@ -30,45 +31,678 @@
|
||||||
(setq *soname* (truename "alien-struct-by-value.so"))
|
(setq *soname* (truename "alien-struct-by-value.so"))
|
||||||
(load-shared-object *soname*))
|
(load-shared-object *soname*))
|
||||||
|
|
||||||
(defmacro assert-unimplemented ((&whole def dar name ret &optional (arg nil argp)))
|
;;; Tiny struct, alignment 8 (fits in one register)
|
||||||
(declare (ignore dar ret))
|
|
||||||
;; all the "caught 1 fatal ERROR" notices are scary to those not expecting to see them
|
|
||||||
`(let ((*error-output* (make-broadcast-stream)))
|
|
||||||
(assert-error (eval '(progn ,def
|
|
||||||
,(if argp `(with-alien ((x ,(second arg))) (,name x)) '(name)))))))
|
|
||||||
|
|
||||||
;;; Tiny struct, alignment 8
|
|
||||||
(define-alien-type nil (struct tiny-align-8 (m0 (integer 64))))
|
(define-alien-type nil (struct tiny-align-8 (m0 (integer 64))))
|
||||||
(with-test (:name :struct-by-value-tiny-align-8-args)
|
|
||||||
(assert-unimplemented (define-alien-routine tiny-align-8-get-m0 (integer 64) (m (struct tiny-align-8))))
|
(define-alien-routine tiny-align-8-get-m0 (integer 64) (m (struct tiny-align-8)))
|
||||||
(assert-unimplemented (define-alien-routine tiny-align-8-mutate void (m (struct tiny-align-8))))
|
(define-alien-routine tiny-align-8-mutate void (m (struct tiny-align-8)))
|
||||||
(assert-unimplemented (define-alien-routine tiny-align-8-return (struct tiny-align-8))))
|
|
||||||
;;; Small struct, alignment 8
|
(define-alien-routine tiny-align-8-return (struct tiny-align-8) (val (integer 64)))
|
||||||
|
(define-alien-routine tiny-align-8-identity (struct tiny-align-8) (m (struct tiny-align-8)))
|
||||||
|
|
||||||
|
;;; Runtime tests for tiny struct
|
||||||
|
(with-test (:name :struct-by-value-tiny-align-8-runtime)
|
||||||
|
;; Test passing struct as argument
|
||||||
|
(with-alien ((s (struct tiny-align-8)))
|
||||||
|
(setf (slot s 'm0) 42)
|
||||||
|
(assert (= (tiny-align-8-get-m0 s) 42)))
|
||||||
|
;; Test passing different values
|
||||||
|
(with-alien ((s (struct tiny-align-8)))
|
||||||
|
(setf (slot s 'm0) -123456789)
|
||||||
|
(assert (= (tiny-align-8-get-m0 s) -123456789))))
|
||||||
|
|
||||||
|
;;; Runtime tests for tiny struct return
|
||||||
|
(with-test (:name :struct-by-value-tiny-align-8-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (tiny-align-8-return 42)))
|
||||||
|
(assert (= (slot result 'm0) 42)))
|
||||||
|
;; Test with negative value
|
||||||
|
(let ((result (tiny-align-8-return -987654321)))
|
||||||
|
(assert (= (slot result 'm0) -987654321)))
|
||||||
|
;; Test with zero
|
||||||
|
(let ((result (tiny-align-8-return 0)))
|
||||||
|
(assert (= (slot result 'm0) 0)))
|
||||||
|
;; Test with max positive value
|
||||||
|
(let ((result (tiny-align-8-return (1- (ash 1 63)))))
|
||||||
|
(assert (= (slot result 'm0) (1- (ash 1 63)))))
|
||||||
|
;; Test identity (pass struct, get same struct back)
|
||||||
|
(with-alien ((s (struct tiny-align-8)))
|
||||||
|
(setf (slot s 'm0) 12345)
|
||||||
|
(let ((result (tiny-align-8-identity s)))
|
||||||
|
(assert (= (slot result 'm0) 12345)))))
|
||||||
|
|
||||||
|
;;; Small struct, alignment 8 (fits in two registers)
|
||||||
(define-alien-type nil (struct small-align-8 (m0 (integer 64)) (m1 (integer 64))))
|
(define-alien-type nil (struct small-align-8 (m0 (integer 64)) (m1 (integer 64))))
|
||||||
(with-test (:name :struct-by-value-small-align-8-args)
|
|
||||||
(assert-unimplemented (define-alien-routine small-align-8-get-m0 (integer 64) (m (struct small-align-8))))
|
(define-alien-routine small-align-8-get-m0 (integer 64) (m (struct small-align-8)))
|
||||||
(assert-unimplemented (define-alien-routine small-align-8-get-m1 (integer 64) (m (struct small-align-8))))
|
(define-alien-routine small-align-8-get-m1 (integer 64) (m (struct small-align-8)))
|
||||||
(assert-unimplemented (define-alien-routine small-align-8-mutate void (m (struct small-align-8)))))
|
(define-alien-routine small-align-8-mutate void (m (struct small-align-8)))
|
||||||
;;; Large struct, alignment 8
|
|
||||||
|
(define-alien-routine small-align-8-return (struct small-align-8)
|
||||||
|
(v0 (integer 64)) (v1 (integer 64)))
|
||||||
|
(define-alien-routine small-align-8-identity (struct small-align-8) (m (struct small-align-8)))
|
||||||
|
|
||||||
|
;;; Runtime tests for small struct (2 registers)
|
||||||
|
(with-test (:name :struct-by-value-small-align-8-runtime)
|
||||||
|
;; Test passing struct as argument
|
||||||
|
(with-alien ((s (struct small-align-8)))
|
||||||
|
(setf (slot s 'm0) 100)
|
||||||
|
(setf (slot s 'm1) 200)
|
||||||
|
(assert (= (small-align-8-get-m0 s) 100))
|
||||||
|
(assert (= (small-align-8-get-m1 s) 200)))
|
||||||
|
;; Test with negative values
|
||||||
|
(with-alien ((s (struct small-align-8)))
|
||||||
|
(setf (slot s 'm0) -999)
|
||||||
|
(setf (slot s 'm1) 888)
|
||||||
|
(assert (= (small-align-8-get-m0 s) -999))
|
||||||
|
(assert (= (small-align-8-get-m1 s) 888))))
|
||||||
|
|
||||||
|
;;; Runtime tests for small struct return
|
||||||
|
(with-test (:name :struct-by-value-small-align-8-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (small-align-8-return 100 200)))
|
||||||
|
(assert (= (slot result 'm0) 100))
|
||||||
|
(assert (= (slot result 'm1) 200)))
|
||||||
|
;; Test with negative values
|
||||||
|
(let ((result (small-align-8-return -111 222)))
|
||||||
|
(assert (= (slot result 'm0) -111))
|
||||||
|
(assert (= (slot result 'm1) 222)))
|
||||||
|
;; Test with zeros
|
||||||
|
(let ((result (small-align-8-return 0 0)))
|
||||||
|
(assert (= (slot result 'm0) 0))
|
||||||
|
(assert (= (slot result 'm1) 0)))
|
||||||
|
;; Test identity (pass struct, get same struct back)
|
||||||
|
(with-alien ((s (struct small-align-8)))
|
||||||
|
(setf (slot s 'm0) 11111)
|
||||||
|
(setf (slot s 'm1) 22222)
|
||||||
|
(let ((result (small-align-8-identity s)))
|
||||||
|
(assert (= (slot result 'm0) 11111))
|
||||||
|
(assert (= (slot result 'm1) 22222)))))
|
||||||
|
|
||||||
|
;;; Large struct, alignment 8 (too big for registers, uses hidden pointer)
|
||||||
(define-alien-type nil
|
(define-alien-type nil
|
||||||
(struct large-align-8
|
(struct large-align-8
|
||||||
(m0 (integer 64)) (m4 (integer 64)) (m8 (integer 64)) (m12 (integer 64))
|
(m0 (integer 64)) (m4 (integer 64)) (m8 (integer 64)) (m12 (integer 64))
|
||||||
(m1 (integer 64)) (m5 (integer 64)) (m9 (integer 64)) (m13 (integer 64))
|
(m1 (integer 64)) (m5 (integer 64)) (m9 (integer 64)) (m13 (integer 64))
|
||||||
(m2 (integer 64)) (m6 (integer 64)) (m10 (integer 64)) (m14 (integer 64))
|
(m2 (integer 64)) (m6 (integer 64)) (m10 (integer 64)) (m14 (integer 64))
|
||||||
(m3 (integer 64)) (m7 (integer 64)) (m11 (integer 64)) (m15 (integer 64))))
|
(m3 (integer 64)) (m7 (integer 64)) (m11 (integer 64)) (m15 (integer 64))))
|
||||||
|
|
||||||
(with-test (:name :struct-by-value-large-align-8-args)
|
(with-test (:name :struct-by-value-large-align-8-args)
|
||||||
|
(macrolet
|
||||||
|
((def-large-align-8-get (i)
|
||||||
|
(let ((lisp-name (sb-int:symbolicate "LARGE-ALIGN-8-GET-M" i)))
|
||||||
|
`(define-alien-routine ,lisp-name (integer 64) (m (struct large-align-8)))))
|
||||||
|
(defs-large-align-8-get ()
|
||||||
|
"Test functions for each member"
|
||||||
|
(let ((defs (loop for i upto 15 collect `(def-large-align-8-get ,i))))
|
||||||
|
`(progn ,@defs))))
|
||||||
|
(defs-large-align-8-get)
|
||||||
|
(define-alien-routine large-align-8-mutate void (m (struct large-align-8))))
|
||||||
|
#-(or x86-64 arm64)
|
||||||
(macrolet
|
(macrolet
|
||||||
((def-large-align-8-get (i)
|
((def-large-align-8-get (i)
|
||||||
(let ((lisp-name (sb-int:symbolicate "LARGE-ALIGN-8-GET-M" i)))
|
(let ((lisp-name (sb-int:symbolicate "LARGE-ALIGN-8-GET-M" i)))
|
||||||
`(assert-unimplemented
|
`(assert-unimplemented
|
||||||
(define-alien-routine ,lisp-name (integer 64) (m (struct large-align-8))))))
|
(define-alien-routine ,lisp-name (integer 64) (m (struct large-align-8))))))
|
||||||
(defs-large-align-8-get ()
|
(defs-large-align-8-get ()
|
||||||
"Test functions for each member"
|
|
||||||
(let ((defs (loop for i upto 15 collect `(def-large-align-8-get ,i))))
|
(let ((defs (loop for i upto 15 collect `(def-large-align-8-get ,i))))
|
||||||
`(progn ,@defs))))
|
`(progn ,@defs))))
|
||||||
(defs-large-align-8-get)
|
(defs-large-align-8-get)
|
||||||
(assert-unimplemented
|
(assert-unimplemented
|
||||||
(define-alien-routine large-align-8-mutate void (m (struct large-align-8))))))
|
(define-alien-routine large-align-8-mutate void (m (struct large-align-8))))))
|
||||||
|
|
||||||
|
|
||||||
|
(define-alien-routine large-align-8-return (struct large-align-8)
|
||||||
|
(v0 (integer 64)) (v1 (integer 64)))
|
||||||
|
(define-alien-routine large-align-8-identity (struct large-align-8) (m (struct large-align-8)))
|
||||||
|
|
||||||
|
;;; Runtime tests for large struct (uses hidden pointer for return)
|
||||||
|
;;; Large structs (>16 bytes) are returned via hidden pointer in x8 (ARM64) or
|
||||||
|
;;; implicit first arg (x86-64).
|
||||||
|
(with-test (:name :struct-by-value-large-align-8-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (large-align-8-return 1000 2000)))
|
||||||
|
(assert (= (slot result 'm0) 1000))
|
||||||
|
(assert (= (slot result 'm1) 2000)))
|
||||||
|
;; Test identity (pass struct, get same struct back)
|
||||||
|
(with-alien ((s (struct large-align-8)))
|
||||||
|
(setf (slot s 'm0) 111)
|
||||||
|
(setf (slot s 'm1) 222)
|
||||||
|
(setf (slot s 'm2) 333)
|
||||||
|
(setf (slot s 'm3) 444)
|
||||||
|
(let ((result (large-align-8-identity s)))
|
||||||
|
(assert (= (slot result 'm0) 111))
|
||||||
|
(assert (= (slot result 'm1) 222))
|
||||||
|
(assert (= (slot result 'm2) 333))
|
||||||
|
(assert (= (slot result 'm3) 444)))))
|
||||||
|
|
||||||
|
;;; Floating-point struct tests (for SSE register handling on x86-64 and HFA on ARM64)
|
||||||
|
(define-alien-type nil (struct two-doubles (d0 double) (d1 double)))
|
||||||
|
(define-alien-type nil (struct two-floats (f0 single-float) (f1 single-float)))
|
||||||
|
(define-alien-type nil (struct int-double (i (integer 64)) (d double)))
|
||||||
|
|
||||||
|
|
||||||
|
(define-alien-routine two-doubles-return (struct two-doubles)
|
||||||
|
(d0 double) (d1 double))
|
||||||
|
(define-alien-routine two-doubles-sum double (m (struct two-doubles)))
|
||||||
|
(define-alien-routine two-doubles-identity (struct two-doubles) (m (struct two-doubles)))
|
||||||
|
|
||||||
|
;;; Runtime tests for floating-point struct (passing as argument)
|
||||||
|
(with-test (:name :struct-by-value-two-doubles-runtime)
|
||||||
|
(with-alien ((s (struct two-doubles)))
|
||||||
|
(setf (slot s 'd0) 1.5d0)
|
||||||
|
(setf (slot s 'd1) 2.5d0)
|
||||||
|
(assert (= (two-doubles-sum s) 4.0d0))))
|
||||||
|
|
||||||
|
;;; Runtime tests for floating-point struct return
|
||||||
|
(with-test (:name :struct-by-value-two-doubles-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (two-doubles-return 1.5d0 2.5d0)))
|
||||||
|
(assert (= (slot result 'd0) 1.5d0))
|
||||||
|
(assert (= (slot result 'd1) 2.5d0)))
|
||||||
|
;; Test with negative values
|
||||||
|
(let ((result (two-doubles-return -3.14159d0 2.71828d0)))
|
||||||
|
(assert (< (abs (- (slot result 'd0) -3.14159d0)) 1d-10))
|
||||||
|
(assert (< (abs (- (slot result 'd1) 2.71828d0)) 1d-10)))
|
||||||
|
;; Test with zeros
|
||||||
|
(let ((result (two-doubles-return 0.0d0 0.0d0)))
|
||||||
|
(assert (= (slot result 'd0) 0.0d0))
|
||||||
|
(assert (= (slot result 'd1) 0.0d0)))
|
||||||
|
;; Test identity (pass struct, get same struct back)
|
||||||
|
(with-alien ((s (struct two-doubles)))
|
||||||
|
(setf (slot s 'd0) 123.456d0)
|
||||||
|
(setf (slot s 'd1) 789.012d0)
|
||||||
|
(let ((result (two-doubles-identity s)))
|
||||||
|
(assert (= (slot result 'd0) 123.456d0))
|
||||||
|
(assert (= (slot result 'd1) 789.012d0)))))
|
||||||
|
|
||||||
|
(define-alien-routine two-floats-return (struct two-floats)
|
||||||
|
(f0 single-float) (f1 single-float))
|
||||||
|
(define-alien-routine two-floats-sum single-float (m (struct two-floats)))
|
||||||
|
(define-alien-routine two-floats-identity (struct two-floats) (m (struct two-floats)))
|
||||||
|
|
||||||
|
;;; Runtime tests for single-float struct return
|
||||||
|
(with-test (:name :struct-by-value-two-floats-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (two-floats-return 1.5 2.5)))
|
||||||
|
(assert (= (slot result 'f0) 1.5))
|
||||||
|
(assert (= (slot result 'f1) 2.5)))
|
||||||
|
;; Test with negative values
|
||||||
|
(let ((result (two-floats-return -3.5 4.5)))
|
||||||
|
(assert (= (slot result 'f0) -3.5))
|
||||||
|
(assert (= (slot result 'f1) 4.5)))
|
||||||
|
;; Test identity (pass struct, get same struct back)
|
||||||
|
(with-alien ((s (struct two-floats)))
|
||||||
|
(setf (slot s 'f0) 11.11)
|
||||||
|
(setf (slot s 'f1) 22.22)
|
||||||
|
(let ((result (two-floats-identity s)))
|
||||||
|
(assert (< (abs (- (slot result 'f0) 11.11)) 0.001))
|
||||||
|
(assert (< (abs (- (slot result 'f1) 22.22)) 0.001)))))
|
||||||
|
|
||||||
|
|
||||||
|
(define-alien-routine int-double-return (struct int-double)
|
||||||
|
(i (integer 64)) (d double))
|
||||||
|
(define-alien-routine int-double-get-int (integer 64) (m (struct int-double)))
|
||||||
|
(define-alien-routine int-double-get-double double (m (struct int-double)))
|
||||||
|
(define-alien-routine int-double-identity (struct int-double) (m (struct int-double)))
|
||||||
|
|
||||||
|
;;; Runtime tests for mixed int-double struct return
|
||||||
|
(with-test (:name :struct-by-value-int-double-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (int-double-return 42 3.14159d0)))
|
||||||
|
(assert (= (slot result 'i) 42))
|
||||||
|
(assert (< (abs (- (slot result 'd) 3.14159d0)) 1d-10)))
|
||||||
|
;; Test with negative values
|
||||||
|
(let ((result (int-double-return -999 -2.71828d0)))
|
||||||
|
(assert (= (slot result 'i) -999))
|
||||||
|
(assert (< (abs (- (slot result 'd) -2.71828d0)) 1d-10)))
|
||||||
|
;; Test identity (pass struct, get same struct back)
|
||||||
|
(with-alien ((s (struct int-double)))
|
||||||
|
(setf (slot s 'i) 12345)
|
||||||
|
(setf (slot s 'd) 67.89d0)
|
||||||
|
(let ((result (int-double-identity s)))
|
||||||
|
(assert (= (slot result 'i) 12345))
|
||||||
|
(assert (= (slot result 'd) 67.89d0)))))
|
||||||
|
|
||||||
|
;;; Medium struct (24 bytes) - tests boundary case (>16 bytes, uses hidden pointer)
|
||||||
|
(define-alien-type nil (struct medium-align-8 (m0 (integer 64)) (m1 (integer 64)) (m2 (integer 64))))
|
||||||
|
|
||||||
|
(define-alien-routine medium-align-8-return (struct medium-align-8)
|
||||||
|
(v0 (integer 64)) (v1 (integer 64)) (v2 (integer 64)))
|
||||||
|
(define-alien-routine medium-align-8-get-m0 (integer 64) (m (struct medium-align-8)))
|
||||||
|
(define-alien-routine medium-align-8-get-m1 (integer 64) (m (struct medium-align-8)))
|
||||||
|
(define-alien-routine medium-align-8-get-m2 (integer 64) (m (struct medium-align-8)))
|
||||||
|
(define-alien-routine medium-align-8-identity (struct medium-align-8) (m (struct medium-align-8)))
|
||||||
|
|
||||||
|
;;; Runtime tests for medium struct (24 bytes - uses hidden pointer)
|
||||||
|
(with-test (:name :struct-by-value-medium-align-8-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (medium-align-8-return 100 200 300)))
|
||||||
|
(assert (= (slot result 'm0) 100))
|
||||||
|
(assert (= (slot result 'm1) 200))
|
||||||
|
(assert (= (slot result 'm2) 300)))
|
||||||
|
;; Test identity
|
||||||
|
(with-alien ((s (struct medium-align-8)))
|
||||||
|
(setf (slot s 'm0) 111)
|
||||||
|
(setf (slot s 'm1) 222)
|
||||||
|
(setf (slot s 'm2) 333)
|
||||||
|
(let ((result (medium-align-8-identity s)))
|
||||||
|
(assert (= (slot result 'm0) 111))
|
||||||
|
(assert (= (slot result 'm1) 222))
|
||||||
|
(assert (= (slot result 'm2) 333)))))
|
||||||
|
|
||||||
|
;;; Four floats struct - tests HFA (Homogeneous Floating-point Aggregate) on ARM64
|
||||||
|
;;; 16 bytes total, fits in 4 single-precision FP registers on ARM64
|
||||||
|
(define-alien-type nil (struct four-floats (f0 single-float) (f1 single-float)
|
||||||
|
(f2 single-float) (f3 single-float)))
|
||||||
|
|
||||||
|
(define-alien-routine four-floats-return (struct four-floats)
|
||||||
|
(f0 single-float) (f1 single-float) (f2 single-float) (f3 single-float))
|
||||||
|
(define-alien-routine four-floats-sum single-float (m (struct four-floats)))
|
||||||
|
(define-alien-routine four-floats-identity (struct four-floats) (m (struct four-floats)))
|
||||||
|
|
||||||
|
(with-test (:name :struct-by-value-four-floats-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (four-floats-return 1.0 2.0 3.0 4.0)))
|
||||||
|
(assert (= (slot result 'f0) 1.0))
|
||||||
|
(assert (= (slot result 'f1) 2.0))
|
||||||
|
(assert (= (slot result 'f2) 3.0))
|
||||||
|
(assert (= (slot result 'f3) 4.0)))
|
||||||
|
;; Test sum (passing as argument)
|
||||||
|
(with-alien ((s (struct four-floats)))
|
||||||
|
(setf (slot s 'f0) 1.0)
|
||||||
|
(setf (slot s 'f1) 2.0)
|
||||||
|
(setf (slot s 'f2) 3.0)
|
||||||
|
(setf (slot s 'f3) 4.0)
|
||||||
|
(assert (= (four-floats-sum s) 10.0)))
|
||||||
|
;; Test identity
|
||||||
|
(with-alien ((s (struct four-floats)))
|
||||||
|
(setf (slot s 'f0) 1.5)
|
||||||
|
(setf (slot s 'f1) 2.5)
|
||||||
|
(setf (slot s 'f2) 3.5)
|
||||||
|
(setf (slot s 'f3) 4.5)
|
||||||
|
(let ((result (four-floats-identity s)))
|
||||||
|
(assert (= (slot result 'f0) 1.5))
|
||||||
|
(assert (= (slot result 'f1) 2.5))
|
||||||
|
(assert (= (slot result 'f2) 3.5))
|
||||||
|
(assert (= (slot result 'f3) 4.5)))))
|
||||||
|
|
||||||
|
;;; Three doubles struct - 24 bytes, HFA on ARM64 (fits in 3 double FP registers)
|
||||||
|
;;; But exceeds 16 bytes so may use memory return depending on ABI interpretation
|
||||||
|
(define-alien-type nil (struct three-doubles (d0 double) (d1 double) (d2 double)))
|
||||||
|
|
||||||
|
(define-alien-routine three-doubles-return (struct three-doubles)
|
||||||
|
(d0 double) (d1 double) (d2 double))
|
||||||
|
(define-alien-routine three-doubles-sum double (m (struct three-doubles)))
|
||||||
|
(define-alien-routine three-doubles-identity (struct three-doubles) (m (struct three-doubles)))
|
||||||
|
|
||||||
|
;;; Runtime tests for three doubles (24 bytes - uses hidden pointer)
|
||||||
|
(with-test (:name :struct-by-value-three-doubles-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (three-doubles-return 1.1d0 2.2d0 3.3d0)))
|
||||||
|
(assert (< (abs (- (slot result 'd0) 1.1d0)) 1d-10))
|
||||||
|
(assert (< (abs (- (slot result 'd1) 2.2d0)) 1d-10))
|
||||||
|
(assert (< (abs (- (slot result 'd2) 3.3d0)) 1d-10)))
|
||||||
|
;; Test sum (passing as argument)
|
||||||
|
(with-alien ((s (struct three-doubles)))
|
||||||
|
(setf (slot s 'd0) 1.0d0)
|
||||||
|
(setf (slot s 'd1) 2.0d0)
|
||||||
|
(setf (slot s 'd2) 3.0d0)
|
||||||
|
(assert (= (three-doubles-sum s) 6.0d0)))
|
||||||
|
;; Test identity
|
||||||
|
(with-alien ((s (struct three-doubles)))
|
||||||
|
(setf (slot s 'd0) 10.0d0)
|
||||||
|
(setf (slot s 'd1) 20.0d0)
|
||||||
|
(setf (slot s 'd2) 30.0d0)
|
||||||
|
(let ((result (three-doubles-identity s)))
|
||||||
|
(assert (= (slot result 'd0) 10.0d0))
|
||||||
|
(assert (= (slot result 'd1) 20.0d0))
|
||||||
|
(assert (= (slot result 'd2) 30.0d0)))))
|
||||||
|
|
||||||
|
;;; HFA with array of 4 floats - tests array-based HFA detection on ARM64
|
||||||
|
;;; This struct has a single field which is an array of 4 floats (16 bytes total)
|
||||||
|
;;; On ARM64, this should be detected as an HFA with 4 single-float members
|
||||||
|
(define-alien-type nil (struct float-array-4 (arr (array single-float 4))))
|
||||||
|
|
||||||
|
(define-alien-routine float-array-4-return (struct float-array-4)
|
||||||
|
(f0 single-float) (f1 single-float) (f2 single-float) (f3 single-float))
|
||||||
|
(define-alien-routine float-array-4-sum single-float (m (struct float-array-4)))
|
||||||
|
(define-alien-routine float-array-4-identity (struct float-array-4) (m (struct float-array-4)))
|
||||||
|
|
||||||
|
(with-test (:name :struct-by-value-float-array-4-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (float-array-4-return 1.0 2.0 3.0 4.0)))
|
||||||
|
(assert (= (deref (slot result 'arr) 0) 1.0))
|
||||||
|
(assert (= (deref (slot result 'arr) 1) 2.0))
|
||||||
|
(assert (= (deref (slot result 'arr) 2) 3.0))
|
||||||
|
(assert (= (deref (slot result 'arr) 3) 4.0)))
|
||||||
|
;; Test sum (passing as argument)
|
||||||
|
(with-alien ((s (struct float-array-4)))
|
||||||
|
(setf (deref (slot s 'arr) 0) 1.0)
|
||||||
|
(setf (deref (slot s 'arr) 1) 2.0)
|
||||||
|
(setf (deref (slot s 'arr) 2) 3.0)
|
||||||
|
(setf (deref (slot s 'arr) 3) 4.0)
|
||||||
|
(assert (= (float-array-4-sum s) 10.0)))
|
||||||
|
;; Test identity
|
||||||
|
(with-alien ((s (struct float-array-4)))
|
||||||
|
(setf (deref (slot s 'arr) 0) 1.5)
|
||||||
|
(setf (deref (slot s 'arr) 1) 2.5)
|
||||||
|
(setf (deref (slot s 'arr) 2) 3.5)
|
||||||
|
(setf (deref (slot s 'arr) 3) 4.5)
|
||||||
|
(let ((result (float-array-4-identity s)))
|
||||||
|
(assert (= (deref (slot result 'arr) 0) 1.5))
|
||||||
|
(assert (= (deref (slot result 'arr) 1) 2.5))
|
||||||
|
(assert (= (deref (slot result 'arr) 2) 3.5))
|
||||||
|
(assert (= (deref (slot result 'arr) 3) 4.5)))))
|
||||||
|
|
||||||
|
;;; HFA with array of 2 doubles - tests array-based HFA with doubles
|
||||||
|
;;; This struct has a single field which is an array of 2 doubles (16 bytes total)
|
||||||
|
(define-alien-type nil (struct double-array-2 (arr (array double 2))))
|
||||||
|
|
||||||
|
(define-alien-routine double-array-2-return (struct double-array-2)
|
||||||
|
(d0 double) (d1 double))
|
||||||
|
(define-alien-routine double-array-2-sum double (m (struct double-array-2)))
|
||||||
|
(define-alien-routine double-array-2-identity (struct double-array-2) (m (struct double-array-2)))
|
||||||
|
|
||||||
|
(with-test (:name :struct-by-value-double-array-2-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (double-array-2-return 1.5d0 2.5d0)))
|
||||||
|
(assert (= (deref (slot result 'arr) 0) 1.5d0))
|
||||||
|
(assert (= (deref (slot result 'arr) 1) 2.5d0)))
|
||||||
|
;; Test sum (passing as argument)
|
||||||
|
(with-alien ((s (struct double-array-2)))
|
||||||
|
(setf (deref (slot s 'arr) 0) 10.0d0)
|
||||||
|
(setf (deref (slot s 'arr) 1) 20.0d0)
|
||||||
|
(assert (= (double-array-2-sum s) 30.0d0)))
|
||||||
|
;; Test identity
|
||||||
|
(with-alien ((s (struct double-array-2)))
|
||||||
|
(setf (deref (slot s 'arr) 0) 100.0d0)
|
||||||
|
(setf (deref (slot s 'arr) 1) 200.0d0)
|
||||||
|
(let ((result (double-array-2-identity s)))
|
||||||
|
(assert (= (deref (slot result 'arr) 0) 100.0d0))
|
||||||
|
(assert (= (deref (slot result 'arr) 1) 200.0d0)))))
|
||||||
|
|
||||||
|
;;; HFA with array of 3 floats - tests odd-sized array HFA (12 bytes)
|
||||||
|
(define-alien-type nil (struct float-array-3 (arr (array single-float 3))))
|
||||||
|
|
||||||
|
(define-alien-routine float-array-3-return (struct float-array-3)
|
||||||
|
(f0 single-float) (f1 single-float) (f2 single-float))
|
||||||
|
(define-alien-routine float-array-3-sum single-float (m (struct float-array-3)))
|
||||||
|
(define-alien-routine float-array-3-identity (struct float-array-3) (m (struct float-array-3)))
|
||||||
|
|
||||||
|
(with-test (:name :struct-by-value-float-array-3-return-runtime)
|
||||||
|
;; Test return from C function
|
||||||
|
(let ((result (float-array-3-return 1.0 2.0 3.0)))
|
||||||
|
(assert (= (deref (slot result 'arr) 0) 1.0))
|
||||||
|
(assert (= (deref (slot result 'arr) 1) 2.0))
|
||||||
|
(assert (= (deref (slot result 'arr) 2) 3.0)))
|
||||||
|
;; Test sum (passing as argument)
|
||||||
|
(with-alien ((s (struct float-array-3)))
|
||||||
|
(setf (deref (slot s 'arr) 0) 1.0)
|
||||||
|
(setf (deref (slot s 'arr) 1) 2.0)
|
||||||
|
(setf (deref (slot s 'arr) 2) 3.0)
|
||||||
|
(assert (= (float-array-3-sum s) 6.0)))
|
||||||
|
;; Test identity
|
||||||
|
(with-alien ((s (struct float-array-3)))
|
||||||
|
(setf (deref (slot s 'arr) 0) 10.0)
|
||||||
|
(setf (deref (slot s 'arr) 1) 20.0)
|
||||||
|
(setf (deref (slot s 'arr) 2) 30.0)
|
||||||
|
(let ((result (float-array-3-identity s)))
|
||||||
|
(assert (= (deref (slot result 'arr) 0) 10.0))
|
||||||
|
(assert (= (deref (slot result 'arr) 1) 20.0))
|
||||||
|
(assert (= (deref (slot result 'arr) 2) 30.0)))))
|
||||||
|
|
||||||
|
;;;; Callback tests for struct-by-value parameters
|
||||||
|
;;;; These test receiving structs by value in Lisp callbacks called from C
|
||||||
|
|
||||||
|
;;; Define alien routines that call callbacks with struct parameters
|
||||||
|
(define-alien-routine call-with-small-struct (integer 64)
|
||||||
|
(cb system-area-pointer) (v0 (integer 64)) (v1 (integer 64)))
|
||||||
|
(define-alien-routine call-with-large-struct (integer 64)
|
||||||
|
(cb system-area-pointer)
|
||||||
|
(v0 (integer 64)) (v1 (integer 64)) (v2 (integer 64)) (v3 (integer 64)))
|
||||||
|
(define-alien-routine call-with-two-structs (integer 64)
|
||||||
|
(cb system-area-pointer)
|
||||||
|
(a0 (integer 64)) (a1 (integer 64)) (b0 (integer 64)) (b1 (integer 64)))
|
||||||
|
(define-alien-routine call-with-float-struct double
|
||||||
|
(cb system-area-pointer) (d0 double) (d1 double))
|
||||||
|
|
||||||
|
;;; Test callback with small struct parameter (16 bytes, passed in registers)
|
||||||
|
(with-test (:name :callback-struct-small)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb (integer 64) ((s (struct small-align-8)))
|
||||||
|
(+ (slot s 'm0) (slot s 'm1))))
|
||||||
|
(assert (= (call-with-small-struct (alien-sap cb) 10 20) 30))
|
||||||
|
(assert (= (call-with-small-struct (alien-sap cb) -100 200) 100))
|
||||||
|
(assert (= (call-with-small-struct (alien-sap cb) 0 0) 0))))
|
||||||
|
|
||||||
|
;;; Test callback with large struct parameter (128 bytes, passed on stack)
|
||||||
|
(with-test (:name :callback-struct-large)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb (integer 64) ((s (struct large-align-8)))
|
||||||
|
(+ (slot s 'm0) (slot s 'm1) (slot s 'm2) (slot s 'm3))))
|
||||||
|
(assert (= (call-with-large-struct (alien-sap cb) 1 2 3 4) 10))
|
||||||
|
(assert (= (call-with-large-struct (alien-sap cb) 100 200 300 400) 1000))
|
||||||
|
(assert (= (call-with-large-struct (alien-sap cb) -1 -2 -3 -4) -10))))
|
||||||
|
|
||||||
|
;;; Test callback with two struct parameters (like clang_visitChildren pattern)
|
||||||
|
(with-test (:name :callback-struct-two-structs)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb (integer 64) ((s1 (struct small-align-8))
|
||||||
|
(s2 (struct small-align-8)))
|
||||||
|
(+ (slot s1 'm0) (slot s1 'm1)
|
||||||
|
(slot s2 'm0) (slot s2 'm1))))
|
||||||
|
(assert (= (call-with-two-structs (alien-sap cb) 1 2 3 4) 10))
|
||||||
|
(assert (= (call-with-two-structs (alien-sap cb) 10 20 30 40) 100))))
|
||||||
|
|
||||||
|
;;; Test callback with float struct parameter (SSE registers)
|
||||||
|
(with-test (:name :callback-struct-floats)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb double ((s (struct two-doubles)))
|
||||||
|
(+ (slot s 'd0) (slot s 'd1))))
|
||||||
|
(assert (= (call-with-float-struct (alien-sap cb) 1.5d0 2.5d0) 4.0d0))
|
||||||
|
(assert (= (call-with-float-struct (alien-sap cb) 100.0d0 200.0d0) 300.0d0))))
|
||||||
|
|
||||||
|
;;; Define alien routines that call callbacks returning structs
|
||||||
|
(define-alien-routine call-returning-small-struct (struct small-align-8)
|
||||||
|
(cb system-area-pointer) (v0 (integer 64)) (v1 (integer 64)))
|
||||||
|
(define-alien-routine call-returning-double-struct (struct two-doubles)
|
||||||
|
(cb system-area-pointer) (d0 double) (d1 double))
|
||||||
|
(define-alien-routine call-returning-medium-struct (struct medium-align-8)
|
||||||
|
(cb system-area-pointer) (v0 (integer 64)) (v1 (integer 64)) (v2 (integer 64)))
|
||||||
|
|
||||||
|
;;; Test callback returning small struct (16 bytes, in registers)
|
||||||
|
(with-test (:name :callback-struct-return-small)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb (struct small-align-8) ((v0 (integer 64)) (v1 (integer 64)))
|
||||||
|
(with-alien ((s (struct small-align-8)))
|
||||||
|
(setf (slot s 'm0) v0)
|
||||||
|
(setf (slot s 'm1) v1)
|
||||||
|
s)))
|
||||||
|
(let ((result (call-returning-small-struct (alien-sap cb) 100 200)))
|
||||||
|
(assert (= (slot result 'm0) 100))
|
||||||
|
(assert (= (slot result 'm1) 200)))
|
||||||
|
(let ((result (call-returning-small-struct (alien-sap cb) -42 42)))
|
||||||
|
(assert (= (slot result 'm0) -42))
|
||||||
|
(assert (= (slot result 'm1) 42)))))
|
||||||
|
|
||||||
|
;;; Test callback returning struct with doubles (SSE registers)
|
||||||
|
(with-test (:name :callback-struct-return-doubles)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb (struct two-doubles) ((d0 double) (d1 double))
|
||||||
|
(with-alien ((s (struct two-doubles)))
|
||||||
|
(setf (slot s 'd0) d0)
|
||||||
|
(setf (slot s 'd1) d1)
|
||||||
|
s)))
|
||||||
|
(let ((result (call-returning-double-struct (alien-sap cb) 1.5d0 2.5d0)))
|
||||||
|
(assert (= (slot result 'd0) 1.5d0))
|
||||||
|
(assert (= (slot result 'd1) 2.5d0)))
|
||||||
|
(let ((result (call-returning-double-struct (alien-sap cb) -3.14d0 2.71d0)))
|
||||||
|
(assert (< (abs (- (slot result 'd0) -3.14d0)) 1d-10))
|
||||||
|
(assert (< (abs (- (slot result 'd1) 2.71d0)) 1d-10)))))
|
||||||
|
|
||||||
|
;;; Test callback returning large struct (24 bytes, via hidden pointer)
|
||||||
|
(with-test (:name :callback-struct-return-large
|
||||||
|
:broken-on :x86-64)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb (struct medium-align-8) ((v0 (integer 64)) (v1 (integer 64)) (v2 (integer 64)))
|
||||||
|
(with-alien ((s (struct medium-align-8)))
|
||||||
|
(setf (slot s 'm0) v0)
|
||||||
|
(setf (slot s 'm1) v1)
|
||||||
|
(setf (slot s 'm2) v2)
|
||||||
|
s)))
|
||||||
|
(let ((result (call-returning-medium-struct (alien-sap cb) 111 222 333)))
|
||||||
|
(assert (= (slot result 'm0) 111))
|
||||||
|
(assert (= (slot result 'm1) 222))
|
||||||
|
(assert (= (slot result 'm2) 333)))
|
||||||
|
(let ((result (call-returning-medium-struct (alien-sap cb) -1 0 1)))
|
||||||
|
(assert (= (slot result 'm0) -1))
|
||||||
|
(assert (= (slot result 'm1) 0))
|
||||||
|
(assert (= (slot result 'm2) 1)))))
|
||||||
|
|
||||||
|
;;;; Union-by-value tests
|
||||||
|
|
||||||
|
;;; Small union (8 bytes) - fits in one register
|
||||||
|
(define-alien-type nil (union small-union
|
||||||
|
(as-int (integer 64))
|
||||||
|
(as-double double)))
|
||||||
|
|
||||||
|
(define-alien-routine small-union-from-int (union small-union) (val (integer 64)))
|
||||||
|
(define-alien-routine small-union-from-double (union small-union) (val double))
|
||||||
|
(define-alien-routine small-union-get-int (integer 64) (u (union small-union)))
|
||||||
|
(define-alien-routine small-union-get-double double (u (union small-union)))
|
||||||
|
(define-alien-routine small-union-identity (union small-union) (u (union small-union)))
|
||||||
|
|
||||||
|
;;; Runtime tests for small union
|
||||||
|
(with-test (:name :union-by-value-small-runtime)
|
||||||
|
;; Test creating union from int and reading back
|
||||||
|
(let ((result (small-union-from-int 42)))
|
||||||
|
(assert (= (slot result 'as-int) 42)))
|
||||||
|
;; Test creating union from double and reading back
|
||||||
|
(let ((result (small-union-from-double 3.14159d0)))
|
||||||
|
(assert (< (abs (- (slot result 'as-double) 3.14159d0)) 1d-10)))
|
||||||
|
;; Test passing union as argument (as int)
|
||||||
|
(with-alien ((u (union small-union)))
|
||||||
|
(setf (slot u 'as-int) 12345)
|
||||||
|
(assert (= (small-union-get-int u) 12345)))
|
||||||
|
;; Test passing union as argument (as double)
|
||||||
|
(with-alien ((u (union small-union)))
|
||||||
|
(setf (slot u 'as-double) 2.71828d0)
|
||||||
|
(assert (< (abs (- (small-union-get-double u) 2.71828d0)) 1d-10)))
|
||||||
|
;; Test identity
|
||||||
|
(with-alien ((u (union small-union)))
|
||||||
|
(setf (slot u 'as-int) 999)
|
||||||
|
(let ((result (small-union-identity u)))
|
||||||
|
(assert (= (slot result 'as-int) 999)))))
|
||||||
|
|
||||||
|
;;; Medium union (16 bytes) - fits in two registers
|
||||||
|
(define-alien-type nil (union medium-union
|
||||||
|
(as-pair (struct medium-union-pair
|
||||||
|
(lo (integer 64))
|
||||||
|
(hi (integer 64))))
|
||||||
|
(as-doubles (struct medium-union-doubles
|
||||||
|
(d0 double)
|
||||||
|
(d1 double)))))
|
||||||
|
|
||||||
|
(define-alien-routine medium-union-from-pair (union medium-union)
|
||||||
|
(lo (integer 64)) (hi (integer 64)))
|
||||||
|
(define-alien-routine medium-union-from-doubles (union medium-union)
|
||||||
|
(d0 double) (d1 double))
|
||||||
|
(define-alien-routine medium-union-get-lo (integer 64) (u (union medium-union)))
|
||||||
|
(define-alien-routine medium-union-get-hi (integer 64) (u (union medium-union)))
|
||||||
|
(define-alien-routine medium-union-get-d0 double (u (union medium-union)))
|
||||||
|
(define-alien-routine medium-union-get-d1 double (u (union medium-union)))
|
||||||
|
(define-alien-routine medium-union-identity (union medium-union) (u (union medium-union)))
|
||||||
|
|
||||||
|
;;; Runtime tests for medium union
|
||||||
|
(with-test (:name :union-by-value-medium-runtime)
|
||||||
|
;; Test creating union from pair of ints
|
||||||
|
(let ((result (medium-union-from-pair 100 200)))
|
||||||
|
(assert (= (slot (slot result 'as-pair) 'lo) 100))
|
||||||
|
(assert (= (slot (slot result 'as-pair) 'hi) 200)))
|
||||||
|
;; Test creating union from pair of doubles
|
||||||
|
(let ((result (medium-union-from-doubles 1.5d0 2.5d0)))
|
||||||
|
(assert (= (slot (slot result 'as-doubles) 'd0) 1.5d0))
|
||||||
|
(assert (= (slot (slot result 'as-doubles) 'd1) 2.5d0)))
|
||||||
|
;; Test passing union as argument
|
||||||
|
(with-alien ((u (union medium-union)))
|
||||||
|
(setf (slot (slot u 'as-pair) 'lo) 111)
|
||||||
|
(setf (slot (slot u 'as-pair) 'hi) 222)
|
||||||
|
(assert (= (medium-union-get-lo u) 111))
|
||||||
|
(assert (= (medium-union-get-hi u) 222))
|
||||||
|
(let ((result (medium-union-identity u)))
|
||||||
|
(assert (= (slot (slot result 'as-pair) 'lo) 111))
|
||||||
|
(assert (= (slot (slot result 'as-pair) 'hi) 222)))))
|
||||||
|
|
||||||
|
;;; Large union (32 bytes) - uses hidden pointer for return
|
||||||
|
(define-alien-type nil (union large-union
|
||||||
|
(arr-int (array (integer 64) 4))
|
||||||
|
(arr-double (array double 4))))
|
||||||
|
|
||||||
|
(define-alien-routine large-union-from-ints (union large-union)
|
||||||
|
(v0 (integer 64)) (v1 (integer 64)) (v2 (integer 64)) (v3 (integer 64)))
|
||||||
|
(define-alien-routine large-union-get-int (integer 64)
|
||||||
|
(u (union large-union)) (index int))
|
||||||
|
(define-alien-routine large-union-get-double double
|
||||||
|
(u (union large-union)) (index int))
|
||||||
|
(define-alien-routine large-union-identity (union large-union) (u (union large-union)))
|
||||||
|
|
||||||
|
;;; Runtime tests for large union
|
||||||
|
(with-test (:name :union-by-value-large-runtime)
|
||||||
|
;; Test creating union from ints
|
||||||
|
(let ((result (large-union-from-ints 10 20 30 40)))
|
||||||
|
(assert (= (deref (slot result 'arr-int) 0) 10))
|
||||||
|
(assert (= (deref (slot result 'arr-int) 1) 20))
|
||||||
|
(assert (= (deref (slot result 'arr-int) 2) 30))
|
||||||
|
(assert (= (deref (slot result 'arr-int) 3) 40)))
|
||||||
|
;; Test passing union as argument
|
||||||
|
(with-alien ((u (union large-union)))
|
||||||
|
(setf (deref (slot u 'arr-int) 0) 100)
|
||||||
|
(setf (deref (slot u 'arr-int) 1) 200)
|
||||||
|
(setf (deref (slot u 'arr-int) 2) 300)
|
||||||
|
(setf (deref (slot u 'arr-int) 3) 400)
|
||||||
|
(assert (= (large-union-get-int u 0) 100))
|
||||||
|
(assert (= (large-union-get-int u 1) 200))
|
||||||
|
(assert (= (large-union-get-int u 2) 300))
|
||||||
|
(assert (= (large-union-get-int u 3) 400)))
|
||||||
|
;; Test identity
|
||||||
|
(with-alien ((u (union large-union)))
|
||||||
|
(setf (deref (slot u 'arr-int) 0) 1)
|
||||||
|
(setf (deref (slot u 'arr-int) 1) 2)
|
||||||
|
(setf (deref (slot u 'arr-int) 2) 3)
|
||||||
|
(setf (deref (slot u 'arr-int) 3) 4)
|
||||||
|
(let ((result (large-union-identity u)))
|
||||||
|
(assert (= (deref (slot result 'arr-int) 0) 1))
|
||||||
|
(assert (= (deref (slot result 'arr-int) 1) 2))
|
||||||
|
(assert (= (deref (slot result 'arr-int) 2) 3))
|
||||||
|
(assert (= (deref (slot result 'arr-int) 3) 4)))))
|
||||||
|
|
||||||
|
;;; Callback tests for unions
|
||||||
|
|
||||||
|
(define-alien-routine call-with-small-union (integer 64)
|
||||||
|
(cb system-area-pointer) (val (integer 64)))
|
||||||
|
(define-alien-routine call-returning-small-union (union small-union)
|
||||||
|
(cb system-area-pointer) (val (integer 64)))
|
||||||
|
|
||||||
|
;;; Test callback with union parameter
|
||||||
|
(with-test (:name :callback-union-parameter)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb (integer 64) ((u (union small-union)))
|
||||||
|
(slot u 'as-int)))
|
||||||
|
(assert (= (call-with-small-union (alien-sap cb) 42) 42))
|
||||||
|
(assert (= (call-with-small-union (alien-sap cb) -999) -999))))
|
||||||
|
|
||||||
|
;;; Test callback returning union
|
||||||
|
(with-test (:name :callback-union-return)
|
||||||
|
(with-alien-callable
|
||||||
|
((cb (union small-union) ((val (integer 64)))
|
||||||
|
(with-alien ((u (union small-union)))
|
||||||
|
(setf (slot u 'as-int) val)
|
||||||
|
u)))
|
||||||
|
(let ((result (call-returning-small-union (alien-sap cb) 12345)))
|
||||||
|
(assert (= (slot result 'as-int) 12345)))
|
||||||
|
(let ((result (call-returning-small-union (alien-sap cb) -54321)))
|
||||||
|
(assert (= (slot result 'as-int) -54321)))))
|
||||||
|
|
||||||
;;; Clean up
|
;;; Clean up
|
||||||
#-win32 (ignore-errors (delete-file *soname*))
|
#-win32 (ignore-errors (delete-file *soname*))
|
||||||
|
|
|
||||||
|
|
@ -1693,5 +1693,8 @@
|
||||||
(#(63C481C2 73D42188 937BB764 A4528420 B0E6341F D0F360C2 D5F368A1)
|
(#(63C481C2 73D42188 937BB764 A4528420 B0E6341F D0F360C2 D5F368A1)
|
||||||
"(CEILING FLOOR TRUNCATE ABS ASH * /)"
|
"(CEILING FLOOR TRUNCATE ABS ASH * /)"
|
||||||
"((& (+ (>> val 1) (>> val 22) (>> val 28)) 7))")
|
"((& (+ (>> val 1) (>> val 22) (>> val 28)) 7))")
|
||||||
|
(#(42D83FFB 71FB8EC1 9E6D8DE3 B8BBE117 FB0112B4)
|
||||||
|
"(:ALLOW-OTHER-KEYS :MEMORY-P :ALIGNMENT :SIZE :REGISTER-SLOTS)"
|
||||||
|
"((& (- (>> val 3) (>> val 22)) 7))")
|
||||||
)
|
)
|
||||||
;; EOF
|
;; EOF
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue