changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/std/bit.lisp

changeset 291: a0dfde3cb3c4
child: e2e5c4831389
author: Richard Westhaver <ellis@rwest.io>
date: Mon, 22 Apr 2024 23:14:47 -0400
permissions: -rw-r--r--
description: begin :STD refactor
1 ;;; std/bit.lisp --- Bit manipulation
2 
3 ;;; Commentary:
4 
5 ;; CMUCL doc: https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node132.html
6 
7 ;; quick primer: https://cp-algorithms.com/algebra/bit-manipulation.html
8 
9 ;;; Code:
10 (in-package :std/bit)
11 
12 ;;; Types
13 ;; Bytes aren't necessarily 8 bits wide in Lisp. OCTET is always 8
14 ;; bits.
15 (deftype octet () '(unsigned-byte 8))
16 (deftype octet-vector (&optional length)
17  `(simple-array octet (,length)))
18 
19 ;;; Bits
20 (defun make-bits (length &rest args)
21  (apply #'make-array length (nconc (list :element-type 'bit) args)))
22 
23 ;; https://graphics.stanford.edu/~seander/bithacks.html
24 ;; http://www.azillionmonkeys.com/qed/asmexample.html
25 (defun haipart (n count)
26  (declare (fixnum n count))
27  (let ((x (abs n)))
28  (if (minusp count)
29  (ldb (byte (- count) 0) x)
30  (ldb (byte count (max 0 (- (integer-length x) count)))
31  x))))
32 
33 ;; minusp = 38 bytes
34 
35 ;; 29 bytes
36 (defun sign-bit (n)
37  "compute the sign bit of a fixnum. If N < 0 return -1 else return 0."
38  (declare (fixnum n))
39  (ash n (- 0 (integer-length n))))
40 
41 ;; 51 bytes (speed 3)
42 ;; 67 bytes (speed 1)
43 (defun different-signs-p (x y)
44  "Return non-nil iff x and y have opposite signs."
45  (declare (fixnum x y) (optimize (speed 1)))
46  (< (expt x y) 0))
47 
48 ;; TODO 2024-02-23:
49 (defun mortify-bits (x y)
50  "Interleave the bits of two numbers (Mortan numbers)."
51  (declare (fixnum x y)
52  (ignore x y))
53  ;; (loop for i across (integer-length)
54  ;; with z = 0
55  ;; ;; z |= (x & 1U << i) << i | (y & 1U << i) << (i + 1);
56  ;; do ()
57  ;; return z)
58  )
59 
60 (defun int-list-bits (n)
61  (declare (fixnum n))
62  (let ((bits '()))
63  (dotimes (position (integer-length n) bits)
64  (push (ldb (byte 1 position) n) bits))))
65 
66 (defun int-bit-vector (n)
67  (declare (fixnum n))
68  (let ((bits (make-array 0 :element-type 'bit :adjustable t :fill-pointer t)))
69  (dotimes (position (integer-length n) bits)
70  (vector-push-extend (ldb (byte 1 position) n) bits))))
71 
72 (defun aref-bit (octets idx)
73  (declare (octet-vector octets) (fixnum idx))
74  (multiple-value-bind (octet-idx bit-idx)
75  (truncate idx 8)
76  (ldb (byte 1 bit-idx)
77  (aref octets octet-idx))))
78 
79 (defun make-bit-vector (size &optional (fill 0))
80  "Make a BIT-VECTOR with SIZE and initial-element FILL which must be a
81 BIT 0|1. Note that this representation is not as useful as you might
82 think - bit-vectors don't have a direct mapping to integers/fixnums --
83 they are vectors (AKA arrays) first, and bits second. Attempting to
84 perform bitwise-ops ends up being very inefficient so whenever
85 possible, stick with fixnums and use LOG* functions."
86  (declare (bit fill))
87  (make-array size :initial-element fill :adjustable nil :element-type 'bit))
88 
89 ;; simple setter/getter for integer bits
90 (define-setf-expander logbit (index place &environment env)
91  (multiple-value-bind (temps vals stores store-form access-form)
92  (get-setf-expansion place env)
93  (let ((i (gensym))
94  (store (gensym))
95  (stemp (first stores)))
96  (values `(,i ,@temps)
97  `(,index ,@vals)
98  `(,store)
99  `(let ((,stemp (dpb ,store (byte 1 ,i) ,access-form))
100  ,@(cdr stores))
101  ,store-form
102  ,store)
103  `(logbit ,i ,access-form)))))
104 
105 (defun logbit (idx n)
106  (declare (fixnum idx n))
107  (ldb (byte 1 idx) n))
108 
109 ;;; Bitfields
110 
111 ;; see https://github.com/marcoheisig/bitfield
112 
113 ;; A bitfield is a simple, efficient mechanism for storing multiple
114 ;; discrete states into a single non-negative integer.
115 
116 (deftype bitfield ()
117  "A bitfield is a non-negative integer that efficiently encodes
118 information about some booleans, enumerations, or small integers."
119  'unsigned-byte)
120 
121 ;;; Bitfield Slots
122 (defgeneric bitfield-slot-name (bitfield-slot)
123  (:documentation
124  "Returns a symbol that is the name of the bitfield slot."))
125 
126 (defgeneric bitfield-slot-start (bitfield-slot)
127  (:documentation
128  "Returns the position of the first bit of this slot in the bitfield."))
129 
130 (defgeneric bitfield-slot-end (bitfield-slot)
131  (:documentation
132  "Returns the position right after the last bit of this slot in the bitfield."))
133 
134 (defgeneric bitfield-slot-size (bitfield-slot)
135  (:documentation
136  "Returns an unsigned byte that is the number of distinct states of the slot."))
137 
138 (defgeneric bitfield-slot-initform (bitfield-slot)
139  (:documentation
140  "Returns a form that produces the initial value for that slot."))
141 
142 (defgeneric bitfield-slot-pack (bitfield-slot value-form)
143  (:documentation
144  "Takes a form that produces a value and turns it into a form that produces
145 a non-negative integer representing that value."))
146 
147 (defgeneric bitfield-slot-unpack (bitfield-slot value-form)
148  (:documentation
149  "Take a form that produces a value that is encoded as a non-negative
150 integer (as produced by BITFIELD-SLOT-PACK), and turn it into a form that
151 produces the decoded value."))
152 
153 (defgeneric parse-atomic-bitfield-slot-specifier
154  (specifier &key initform)
155  (:documentation
156  "Parses an atomic bitfield slot specifier, i.e., a bitfield slot
157 specifier that is not a list. Returns three values:
158 
159 1. A designator for a bitfield slot class.
160 
161 2. The size of the bitfield slot.
162 
163 3. A list of additional arguments that will be supplied to MAKE-INSTANCE
164 when creating the bitfield slot instance."))
165 
166 (defgeneric parse-compound-bitfield-slot-specifier
167  (specifier arguments &key initform)
168  (:documentation
169  "Parses a compount bitfield slot specifier, i.e., a bitfield slot
170 specifier that is a list. The SPECIFIER is the CAR of that list and the
171 ARGUMENTS are the CDR of that list. Returns three values:
172 
173 1. A designator for a bitfield slot class.
174 
175 2. The size of the bitfield slot.
176 
177 3. A list of additional arguments that will be supplied to MAKE-INSTANCE
178 when creating the bitfield slot instance."))
179 
180 (defclass bitfield-slot ()
181  ((%name :initarg :name :reader bitfield-slot-name)
182  (%initform :initarg :initform :reader bitfield-slot-initform)
183  (%start :initarg :start :reader bitfield-slot-start)
184  (%end :initarg :end :reader bitfield-slot-end)
185  (%size :initarg :size :reader bitfield-slot-size)))
186 
187 ;;; Boolean Slots
188 (defclass bitfield-boolean-slot (bitfield-slot)
189  ())
190 
191 (defmethod bitfield-slot-pack ((slot bitfield-boolean-slot) value-form)
192  `(if ,value-form 1 0))
193 
194 (defmethod bitfield-slot-unpack ((slot bitfield-boolean-slot) value-form)
195  `(ecase ,value-form (0 nil) (1 t)))
196 
197 (defmethod parse-atomic-bitfield-slot-specifier
198  ((specifier (eql 'boolean)) &key (initform 'nil))
199  (values 'bitfield-boolean-slot
200  2
201  `(:initform ,initform)))
202 
203 ;;; Integer Slots
204 (defclass bitfield-integer-slot (bitfield-slot)
205  ((%offset
206  :type integer
207  :initarg :offset
208  :reader bitfield-integer-slot-offset)))
209 
210 (defmethod bitfield-slot-pack ((slot bitfield-integer-slot) value-form)
211  (let ((offset (bitfield-integer-slot-offset slot))
212  (size (bitfield-slot-size slot)))
213  `(the (integer 0 (,size))
214  (- (the (integer ,offset (,(+ offset size))) ,value-form)
215  ,offset))))
216 
217 (defmethod bitfield-slot-unpack ((slot bitfield-integer-slot) value-form)
218  (let ((offset (bitfield-integer-slot-offset slot))
219  (size (bitfield-slot-size slot)))
220  `(the (integer ,offset (,(+ offset size)))
221  (+ ,value-form ,offset))))
222 
223 (defmethod parse-atomic-bitfield-slot-specifier
224  ((specifier (eql 'bit)) &key (initform '0))
225  (values 'bitfield-unsigned-byte-slot
226  2
227  `(:offset 0 :initform ,initform)))
228 
229 (defmethod parse-compound-bitfield-slot-specifier
230  ((specifier (eql 'unsigned-byte)) arguments &key (initform '0))
231  (destructuring-bind (bits) arguments
232  (check-type bits unsigned-byte)
233  (values 'bitfield-integer-slot
234  (expt 2 bits)
235  `(:offset 0 :initform ,initform))))
236 
237 (defmethod parse-compound-bitfield-slot-specifier
238  ((specifier (eql 'signed-byte)) arguments &key (initform '0))
239  (destructuring-bind (bits) arguments
240  (check-type bits unsigned-byte)
241  (values 'bitfield-integer-slot
242  (expt 2 bits)
243  `(:offset ,(- (expt 2 (1- bits))) :initform ,initform))))
244 
245 (defmethod parse-compound-bitfield-slot-specifier
246  ((specifier (eql 'integer)) bounds &key (initform nil initform-supplied-p))
247  (flet ((fail ()
248  (error "Invalid integer bitfield slot specifier: ~S"
249  `(integer ,@bounds))))
250  (unless (typep bounds '(cons t (cons t null)))
251  (fail))
252  (destructuring-bind (lo hi) bounds
253  (let* ((start (typecase lo
254  (integer lo)
255  ((cons integer null)
256  (1+ (first lo)))
257  (otherwise (fail))))
258  (end (typecase hi
259  (integer (1+ hi))
260  ((cons integer null)
261  (first hi))
262  (otherwise (fail))))
263  (size (- end start)))
264  (unless (plusp size)
265  (fail))
266  (values 'bitfield-integer-slot
267  size
268  `(:offset ,start :initform ,(if initform-supplied-p initform start)))))))
269 
270 ;;; Member Slots
271 (defclass bitfield-member-slot (bitfield-slot)
272  ((%objects
273  :type list
274  :initarg :objects
275  :reader bitfield-member-slot-objects)))
276 
277 (defmethod bitfield-slot-pack ((slot bitfield-member-slot) value-form)
278  `(ecase ,value-form
279  ,@(loop for key in (bitfield-member-slot-objects slot)
280  for value from 0
281  collect `((,key) ,value))))
282 
283 (defmethod bitfield-slot-unpack ((slot bitfield-member-slot) value-form)
284  `(ecase ,value-form
285  ,@(loop for key from 0
286  for value in (bitfield-member-slot-objects slot)
287  collect `((,key) ',value))))
288 
289 (defmethod parse-compound-bitfield-slot-specifier
290  ((specifier (eql 'member)) objects &key (initform `',(first objects)))
291  (values 'bitfield-member-slot
292  (length objects)
293  `(:initform ,initform :objects ,objects)))
294 
295 ;;; Parsing
296 ;; The position right after the last slot that has been parsed so far.
297 (defvar *bitfield-position*)
298 
299 (defun parse-bitfield-slot (slot)
300  (destructuring-bind (slot-name slot-specifier &rest rest) slot
301  (check-type slot-name symbol)
302  (multiple-value-bind (slot-class size args)
303  (if (consp slot-specifier)
304  (apply #'parse-compound-bitfield-slot-specifier
305  (car slot-specifier)
306  (cdr slot-specifier)
307  rest)
308  (apply #'parse-atomic-bitfield-slot-specifier
309  slot-specifier
310  rest))
311  (apply #'make-instance slot-class
312  :name slot-name
313  :size size
314  :start *bitfield-position*
315  :end (incf *bitfield-position* (integer-length (1- size)))
316  args))))
317 
318 (defmacro define-bitfield (name &body slots)
319  "Defines an encoding of enumerable properties like booleans,
320 integers or finite sets as a single non-negative integer.
321 
322 For a supplied bitfield name NAME, and for some slot definitions of the
323 form (SLOT-NAME TYPE &KEY INITFORM &ALLOW-OTHER-KEYS), this macro defines
324 the following functions:
325 
326 1. A constructor named MAKE-{NAME}, that takes one keyword argument per
327  SLOT-NAME, similar to the default constructor generated by DEFSTRUCT.
328  It returns a bitfield whose entries have the values indicated by the
329  keyword arguments, or the supplied initform.
330 
331 2. A clone operation named CLONE-{NAME}, that takes an existing bitfield
332  and one keyword argument per SLOT-NAME. It returns a copy of the
333  existing bitfield, but where each supplied keyword argument supersedes
334  the value of the corresponding slot.
335 
336 3. A reader function named {NAME}-{SLOT-NAME} for each slot.
337 
338 In addition to these functions, NAME is defined as a suitable subtype of
339 UNSIGNED-BYTE.
340 
341 This macro supports boolean, integer, and member slots. It is also
342 possible to add new kinds of slots by defining new subclasses of
343 BITFIELD-SLOT and the corresponding methods on BITFIELD-SLOT-PACK,
344 BITFIELD-SLOT-UNPACK and PARSE-ATOMIC-BITFIELD-SLOT-SPECIFIER or
345 PARSE-COMPOUND-BITFIELD-SLOT-SPECIFIER.
346 
347  Example:
348 
349  (define-bitfield examplebits
350  (a boolean)
351  (b (signed-byte 2))
352  (c (unsigned-byte 3) :initform 1)
353  (d (integer -100 100))
354  (e (member foo bar baz)))
355 
356  (defun examplebits-values (examplebits)
357  (list
358  (examplebits-a examplebits)
359  (examplebits-b examplebits)
360  (examplebits-c examplebits)
361  (examplebits-d examplebits)
362  (examplebits-e examplebits)))
363 
364  (defparameter *default* (make-examplebits))
365 
366  (examplebits-values *default*)
367  ;; => (nil 0 1 -100 foo)
368 
369  (defparameter *explicit* (make-examplebits :a t :b -1 :c 7 :d 42 :e 'baz))
370 
371  (examplebits-values *explicit*)
372  ;; => (t -1 7 42 baz)
373 
374  (defparameter *clone* (clone-examplebits *explicit* :a nil :b -1 :c 2 :d -12 :e 'bar))
375 
376  (examplebits-values *clone*)
377  ;; => (nil -1 2 -12 bar)
378 "
379  (let* ((*bitfield-position* 0)
380  (package (symbol-package name))
381  (constructor
382  (intern (concatenate 'string "MAKE-" (symbol-name name)) package))
383  (cloner
384  (intern (concatenate 'string "CLONE-" (symbol-name name)) package))
385  (reader-prefix
386  (concatenate 'string ))
387  (slots
388  (mapcar #'parse-bitfield-slot slots))
389  (reader-names
390  (loop for slot in slots
391  collect
392  (intern (concatenate 'string (symbol-name name) "-" reader-prefix
393  (symbol-name (bitfield-slot-name slot)))
394  package))))
395  `(progn
396  (deftype ,name () '(unsigned-byte ,*bitfield-position*))
397  ;; Define all slot readers.
398  ,@(loop for slot in slots
399  for reader-name in reader-names
400  for start = (bitfield-slot-start slot)
401  for end = (bitfield-slot-end slot)
402  collect
403  `(declaim (inline ,reader-name))
404  collect
405  `(defun ,reader-name (,name)
406  (declare (,name ,name))
407  ,(bitfield-slot-unpack
408  slot
409  `(ldb (byte ,(- end start) ,start) ,name))))
410  ;; Define the cloner.
411  (declaim (inline ,cloner))
412  (defun ,cloner
413  (,name &key ,@(loop for slot in slots
414  for reader-name in reader-names
415  collect
416  `(,(bitfield-slot-name slot)
417  (,reader-name ,name))))
418  (declare (,name ,name))
419  (logior
420  ,@(loop for slot in slots
421  collect
422  `(ash ,(bitfield-slot-pack slot (bitfield-slot-name slot))
423  ,(bitfield-slot-start slot)))))
424  ;; Define the constructor.
425  (declaim (inline ,constructor))
426  (defun ,constructor
427  (&key ,@(loop for slot in slots
428  collect
429  `(,(bitfield-slot-name slot)
430  ,(bitfield-slot-initform slot))))
431  (logior
432  ,@(loop for slot in slots
433  collect
434  `(ash ,(bitfield-slot-pack slot (bitfield-slot-name slot))
435  ,(bitfield-slot-start slot)))))
436  ',name)))
437 
438 ;;; From bit-smasher
439 (declaim (type (simple-array (simple-bit-vector 4) (16)) *bit-map*))
440 (defvar *bit-map* #(#*0000
441  #*0001
442  #*0010
443  #*0011
444  #*0100
445  #*0101
446  #*0110
447  #*0111
448  #*1000
449  #*1001
450  #*1010
451  #*1011
452  #*1100
453  #*1101
454  #*1110
455  #*1111))
456 
457 (deftype hex-char ()
458  `(member #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9
459  #\a #\b #\c #\d #\e #\f
460  #\A #\B #\C #\D #\E #\F))
461 
462 (declaim (ftype (function (hex-char) (integer 0 16)) hexchar->int)
463  (inline hexchar->int))
464 (defun hexchar-to-int (char)
465  "Return the bit vector associated with a hex-value character CHAR from *bit-map*."
466  (declare (optimize (speed 2) (safety 0)))
467  (cond ((char<= #\0 char #\9) (- (char-code char) #.(char-code #\0)))
468  ((char<= #\a char #\f) (- (char-code char) #.(- (char-code #\a) 10)))
469  (t (- (char-code char) #.(- (char-code #\A) 10))
470  ;; always return these results
471  #+nil (char<= #\A char #\F))))
472 
473 ;;; From Ironclad
474 (defun hex-string-to-octet-vector (string &aux (start 0) (end (length string)))
475  "Parses a substring of STRING delimited by START and END of
476 hexadecimal digits into a byte array."
477  (declare (type string string))
478  (let* ((length
479  (ash (- end start) -1)
480  #+nil (/ (- end start) 2))
481  (key (make-array length :element-type '(unsigned-byte 8))))
482  (declare (type (simple-array (unsigned-byte 8)) key))
483  (loop for i from 0
484  for j from start below end by 2
485  do (setf (aref key i)
486  (+ (* (hexchar-to-int (char string j)) 16)
487  (hexchar-to-int (char string (1+ j)))))
488  finally (return key))))
489 
490 (defun octet-vector-to-hex-string (vector)
491  "Return a string containing the hexadecimal representation of the
492 subsequence of VECTOR between START and END. ELEMENT-TYPE controls
493 the element-type of the returned string."
494  (declare (type (vector (unsigned-byte 8)) vector))
495  (let* ((length (length vector))
496  (hexdigits #.(coerce "0123456789abcdef" 'simple-base-string)))
497  (loop with string = (make-string (* length 2) :element-type 'base-char)
498  for i from 0 below length
499  for j from 0 by 2
500  do (let ((byte (aref vector i)))
501  (declare (optimize (safety 0)))
502  (setf (aref string j)
503  (aref hexdigits (ldb (byte 4 4) byte))
504  (aref string (1+ j))
505  (aref hexdigits (ldb (byte 4 0) byte))))
506  finally (return string))))
507 
508 (defun octets-to-integer (octet-vec &optional (end (length octet-vec)))
509  (declare (type (simple-array (unsigned-byte 8)) octet-vec))
510  (do ((j 0 (1+ j))
511  (sum 0))
512  ((>= j end) sum)
513  (setf sum (+ (aref octet-vec j) (ash sum 8)))))
514 
515 (defun integer-to-octets (bignum &optional (n-bits (integer-length bignum)))
516  (let* ((n-bytes (ceiling n-bits 8))
517  (octet-vec (make-array n-bytes :element-type '(unsigned-byte 8))))
518  (declare (type (simple-array (unsigned-byte 8)) octet-vec))
519  (loop for i from (1- n-bytes) downto 0
520  for index from 0
521  do (setf (aref octet-vec index) (ldb (byte 8 (* i 8)) bignum))
522  finally (return octet-vec))))