changelog shortlog graph tags branches changeset file revisions annotate raw help

Mercurial > core / lisp/lib/io/static-vector.lisp

revision 695: 2bad47888dbf
     1.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2+++ b/lisp/lib/io/static-vector.lisp	Fri Oct 04 16:14:44 2024 -0400
     1.3@@ -0,0 +1,299 @@
     1.4+;;; static-vector.lisp --- Static Vectors
     1.5+
     1.6+;; Vectors allocated in static memory. Useful for things like IO buffers
     1.7+;; created from Lisp and shared with C code.
     1.8+
     1.9+;;; Commentary:
    1.10+
    1.11+;; The source here is pulled directly from the STATIC-VECTORS package on
    1.12+;; Quicklisp: https://github.com/sionescu/static-vectors
    1.13+
    1.14+;;; Code:
    1.15+(in-package :io/static-vector)
    1.16+;;; --- Checking for compile-time constants and evaluating such forms
    1.17+
    1.18+(defun quotedp (form)
    1.19+  (and (listp form)
    1.20+       (= 2 (length form))
    1.21+       (eql 'quote (car form))))
    1.22+
    1.23+(defun constantp (form &optional env)
    1.24+  (let ((form (if (symbolp form)
    1.25+                  (macroexpand form env)
    1.26+                  form)))
    1.27+    (or (quotedp form)
    1.28+        (cl:constantp form))))
    1.29+
    1.30+(defun eval-constant (form &optional env)
    1.31+  (declare (ignorable env))
    1.32+  (cond
    1.33+    ((quotedp form)
    1.34+     (second form))
    1.35+    (t
    1.36+     #+clozure
    1.37+     (ccl::eval-constant form)
    1.38+     #+sbcl
    1.39+     (sb-int:constant-form-value form env)
    1.40+     #-(or clozure sbcl)
    1.41+     (eval form))))
    1.42+
    1.43+(defun canonicalize-args (env element-type length)
    1.44+  (let* ((eltype-spec (or (and (constantp element-type)
    1.45+                               (ignore-errors
    1.46+                                (upgraded-array-element-type
    1.47+                                 (eval-constant element-type))))
    1.48+                          '*))
    1.49+         (length-spec (if (constantp length env)
    1.50+                          `,(eval-constant length env)
    1.51+                          '*))
    1.52+         (type-decl (if (eql '* eltype-spec)
    1.53+                        'simple-array
    1.54+                        `(simple-array ,eltype-spec (,length-spec)))))
    1.55+    (values (if (eql '* eltype-spec)
    1.56+                element-type
    1.57+                `(quote ,eltype-spec))
    1.58+            (if (eql '* length-spec)
    1.59+                length
    1.60+                length-spec)
    1.61+            type-decl)))
    1.62+
    1.63+;;; --- SBCL implementation
    1.64+(declaim (inline fill-foreign-memory))
    1.65+(defun fill-foreign-memory (pointer length value)
    1.66+  "Fill LENGTH octets in foreign memory area POINTER with VALUE."
    1.67+  (std/alien:memset pointer value length)
    1.68+  pointer)
    1.69+
    1.70+(declaim (inline replace-foreign-memory))
    1.71+(defun replace-foreign-memory (dst-ptr src-ptr length)
    1.72+  "Copy LENGTH octets from foreign memory area SRC-PTR to DST-PTR."
    1.73+  (std/alien:memcpy dst-ptr src-ptr length)
    1.74+  dst-ptr)
    1.75+
    1.76+;;; We have to handle all the low-level bits including setting the array header
    1.77+;;; and keeping around the info about the original pointer returned by the
    1.78+;;; foreign allocator.
    1.79+;;;
    1.80+;;; It goes like this:
    1.81+;;;
    1.82+;;; 1. Compute the data size for the Lisp-visible memory (that means an extra #\Nul
    1.83+;;;    at the end for strings)
    1.84+;;; 2. Sum the data size, the SBCL header size, and our extra header size to get
    1.85+;;;    the total foreign size required
    1.86+;;; 3. Adjust the total foreign size to the required alignment, compute the header offset
    1.87+;;;    and write the headers.
    1.88+;;;
    1.89+;;; Array layout:
    1.90+;;;
    1.91+;;;    +-------------------+
    1.92+;;;    | Allocated address | <-- Original pointer
    1.93+;;;    +-------------------+
    1.94+;;;    | Start gap ...     | <-- For large alignments, there's a gap between
    1.95+;;;    |                   |     the data block and the headers.
    1.96+;;;    +-------------------+
    1.97+;;;    | SV header         | <-- The offset from the original pointer (DWORD)
    1.98+;;;    +-------------------+
    1.99+;;;    | Lisp array header | <-- Array element-type and size (DWORD)
   1.100+;;;    +-------------------+
   1.101+;;;    | Lisp array data   | <-- Lisp-visible data
   1.102+;;;    +-------------------+
   1.103+;;;
   1.104+;;; There's no end gap because when a alignment is requested,
   1.105+;;; the requested size must also be a multiple of the alignment.
   1.106+
   1.107+(defconstant +array-header-size+
   1.108+  (* sb-vm:vector-data-offset sb-vm:n-word-bytes))
   1.109+
   1.110+(declaim (inline vector-widetag-and-n-bytes))
   1.111+(defun vector-widetag-and-n-bytes (type)
   1.112+  "Returns the widetag and octet size of the upgraded array element type
   1.113+for a given type specifier."
   1.114+  (let ((upgraded-type (upgraded-array-element-type type)))
   1.115+    (case upgraded-type
   1.116+      ((nil t) (error "~A is not a specializable array element type" type))
   1.117+      (t
   1.118+       #+#.(cl:if (cl:find-symbol "%VECTOR-WIDETAG-AND-N-BITS" "SB-IMPL")
   1.119+                  '(and) '(or))
   1.120+       (sb-impl::%vector-widetag-and-n-bits type)
   1.121+       #+#.(cl:if (cl:find-symbol "%VECTOR-WIDETAG-AND-N-BITS-SHIFT" "SB-IMPL")
   1.122+                  '(and) '(or))
   1.123+       (multiple-value-bind (widetag shift)
   1.124+           (sb-impl::%vector-widetag-and-n-bits-shift type)
   1.125+         (values widetag (expt 2 (- shift 3))))))))
   1.126+
   1.127+(declaim (inline align))
   1.128+(defun align (size boundary)
   1.129+  (* boundary
   1.130+     (ceiling size boundary)))
   1.131+
   1.132+(declaim (inline %memalign))
   1.133+(defun %memalign (size alignment)
   1.134+  (with-alien ((box (* t)))
   1.135+    (let ((errno (std/alien:posix-memalign box alignment size)))
   1.136+      (when (not (zerop errno))
   1.137+        (error "posix_memalign() returned error ~A" errno))
   1.138+      ;; (mem-ref box :pointer)
   1.139+      (deref box))))
   1.140+
   1.141+(defun %allocate-static-vector (length element-type alignment)
   1.142+  (declare (type (unsigned-byte 16) alignment))
   1.143+  (flet ((allocation-sizes (length widetag n-bytes)
   1.144+           (values
   1.145+            ;; We're allocating two headers: one for SBCL and
   1.146+            ;; the other one for our bookkeeping.
   1.147+            (align (* 2 +array-header-size+) alignment)
   1.148+            ;; Align data size.
   1.149+            (align
   1.150+             (* (if (= widetag sb-vm:simple-character-string-widetag)
   1.151+                    (1+ length)         ; for the final #\Nul
   1.152+                    length)
   1.153+                n-bytes)
   1.154+             alignment))))
   1.155+    (multiple-value-bind (widetag n-bytes)
   1.156+        (vector-widetag-and-n-bytes element-type)
   1.157+      (multiple-value-bind (header-size data-size)
   1.158+          (allocation-sizes length widetag n-bytes)
   1.159+        (let* ((total-size (+ header-size data-size))
   1.160+               (foreign-block (%memalign total-size alignment))
   1.161+               (data-offset header-size )
   1.162+               (lisp-header-offset
   1.163+                 (- data-offset +array-header-size+))
   1.164+               (lisp-header-pointer
   1.165+                 (sb-sys:sap+ foreign-block lisp-header-offset))
   1.166+               (extra-header-offset
   1.167+                 (- data-offset (* 2 +array-header-size+)))
   1.168+               (extra-header-pointer
   1.169+                 (sb-sys:sap+ foreign-block extra-header-offset)))
   1.170+          ;; Write Lisp header: tag and length
   1.171+          (setf (sb-sys:sap-ref-word lisp-header-pointer 0) widetag)
   1.172+          (setf (sb-sys:sap-ref-word lisp-header-pointer sb-vm:n-word-bytes)
   1.173+                (sb-vm:fixnumize length))
   1.174+          ;; Save the relative position from the start of the foreign block
   1.175+          (setf (sb-sys:sap-ref-word extra-header-pointer 0)
   1.176+                (- data-offset (* 2 +array-header-size+)))
   1.177+          ;; Instantiate Lisp object
   1.178+          (sb-kernel:%make-lisp-obj (logior (sb-sys:sap-int lisp-header-pointer)
   1.179+                                            sb-vm:other-pointer-lowtag)))))))
   1.180+
   1.181+(declaim (inline static-vector-address))
   1.182+(defun static-vector-address (vector)
   1.183+  "Return a foreign pointer to start of the Lisp VECTOR(including its header).
   1.184+VECTOR must be a vector created by MAKE-STATIC-VECTOR."
   1.185+  (logandc2 (sb-kernel:get-lisp-obj-address vector)
   1.186+            sb-vm:lowtag-mask))
   1.187+
   1.188+(declaim (inline static-vector-pointer))
   1.189+(defun static-vector-pointer (vector &key (offset 0))
   1.190+  "Return a foreign pointer to the beginning of VECTOR + OFFSET octets.
   1.191+VECTOR must be a vector created by MAKE-STATIC-VECTOR."
   1.192+  (check-type offset unsigned-byte)
   1.193+  (sb-sys:int-sap (+ (static-vector-address vector)
   1.194+                   +array-header-size+
   1.195+                   offset)))
   1.196+
   1.197+(declaim (inline free-static-vector))
   1.198+(defun free-static-vector (vector)
   1.199+  "Free VECTOR, which must be a vector created by MAKE-STATIC-VECTOR."
   1.200+  (declare (sb-ext:muffle-conditions sb-ext:compiler-note))
   1.201+  (let* ((extra-header-pointer
   1.202+           (sb-sys:int-sap (- (static-vector-address vector) +array-header-size+)))
   1.203+         (start-offset
   1.204+           (sb-sys:sap-ref-word extra-header-pointer 0)))
   1.205+    (free-alien (sap-alien (sb-sys:sap+ extra-header-pointer (- start-offset)) (* (unsigned 8)))))
   1.206+  (values))
   1.207+
   1.208+(defmacro with-static-vector ((var length &rest args
   1.209+                               &key (element-type ''(unsigned-byte 8))
   1.210+                                 initial-contents initial-element)
   1.211+                              &body body &environment env)
   1.212+  "Bind PTR-VAR to a static vector of length LENGTH and execute BODY
   1.213+within its dynamic extent. The vector is freed upon exit."
   1.214+  (declare (ignorable element-type initial-contents initial-element))
   1.215+  (multiple-value-bind (real-element-type length type-spec)
   1.216+      (canonicalize-args env element-type length)
   1.217+    (let ((args (copy-list args)))
   1.218+      (remf args :element-type)
   1.219+      `(sb-sys:without-interrupts
   1.220+         (let ((,var (make-static-vector ,length ,@args
   1.221+                                         :element-type ,real-element-type)))
   1.222+           (declare (type ,type-spec ,var))
   1.223+           (unwind-protect
   1.224+                (sb-sys:with-local-interrupts ,@body)
   1.225+             (when ,var (free-static-vector ,var))))))))
   1.226+
   1.227+
   1.228+;;; --- MAKE-STATIC-VECTOR
   1.229+(declaim (inline check-initial-element))
   1.230+(defun check-initial-element (element-type initial-element)
   1.231+  (when (not (typep initial-element element-type))
   1.232+    ;; FIXME: signal SUBTYPE-ERROR
   1.233+    (error "MAKE-STATIC-VECTOR: The type of :INITIAL-ELEMENT ~S is not a subtype ~
   1.234+of the array's :ELEMENT-TYPE ~S"
   1.235+           initial-element element-type)))
   1.236+
   1.237+(declaim (inline check-initial-contents))
   1.238+(defun check-initial-contents (length initial-contents)
   1.239+  (let ((initial-contents-length (length initial-contents)))
   1.240+    (when (/= length initial-contents-length)
   1.241+      ;; FIXME: signal TYPE-ERROR
   1.242+      (error "MAKE-STATIC-VECTOR: There are ~A elements in the :INITIAL-CONTENTS, ~
   1.243+but requested vector length is ~A."
   1.244+             initial-contents-length length))))
   1.245+
   1.246+(declaim (inline check-initialization-arguments))
   1.247+(defun check-initialization-arguments (initial-element-p initial-contents-p)
   1.248+  (when (and initial-element-p initial-contents-p)
   1.249+    ;; FIXME: signal ARGUMENT-LIST-ERROR
   1.250+    (error "MAKE-STATIC-VECTOR: You must not specify both ~
   1.251+:INITIAL-ELEMENT and :INITIAL-CONTENTS")))
   1.252+
   1.253+(defun check-arguments (length element-type
   1.254+                        initial-element initial-element-p
   1.255+                        initial-contents initial-contents-p)
   1.256+  (check-initialization-arguments initial-element-p initial-contents-p)
   1.257+  (check-type length non-negative-fixnum)
   1.258+  (when initial-element-p
   1.259+    (check-initial-element element-type initial-element))
   1.260+  (when initial-contents-p
   1.261+    (check-initial-contents length initial-contents)))
   1.262+
   1.263+(defconstant +default-alignment+ 16)
   1.264+(defconstant +max-alignment+ 4096)
   1.265+
   1.266+(declaim (inline make-static-vector))
   1.267+(defun make-static-vector (length &key (element-type '(unsigned-byte 8))
   1.268+                           (initial-element nil initial-element-p)
   1.269+                           (initial-contents nil initial-contents-p)
   1.270+                           (alignment nil alignp))
   1.271+  "Create a simple vector of length LENGTH and type ELEMENT-TYPE which will
   1.272+not be moved by the garbage collector. The vector might be allocated in
   1.273+foreign memory so you must always call FREE-STATIC-VECTOR to free it."
   1.274+  (declare #+sbcl (sb-ext:muffle-conditions sb-ext:compiler-note)
   1.275+           (optimize speed))
   1.276+  (check-arguments length element-type initial-element initial-element-p
   1.277+                   initial-contents initial-contents-p)
   1.278+  (when alignp
   1.279+    ;; Check that the alignment is a power of 2 beteeen 16 and 4096.
   1.280+    #+(and sbcl unix)
   1.281+    (assert (and (<= +default-alignment+ alignment +max-alignment+)
   1.282+                 (= 1 (logcount alignment))))
   1.283+    #-(and sbcl unix)
   1.284+    (error "Allocation alignment not supported on this implementation."))
   1.285+  ;; TODO: fix %allocate-static-vector for all implementations
   1.286+  (let ((vector
   1.287+          (%allocate-static-vector length element-type
   1.288+                                   #+sbcl
   1.289+                                   (or alignment +default-alignment+))))
   1.290+    (if initial-element-p
   1.291+        (fill vector initial-element)
   1.292+        (replace vector initial-contents))))
   1.293+
   1.294+(defmacro with-static-vectors (((var length &rest args) &rest more-clauses)
   1.295+                               &body body)
   1.296+  "Allocate multiple static vectors at once."
   1.297+  `(with-static-vector (,var ,length ,@args)
   1.298+     ,@(if more-clauses
   1.299+           `((with-static-vectors ,more-clauses
   1.300+               ,@body))
   1.301+           body)))
   1.302+