changelog shortlog graph tags branches changeset files revisions annotate raw help

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

changeset 695: 2bad47888dbf
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 16:14:44 -0400
permissions: -rw-r--r--
description: add static-vector
1 ;;; static-vector.lisp --- Static Vectors
2 
3 ;; Vectors allocated in static memory. Useful for things like IO buffers
4 ;; created from Lisp and shared with C code.
5 
6 ;;; Commentary:
7 
8 ;; The source here is pulled directly from the STATIC-VECTORS package on
9 ;; Quicklisp: https://github.com/sionescu/static-vectors
10 
11 ;;; Code:
12 (in-package :io/static-vector)
13 ;;; --- Checking for compile-time constants and evaluating such forms
14 
15 (defun quotedp (form)
16  (and (listp form)
17  (= 2 (length form))
18  (eql 'quote (car form))))
19 
20 (defun constantp (form &optional env)
21  (let ((form (if (symbolp form)
22  (macroexpand form env)
23  form)))
24  (or (quotedp form)
25  (cl:constantp form))))
26 
27 (defun eval-constant (form &optional env)
28  (declare (ignorable env))
29  (cond
30  ((quotedp form)
31  (second form))
32  (t
33  #+clozure
34  (ccl::eval-constant form)
35  #+sbcl
36  (sb-int:constant-form-value form env)
37  #-(or clozure sbcl)
38  (eval form))))
39 
40 (defun canonicalize-args (env element-type length)
41  (let* ((eltype-spec (or (and (constantp element-type)
42  (ignore-errors
43  (upgraded-array-element-type
44  (eval-constant element-type))))
45  '*))
46  (length-spec (if (constantp length env)
47  `,(eval-constant length env)
48  '*))
49  (type-decl (if (eql '* eltype-spec)
50  'simple-array
51  `(simple-array ,eltype-spec (,length-spec)))))
52  (values (if (eql '* eltype-spec)
53  element-type
54  `(quote ,eltype-spec))
55  (if (eql '* length-spec)
56  length
57  length-spec)
58  type-decl)))
59 
60 ;;; --- SBCL implementation
61 (declaim (inline fill-foreign-memory))
62 (defun fill-foreign-memory (pointer length value)
63  "Fill LENGTH octets in foreign memory area POINTER with VALUE."
64  (std/alien:memset pointer value length)
65  pointer)
66 
67 (declaim (inline replace-foreign-memory))
68 (defun replace-foreign-memory (dst-ptr src-ptr length)
69  "Copy LENGTH octets from foreign memory area SRC-PTR to DST-PTR."
70  (std/alien:memcpy dst-ptr src-ptr length)
71  dst-ptr)
72 
73 ;;; We have to handle all the low-level bits including setting the array header
74 ;;; and keeping around the info about the original pointer returned by the
75 ;;; foreign allocator.
76 ;;;
77 ;;; It goes like this:
78 ;;;
79 ;;; 1. Compute the data size for the Lisp-visible memory (that means an extra #\Nul
80 ;;; at the end for strings)
81 ;;; 2. Sum the data size, the SBCL header size, and our extra header size to get
82 ;;; the total foreign size required
83 ;;; 3. Adjust the total foreign size to the required alignment, compute the header offset
84 ;;; and write the headers.
85 ;;;
86 ;;; Array layout:
87 ;;;
88 ;;; +-------------------+
89 ;;; | Allocated address | <-- Original pointer
90 ;;; +-------------------+
91 ;;; | Start gap ... | <-- For large alignments, there's a gap between
92 ;;; | | the data block and the headers.
93 ;;; +-------------------+
94 ;;; | SV header | <-- The offset from the original pointer (DWORD)
95 ;;; +-------------------+
96 ;;; | Lisp array header | <-- Array element-type and size (DWORD)
97 ;;; +-------------------+
98 ;;; | Lisp array data | <-- Lisp-visible data
99 ;;; +-------------------+
100 ;;;
101 ;;; There's no end gap because when a alignment is requested,
102 ;;; the requested size must also be a multiple of the alignment.
103 
104 (defconstant +array-header-size+
105  (* sb-vm:vector-data-offset sb-vm:n-word-bytes))
106 
107 (declaim (inline vector-widetag-and-n-bytes))
108 (defun vector-widetag-and-n-bytes (type)
109  "Returns the widetag and octet size of the upgraded array element type
110 for a given type specifier."
111  (let ((upgraded-type (upgraded-array-element-type type)))
112  (case upgraded-type
113  ((nil t) (error "~A is not a specializable array element type" type))
114  (t
115  #+#.(cl:if (cl:find-symbol "%VECTOR-WIDETAG-AND-N-BITS" "SB-IMPL")
116  '(and) '(or))
117  (sb-impl::%vector-widetag-and-n-bits type)
118  #+#.(cl:if (cl:find-symbol "%VECTOR-WIDETAG-AND-N-BITS-SHIFT" "SB-IMPL")
119  '(and) '(or))
120  (multiple-value-bind (widetag shift)
121  (sb-impl::%vector-widetag-and-n-bits-shift type)
122  (values widetag (expt 2 (- shift 3))))))))
123 
124 (declaim (inline align))
125 (defun align (size boundary)
126  (* boundary
127  (ceiling size boundary)))
128 
129 (declaim (inline %memalign))
130 (defun %memalign (size alignment)
131  (with-alien ((box (* t)))
132  (let ((errno (std/alien:posix-memalign box alignment size)))
133  (when (not (zerop errno))
134  (error "posix_memalign() returned error ~A" errno))
135  ;; (mem-ref box :pointer)
136  (deref box))))
137 
138 (defun %allocate-static-vector (length element-type alignment)
139  (declare (type (unsigned-byte 16) alignment))
140  (flet ((allocation-sizes (length widetag n-bytes)
141  (values
142  ;; We're allocating two headers: one for SBCL and
143  ;; the other one for our bookkeeping.
144  (align (* 2 +array-header-size+) alignment)
145  ;; Align data size.
146  (align
147  (* (if (= widetag sb-vm:simple-character-string-widetag)
148  (1+ length) ; for the final #\Nul
149  length)
150  n-bytes)
151  alignment))))
152  (multiple-value-bind (widetag n-bytes)
153  (vector-widetag-and-n-bytes element-type)
154  (multiple-value-bind (header-size data-size)
155  (allocation-sizes length widetag n-bytes)
156  (let* ((total-size (+ header-size data-size))
157  (foreign-block (%memalign total-size alignment))
158  (data-offset header-size )
159  (lisp-header-offset
160  (- data-offset +array-header-size+))
161  (lisp-header-pointer
162  (sb-sys:sap+ foreign-block lisp-header-offset))
163  (extra-header-offset
164  (- data-offset (* 2 +array-header-size+)))
165  (extra-header-pointer
166  (sb-sys:sap+ foreign-block extra-header-offset)))
167  ;; Write Lisp header: tag and length
168  (setf (sb-sys:sap-ref-word lisp-header-pointer 0) widetag)
169  (setf (sb-sys:sap-ref-word lisp-header-pointer sb-vm:n-word-bytes)
170  (sb-vm:fixnumize length))
171  ;; Save the relative position from the start of the foreign block
172  (setf (sb-sys:sap-ref-word extra-header-pointer 0)
173  (- data-offset (* 2 +array-header-size+)))
174  ;; Instantiate Lisp object
175  (sb-kernel:%make-lisp-obj (logior (sb-sys:sap-int lisp-header-pointer)
176  sb-vm:other-pointer-lowtag)))))))
177 
178 (declaim (inline static-vector-address))
179 (defun static-vector-address (vector)
180  "Return a foreign pointer to start of the Lisp VECTOR(including its header).
181 VECTOR must be a vector created by MAKE-STATIC-VECTOR."
182  (logandc2 (sb-kernel:get-lisp-obj-address vector)
183  sb-vm:lowtag-mask))
184 
185 (declaim (inline static-vector-pointer))
186 (defun static-vector-pointer (vector &key (offset 0))
187  "Return a foreign pointer to the beginning of VECTOR + OFFSET octets.
188 VECTOR must be a vector created by MAKE-STATIC-VECTOR."
189  (check-type offset unsigned-byte)
190  (sb-sys:int-sap (+ (static-vector-address vector)
191  +array-header-size+
192  offset)))
193 
194 (declaim (inline free-static-vector))
195 (defun free-static-vector (vector)
196  "Free VECTOR, which must be a vector created by MAKE-STATIC-VECTOR."
197  (declare (sb-ext:muffle-conditions sb-ext:compiler-note))
198  (let* ((extra-header-pointer
199  (sb-sys:int-sap (- (static-vector-address vector) +array-header-size+)))
200  (start-offset
201  (sb-sys:sap-ref-word extra-header-pointer 0)))
202  (free-alien (sap-alien (sb-sys:sap+ extra-header-pointer (- start-offset)) (* (unsigned 8)))))
203  (values))
204 
205 (defmacro with-static-vector ((var length &rest args
206  &key (element-type ''(unsigned-byte 8))
207  initial-contents initial-element)
208  &body body &environment env)
209  "Bind PTR-VAR to a static vector of length LENGTH and execute BODY
210 within its dynamic extent. The vector is freed upon exit."
211  (declare (ignorable element-type initial-contents initial-element))
212  (multiple-value-bind (real-element-type length type-spec)
213  (canonicalize-args env element-type length)
214  (let ((args (copy-list args)))
215  (remf args :element-type)
216  `(sb-sys:without-interrupts
217  (let ((,var (make-static-vector ,length ,@args
218  :element-type ,real-element-type)))
219  (declare (type ,type-spec ,var))
220  (unwind-protect
221  (sb-sys:with-local-interrupts ,@body)
222  (when ,var (free-static-vector ,var))))))))
223 
224 
225 ;;; --- MAKE-STATIC-VECTOR
226 (declaim (inline check-initial-element))
227 (defun check-initial-element (element-type initial-element)
228  (when (not (typep initial-element element-type))
229  ;; FIXME: signal SUBTYPE-ERROR
230  (error "MAKE-STATIC-VECTOR: The type of :INITIAL-ELEMENT ~S is not a subtype ~
231 of the array's :ELEMENT-TYPE ~S"
232  initial-element element-type)))
233 
234 (declaim (inline check-initial-contents))
235 (defun check-initial-contents (length initial-contents)
236  (let ((initial-contents-length (length initial-contents)))
237  (when (/= length initial-contents-length)
238  ;; FIXME: signal TYPE-ERROR
239  (error "MAKE-STATIC-VECTOR: There are ~A elements in the :INITIAL-CONTENTS, ~
240 but requested vector length is ~A."
241  initial-contents-length length))))
242 
243 (declaim (inline check-initialization-arguments))
244 (defun check-initialization-arguments (initial-element-p initial-contents-p)
245  (when (and initial-element-p initial-contents-p)
246  ;; FIXME: signal ARGUMENT-LIST-ERROR
247  (error "MAKE-STATIC-VECTOR: You must not specify both ~
248 :INITIAL-ELEMENT and :INITIAL-CONTENTS")))
249 
250 (defun check-arguments (length element-type
251  initial-element initial-element-p
252  initial-contents initial-contents-p)
253  (check-initialization-arguments initial-element-p initial-contents-p)
254  (check-type length non-negative-fixnum)
255  (when initial-element-p
256  (check-initial-element element-type initial-element))
257  (when initial-contents-p
258  (check-initial-contents length initial-contents)))
259 
260 (defconstant +default-alignment+ 16)
261 (defconstant +max-alignment+ 4096)
262 
263 (declaim (inline make-static-vector))
264 (defun make-static-vector (length &key (element-type '(unsigned-byte 8))
265  (initial-element nil initial-element-p)
266  (initial-contents nil initial-contents-p)
267  (alignment nil alignp))
268  "Create a simple vector of length LENGTH and type ELEMENT-TYPE which will
269 not be moved by the garbage collector. The vector might be allocated in
270 foreign memory so you must always call FREE-STATIC-VECTOR to free it."
271  (declare #+sbcl (sb-ext:muffle-conditions sb-ext:compiler-note)
272  (optimize speed))
273  (check-arguments length element-type initial-element initial-element-p
274  initial-contents initial-contents-p)
275  (when alignp
276  ;; Check that the alignment is a power of 2 beteeen 16 and 4096.
277  #+(and sbcl unix)
278  (assert (and (<= +default-alignment+ alignment +max-alignment+)
279  (= 1 (logcount alignment))))
280  #-(and sbcl unix)
281  (error "Allocation alignment not supported on this implementation."))
282  ;; TODO: fix %allocate-static-vector for all implementations
283  (let ((vector
284  (%allocate-static-vector length element-type
285  #+sbcl
286  (or alignment +default-alignment+))))
287  (if initial-element-p
288  (fill vector initial-element)
289  (replace vector initial-contents))))
290 
291 (defmacro with-static-vectors (((var length &rest args) &rest more-clauses)
292  &body body)
293  "Allocate multiple static vectors at once."
294  `(with-static-vector (,var ,length ,@args)
295  ,@(if more-clauses
296  `((with-static-vectors ,more-clauses
297  ,@body))
298  body)))
299