mirror of
https://github.com/open-goal/jak-project
synced 2026-08-07 18:23:13 -04:00
b88552a35d
Co-authored-by: Tyler Wilding <xtvaser@gmail.com>
242 lines
7.8 KiB
Common Lisp
242 lines
7.8 KiB
Common Lisp
;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Hashing Functions
|
|
;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defconstant CRC_POLY #x04c11db7)
|
|
;; Could predefine this and only allocate memory when init is called
|
|
(define *crc-table* (new 'global 'boxed-array uint32 #x100))
|
|
(defun crc32-init ()
|
|
"Initialize the CRC32 table"
|
|
(dotimes (i #x100)
|
|
(let ((n (the uint32 (shl i 24))))
|
|
(dotimes (j 8)
|
|
(set! n (if (!= 0 (logand n #x80000000)) (logxor (shl n 1) CRC_POLY) (shl n 1))))
|
|
(set! (-> *crc-table* i) n))
|
|
)
|
|
)
|
|
|
|
(defun crc32 ((data (pointer uint8)) (size int))
|
|
"Take the CRC32 hash of some data"
|
|
(let ((crc (the uint32 0)))
|
|
(dotimes (i size)
|
|
(set! crc (logxor (-> *crc-table* (shr crc 24)) (logior (shl crc 8) (-> data 0))))
|
|
(&+! data 1))
|
|
crc)
|
|
)
|
|
|
|
(defmacro murmur-32-scramble (k-in)
|
|
`(let ((k ,k-in))
|
|
(*! k #xcc9e2d51)
|
|
(set! k (logior (shl k 15) (shr k 17)))
|
|
(*! k #x1b873593)
|
|
k)
|
|
)
|
|
|
|
(defun murmur3-32 ((key (pointer uint8)) (len int) (seed uint))
|
|
"Take the murmur3-32 hash of some data. The seed allows for a different hash set given the same input.
|
|
https://en.wikipedia.org/wiki/MurmurHash#Algorithm"
|
|
(let ((h seed)
|
|
(k (the uint32 0))
|
|
(i (shr len 2)) ;; Initialized to number of remaining 4-byte chunks
|
|
)
|
|
|
|
;; Read each 4-byte chunk
|
|
(while (!= i 0) (1-! i)
|
|
;; Copy this 4 byte chunk into a 32-bit int
|
|
(logior! k (shl (-> key 3) 24))
|
|
(logior! k (shl (-> key 2) 16))
|
|
(logior! k (shl (-> key 1) 8))
|
|
(logior! k (-> key))
|
|
|
|
;; Move our data forward to the next 4-byte chunk
|
|
(&+! key (the uint (size-of uint32)))
|
|
|
|
;; Scramble and shift
|
|
(logxor! h (murmur-32-scramble k))
|
|
(set! h (logior (shl h 13) (shr h 19)))
|
|
(set! h (+ (* h 5) #xe6546b64))
|
|
)
|
|
|
|
;; Read the rest
|
|
(set! k (the uint32 0))
|
|
(set! i (logand len 3))
|
|
(while (!= i 0)
|
|
(set! k (shl k 8))
|
|
(logior! k (-> key (- i 1)))
|
|
(1-! i)
|
|
)
|
|
|
|
;; Final scramble and shift
|
|
(logxor! h (murmur-32-scramble k))
|
|
(logxor! h len)
|
|
(logxor! h (shr h 16))
|
|
(*! h #x85ebca6b)
|
|
(logxor! h (shr h 13))
|
|
(*! h #xc2b2ae35)
|
|
(logxor! h (shr h 16))
|
|
h)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Hash Table Implementation
|
|
;;
|
|
;; This is a naive hash table
|
|
;; - assumes the key has a data field to hash, would need to add a hash method to types
|
|
;; - static size after initialization
|
|
;; - no collision resolution; it will overwrite
|
|
;; - assumes key size of 8 unless specified to avoid calculating
|
|
;; size and 8 works well with murmur3
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
(defmacro hash-truncate (value &key (size 8) &key (seed (the uint 62)))
|
|
"Take the murmur3-32 hash and truncate. Intended to keep the backing array of a hash table small.
|
|
Note that this increases the chance of collision."
|
|
`(logand (murmur3-32 ,value ,size ,seed) #xFF)
|
|
)
|
|
|
|
(defmacro new-hash-table (&key (type basic) &key (size 66))
|
|
"This is just an alias for a boxed array of the given type with a default type and size"
|
|
`(new 'global 'boxed-array ,type ,size)
|
|
)
|
|
|
|
(defmacro get-hash (table key &key (key-size 8) &key (seed (the uint 62)))
|
|
`(-> ,table (hash-truncate (-> ,key data) :size ,key-size :seed ,seed))
|
|
)
|
|
|
|
(defmacro set-hash! (table key &key (key-size 8) &key (seed (the uint 62)) value)
|
|
`(set! (-> ,table (hash-truncate (-> ,key data) :size ,key-size :seed ,seed)) ,value)
|
|
)
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;
|
|
;; Example usage
|
|
;;;;;;;;;;;;;;;;;;
|
|
;; (let ((table (new-hash-table)))
|
|
;; (set-hash! table "banana" "cat")
|
|
;; (set-hash! table "apple" "dog")
|
|
|
|
;; ;; prints "banana: cat, apple: dog"
|
|
;; (format #t "banana: ~S, apple: ~S\n" (get-hash table "banana") (get-hash table "apple"))
|
|
;; )
|
|
;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Jump Table Implementation
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; This is intended to replace (case)
|
|
;; when the keys are all integers. It trades
|
|
;; memory based on the highest key and in return
|
|
;; it avoids all of the branch comparisons
|
|
(defmacro defjumptable (table &rest cases)
|
|
"Builds a boxed-array of lambdas at startup where the size is the highest key and the index is the key for use as a jump table.
|
|
Note: The keys must all be integers."
|
|
`(begin
|
|
(let ((highest-value -9999))
|
|
,@(apply (lambda (case)
|
|
`(when (>= ,(car case) highest-value)
|
|
(when (= ,(car case) highest-value)
|
|
(format #t "[WARNING] Duplicate case in jump-table!\n")
|
|
)
|
|
(set! highest-value ,(car case))))
|
|
cases)
|
|
(define ,table (new 'global 'boxed-array function highest-value))
|
|
,@(apply (lambda (case)
|
|
`(set! (-> ,table ,(car case)) (lambda (,@(cadr case)) ,@(cddr case))))
|
|
cases))
|
|
)
|
|
)
|
|
|
|
(defmacro jump-to (table key &key (typespec ()) &rest args)
|
|
"Calls the lambda stored in the jump-table at the given key with the provided arguments"
|
|
`((the (function ,@typespec none) (-> ,table ,key)) ,@args)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;
|
|
;; Example usage
|
|
;;;;;;;;;;;;;;;;;;
|
|
;; (defjumptable *test-table*
|
|
;; (0 ((value int))
|
|
;; (format #t "case 0, value ~D\n" value)
|
|
;; )
|
|
;; (1 ((value int))
|
|
;; (format #t "case 1, value: ~D\n" value)
|
|
;; )
|
|
;; )
|
|
;;
|
|
;; prints "case 1, value: 32"
|
|
;; (jump-to *test-table* 1 :typespec (int) 32)
|
|
;;;;;;;;;;;;;;;;;;
|
|
|
|
;; This is intended to improve the performance
|
|
;; of large (case) statements of symbols, like
|
|
;; might be used in process :event hooks
|
|
|
|
;; It uses additional memory and must compute
|
|
;; the hash, but scales O(1) with the number of cases
|
|
;;
|
|
;; It is naive and assumes no collisions.
|
|
;; If keys collide, it will warn and a
|
|
;; different seed should be provided. This
|
|
;; could be improved to recompute automatically
|
|
;;
|
|
;; It assumes a static symbol length of 8 for
|
|
;; performance with murmur3 so that it fits within 2 chunks
|
|
;; and doesn't have to calculate the symbol length
|
|
;;
|
|
;; It truncates the hash to 3 bytes to minimize memory use,
|
|
;; but this increases the chance of collision
|
|
;;
|
|
;; In my testing, this starts to outperform (case) when > ~20 cases
|
|
|
|
(defmacro defjumptablesymb (table &rest cases)
|
|
"Builds a global boxed-array of lambdas at compile time where the size is the highest value provided, and the index is the switch-case key for use as a jump table"
|
|
`(begin
|
|
(let ((highest-value -9999))
|
|
,@(apply (lambda (case)
|
|
`(let ((hash (the int (hash-truncate (-> (symbol->string ,(car case)) data))))
|
|
)
|
|
(when (>= hash highest-value)
|
|
(when (= hash highest-value)
|
|
(format #t "[WARNING] Hash collision generating jump table!\n"))
|
|
(set! highest-value hash)
|
|
)
|
|
)
|
|
)
|
|
cases)
|
|
(define ,table (new 'global 'boxed-array function highest-value))
|
|
,@(apply (lambda (case)
|
|
;; Converts the symbol to a string by adding the string offset and then
|
|
`(set! (-> ,table (hash-truncate (-> (symbol->string ,(car case)) data)))
|
|
(lambda (,@(cadr case)) ,@(cddr case)))
|
|
)
|
|
cases)
|
|
)
|
|
)
|
|
)
|
|
|
|
(defmacro jump-to-sym (table key &key (typespec ()) &rest args)
|
|
"Calls the lambda stored in the jump-table at the hash of the given key
|
|
with provided typespec and arguments"
|
|
`((the (function ,@typespec none) (-> ,table (hash-truncate (-> (symbol->string ,key) data)))) ,@args)
|
|
)
|
|
|
|
;;;;;;;;;;;;;;;;;;
|
|
;; Example usage
|
|
;;;;;;;;;;;;;;;;;;
|
|
;; (defjumptablesymb *test-symbol-table*
|
|
;; ('jump ((value int))
|
|
;; (format #t "jumpin, value ~D\n" value)
|
|
;; )
|
|
;; ('flop ((value int))
|
|
;; (format #t "floppin, value: ~D\n" value)
|
|
;; )
|
|
;; )
|
|
;;
|
|
;; prints "floppin, value: 32"
|
|
;; (jump-to-sym *test-symbol-table* 'flop :typespec (int) 32)
|
|
;;;;;;;;;;;;;;;;;;
|
|
|
|
|