hashtables.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. ;;; Hoot hashtables
  2. ;;; Copyright (C) 2023, 2024 David Thompson <dave@spritely.institute>
  3. ;;;
  4. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  5. ;;; you may not use this file except in compliance with the License.
  6. ;;; You may obtain a copy of the License at
  7. ;;;
  8. ;;; http://www.apache.org/licenses/LICENSE-2.0
  9. ;;;
  10. ;;; Unless required by applicable law or agreed to in writing, software
  11. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  12. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. ;;; See the License for the specific language governing permissions and
  14. ;;; limitations under the License.
  15. ;;; Commentary:
  16. ;;;
  17. ;;; R6RS-inspired hashtables.
  18. ;;;
  19. ;;; Code:
  20. (library (hoot hashtables)
  21. (export hashq
  22. hashv
  23. hash
  24. make-hashtable
  25. make-eq-hashtable
  26. make-eqv-hashtable
  27. hashtable?
  28. hashtable-hash
  29. hashtable-equiv
  30. hashtable-size
  31. hashtable-ref
  32. hashtable-set!
  33. hashtable-delete!
  34. hashtable-clear!
  35. hashtable-contains?
  36. hashtable-copy
  37. hashtable-keys
  38. hashtable-values
  39. hashtable-for-each
  40. hashtable-fold
  41. make-weak-key-hashtable
  42. weak-key-hashtable?
  43. weak-key-hashtable-ref
  44. weak-key-hashtable-set!
  45. weak-key-hashtable-delete!)
  46. (import (hoot primitives)
  47. (hoot pairs)
  48. (hoot numbers)
  49. (hoot bitwise)
  50. (hoot bitvectors)
  51. (hoot bytevectors)
  52. (hoot eq)
  53. (hoot equal)
  54. (hoot procedures)
  55. (hoot values)
  56. (hoot vectors)
  57. (hoot lists)
  58. (hoot records)
  59. (hoot strings)
  60. (hoot write)
  61. (hoot match)
  62. (hoot errors)
  63. (hoot cond-expand))
  64. (cond-expand
  65. (guile-vm
  66. (define (hashq key size) (guile:hashq key size))
  67. (define (hashv key size) (guile:hashv key size))
  68. (define (hash key size) (guile:hash key size)))
  69. (hoot
  70. (define (string-hash str)
  71. (%inline-wasm
  72. '(func (param $str (ref eq)) (result i64)
  73. (i64.extend_i32_u
  74. (call $string-hash
  75. (struct.get $string $str
  76. (ref.cast $string (local.get $str))))))
  77. str))
  78. (define (%hashq key)
  79. (%inline-wasm
  80. '(func (param $key (ref eq)) (result i64)
  81. (i64.extend_i32_u
  82. (call $hashq (local.get $key))))
  83. key))
  84. (define (%hashv key)
  85. (if (number? key)
  86. ;; Use hashq for integers, otherwise convert to a string and
  87. ;; hash that.
  88. (if (integer? key)
  89. (if (exact? key)
  90. (%hashq key)
  91. (%hashq (exact key)))
  92. (string-hash (number->string key)))
  93. (%hashq key)))
  94. (define (%hash key)
  95. ;; Simple, non-commutative hash code combiner.
  96. (define (combine-hashes h1 h2)
  97. (logxor (ash h1 5) h2))
  98. ;; For hashing records:
  99. (define (assq-ref alist k)
  100. (and (pair? alist)
  101. (if (eq? (caar alist) k)
  102. (cdar alist)
  103. (assq-ref (cdr alist) k))))
  104. (define (record-nfields record)
  105. (%struct-ref (%struct-vtable record) 0))
  106. (define (record-properties record)
  107. (%struct-ref (%struct-vtable record) 4))
  108. (define (record-opaque? record)
  109. (assq-ref (record-properties record) 'opaque))
  110. ;; This recursive hashing algorithm with effort limit is inspired
  111. ;; by Chez Scheme.
  112. (define (hash key k)
  113. (let ((k (- k 1)))
  114. (cond
  115. ((<= k 0) ; out of hash juice :(
  116. (values (%hashv key) 0))
  117. ((string? key)
  118. (values (string-hash key) k))
  119. ((pair? key)
  120. (let ((k/2 (ash (+ k 1) -1)))
  121. (call-with-values (lambda () (hash (car key) k/2))
  122. (lambda (h1 k*)
  123. (call-with-values (lambda () (hash (cdr key) (+ (- k k/2) k*)))
  124. (lambda (h2 k)
  125. (values (combine-hashes h1 h2) k)))))))
  126. ((vector? key)
  127. (let ((seed #xbeadcafe))
  128. (let lp ((i 0) (h seed) (k k))
  129. (if (and (< i (vector-length key)) (> k 0))
  130. (let ((k/2 (ash (+ k 1) -1)))
  131. (call-with-values (lambda () (hash (vector-ref key i) k/2))
  132. (lambda (h* k*)
  133. (lp (+ i 1) (combine-hashes h h*) (+ (- k k/2) k*)))))
  134. (values h k)))))
  135. ((bytevector? key)
  136. (values (%inline-wasm
  137. '(func (param $bv (ref eq)) (result i64)
  138. (i64.extend_i32_u
  139. (call $hash-bytevector
  140. (ref.cast $bytevector (local.get $bv)))))
  141. key)
  142. k))
  143. ((bitvector? key)
  144. (values (%inline-wasm
  145. '(func (param $bv (ref eq)) (result i64)
  146. (i64.extend_i32_u
  147. (call $hash-bitvector
  148. (ref.cast $bitvector (local.get $bv)))))
  149. key)
  150. k))
  151. ((record? key)
  152. (if (record-opaque? key)
  153. (values (%hashq key) k)
  154. (let ((nfields (record-nfields key))
  155. (seed #xfacefeed))
  156. (let lp ((i 0) (h seed) (k k))
  157. (if (and (< i nfields) (> k 0))
  158. (let ((k/2 (ash k -1)))
  159. (call-with-values (lambda ()
  160. (hash (%struct-ref key i) k/2))
  161. (lambda (h* k*)
  162. (lp (+ i 1) (combine-hashes h h*) (+ (- k k/2) k*)))))
  163. (values h k))))))
  164. (else
  165. (values (%hashv key) k)))))
  166. (call-with-values (lambda () (hash key 64))
  167. (lambda (hash-code k)
  168. hash-code)))
  169. (define max-hash-size (1- (ash 1 32)))
  170. (define (hashq key size)
  171. (check-size size max-hash-size 'hashq)
  172. (modulo (%hashq key) size))
  173. (define (hashv key size)
  174. (check-size size max-hash-size 'hashv)
  175. (modulo (%hashv key) size))
  176. (define (hash key size)
  177. (check-size size max-hash-size 'hash)
  178. (modulo (%hash key) size))))
  179. ;; Numbers taken from https://planetmath.org/goodhashtableprimes
  180. (define %bucket-sizes
  181. #(53 97 193 389 769 1543 3079 6151 12289 24593 98317 196613 393241 786433 1572869))
  182. (define %min-buckets 53)
  183. (define (lower-bound k)
  184. (quotient k 4))
  185. (define (upper-bound k)
  186. (quotient (* k 9) 10))
  187. (define (optimal-buckets k)
  188. (let ((last (- (vector-length %bucket-sizes) 1)))
  189. (let lp ((idx 0))
  190. (if (= idx last)
  191. (vector-ref %bucket-sizes last)
  192. (let ((size (vector-ref %bucket-sizes idx)))
  193. (if (> k (upper-bound size))
  194. (lp (+ idx 1))
  195. size))))))
  196. (define-record-type <hashtable>
  197. #:printer (lambda (table port)
  198. (display "#<hashtable size: " port)
  199. (display (hashtable-size table) port)
  200. (display ">" port))
  201. (%make-hashtable hash equiv size buckets lower upper)
  202. hashtable?
  203. (hash hashtable-hash)
  204. (equiv hashtable-equiv)
  205. (size hashtable-size set-hashtable-size!)
  206. (buckets hashtable-buckets set-hashtable-buckets!)
  207. ;; Lower and upper bounds for growing/shrinking
  208. (lower hashtable-lower set-hashtable-lower!)
  209. (upper hashtable-upper set-hashtable-upper!))
  210. (define* (make-hashtable #:optional (hash hash) (equiv equal?))
  211. "Return a new, empty hashtable that uses the hash procedure @var{hash}
  212. and equivalence procedure @var{equiv}."
  213. (%make-hashtable hash equiv 0 (make-vector %min-buckets '())
  214. 0 (upper-bound %min-buckets)))
  215. (define (make-eq-hashtable)
  216. "Return a new, empty hashtable that uses @code{eq?} as the equivalence
  217. function and hashes keys accordingly."
  218. (make-hashtable hashq eq?))
  219. (define (make-eqv-hashtable)
  220. "Return a new, empty hashtable that uses @code{eqv?} as the equivalence
  221. function and hashes keys accordingly."
  222. (make-hashtable hashv eqv?))
  223. (define* (hashtable-ref table key #:optional default)
  224. "Return the value associated with @var{key} in @var{table}, or
  225. @var{default} if there is no such association."
  226. (let ((hash (hashtable-hash table))
  227. (equiv? (hashtable-equiv table))
  228. (buckets (hashtable-buckets table)))
  229. (let lp ((chain (vector-ref buckets (hash key (vector-length buckets)))))
  230. (match chain
  231. (() default)
  232. (((other-key . val) . rest)
  233. (if (equiv? key other-key)
  234. val
  235. (lp rest)))))))
  236. (define (hashtable-resize! table k)
  237. (let ((old (hashtable-buckets table))
  238. (new (make-vector k '()))
  239. (hash (hashtable-hash table)))
  240. (set-hashtable-lower! table (if (eq? k %min-buckets) 0 (lower-bound k)))
  241. (set-hashtable-upper! table (upper-bound k))
  242. (set-hashtable-buckets! table new)
  243. ;; Rehash all key/value pairs.
  244. (do ((idx 0 (+ idx 1)))
  245. ((= idx (vector-length old)))
  246. (let lp ((chain (vector-ref old idx)))
  247. (match chain
  248. (() (values))
  249. (((and link (key . _)) . rest)
  250. (let ((new-idx (hash key k)))
  251. (vector-set! new new-idx (cons link (vector-ref new new-idx)))
  252. (lp rest))))))))
  253. (define (hashtable-resize-maybe! table)
  254. (let ((size (hashtable-size table))
  255. (lower (hashtable-lower table))
  256. (upper (hashtable-upper table)))
  257. (when (or (< size lower) (> size upper))
  258. (hashtable-resize! table (optimal-buckets size)))))
  259. (define (hashtable-set! table key val)
  260. "Associate @{val} with @var{key} in @var{table}, potentially
  261. overwriting any previous association with @var{key}."
  262. (let* ((hash (hashtable-hash table))
  263. (equiv? (hashtable-equiv table))
  264. (size (hashtable-size table))
  265. (buckets (hashtable-buckets table))
  266. (idx (hash key (vector-length buckets)))
  267. (chain (vector-ref buckets idx)))
  268. (let lp ((chain* chain))
  269. (match chain*
  270. (()
  271. (vector-set! buckets idx (cons (cons key val) chain))
  272. (set-hashtable-size! table (+ size 1))
  273. (hashtable-resize-maybe! table))
  274. (((and link (other-key . _)) . rest)
  275. (if (equiv? key other-key)
  276. (set-cdr! link val)
  277. (lp rest))))))
  278. (values))
  279. (define (hashtable-delete! table key)
  280. "Remove the association with @var{key} in @var{table}, if one exists."
  281. (let* ((hash (hashtable-hash table))
  282. (equiv? (hashtable-equiv table))
  283. (size (hashtable-size table))
  284. (buckets (hashtable-buckets table))
  285. (idx (hash key (vector-length buckets))))
  286. (vector-set! buckets idx
  287. (let lp ((chain (vector-ref buckets idx)))
  288. (match chain
  289. (() '())
  290. (((and link (other-key . _)) . rest)
  291. (if (equiv? key other-key)
  292. (begin
  293. (set-hashtable-size! table (- size 1))
  294. rest)
  295. (cons link (lp rest)))))))
  296. (hashtable-resize-maybe! table))
  297. (values))
  298. (define* (hashtable-clear! table)
  299. "Remove all items from @var{table}."
  300. (vector-fill! (hashtable-buckets table) '())
  301. (set-hashtable-size! table 0)
  302. (values))
  303. (define (hashtable-contains? table key)
  304. "Return #t if @var{key} has an associated value in @var{table}."
  305. (let ((hash (hashtable-hash table))
  306. (equiv? (hashtable-equiv table))
  307. (buckets (hashtable-buckets table)))
  308. (let lp ((chain (vector-ref buckets (hash key (vector-length buckets)))))
  309. (match chain
  310. (() #f)
  311. (((other-key . _) . rest)
  312. (or (equiv? key other-key) (lp rest)))))))
  313. (define* (hashtable-copy table)
  314. "Return a copy of @var{table}."
  315. (let* ((buckets (hashtable-buckets table))
  316. (k (vector-length buckets))
  317. (buckets* (make-vector k))
  318. (table* (%make-hashtable (hashtable-hash table)
  319. (hashtable-equiv table)
  320. (hashtable-size table)
  321. buckets*
  322. (hashtable-lower table)
  323. (hashtable-upper table))))
  324. (do ((i 0 (+ i 1)))
  325. ((= i k))
  326. (vector-set! buckets* i
  327. (map (lambda (link)
  328. (cons (car link) (cdr link)))
  329. (vector-ref buckets i))))
  330. table*))
  331. (define (hashtable-keys table)
  332. "Return a list of keys in @var{table}."
  333. (hashtable-fold (lambda (key val result)
  334. (cons key result))
  335. '() table))
  336. (define (hashtable-values table)
  337. "Return a list of values in @var{table}."
  338. (hashtable-fold (lambda (key val result)
  339. (cons val result))
  340. '() table))
  341. (define (hashtable-for-each proc table)
  342. "Apply @var{proc} to each key/value association in @var{table}.
  343. Each call is of the form @code{(proc key value)}."
  344. (let ((buckets (hashtable-buckets table)))
  345. (do ((idx 0 (+ idx 1)))
  346. ((= idx (vector-length buckets)))
  347. (let lp ((chain (vector-ref buckets idx)))
  348. (match chain
  349. (() (values))
  350. (((key . val) . rest)
  351. (proc key val)
  352. (lp rest)))))))
  353. (define (hashtable-fold proc init table)
  354. "Accumulate a result by applying @var{proc} with each key/value
  355. association in @var{table} and the result of the previous @var{proc}
  356. call. Each call is of the form @code{(proc key value prev)}. For the
  357. first call, @code{prev} is the initial value @var{init}."
  358. (let ((buckets (hashtable-buckets table)))
  359. (let bucket-lp ((idx 0) (result init))
  360. (if (< idx (vector-length buckets))
  361. (bucket-lp (+ idx 1)
  362. (let chain-lp ((chain (vector-ref buckets idx))
  363. (result result))
  364. (match chain
  365. (() result)
  366. (((key . val) . rest)
  367. (chain-lp rest (proc key val result))))))
  368. result))))
  369. ;; Weak key hashtables
  370. (define (make-weak-key-hashtable)
  371. (%inline-wasm
  372. '(func (result (ref eq))
  373. (struct.new $weak-table
  374. (i32.const 0)
  375. (call $make-weak-map)))))
  376. (define (weak-key-hashtable? obj)
  377. (%inline-wasm
  378. '(func (param $obj (ref eq)) (result (ref eq))
  379. (if (ref eq)
  380. (ref.test $weak-table (local.get $obj))
  381. (then (ref.i31 (i32.const 17)))
  382. (else (ref.i31 (i32.const 1)))))
  383. obj))
  384. (define* (weak-key-hashtable-ref table key #:optional default)
  385. (check-type table weak-key-hashtable? 'weak-key-hashtable-ref)
  386. (%inline-wasm
  387. '(func (param $table (ref eq)) (param $key (ref eq))
  388. (param $default (ref eq)) (result (ref eq))
  389. (call $weak-map-get
  390. (struct.get $weak-table $val
  391. (ref.cast $weak-table (local.get $table)))
  392. (local.get $key)
  393. (local.get $default)))
  394. table key default))
  395. (define (weak-key-hashtable-set! table key value)
  396. (check-type table weak-key-hashtable? 'weak-key-hashtable-set!)
  397. (%inline-wasm
  398. '(func (param $table (ref eq)) (param $key (ref eq)) (param $val (ref eq))
  399. (call $weak-map-set
  400. (struct.get $weak-table $val
  401. (ref.cast $weak-table (local.get $table)))
  402. (local.get $key)
  403. (local.get $val)))
  404. table key value))
  405. (define (weak-key-hashtable-delete! table key)
  406. (check-type table weak-key-hashtable? 'weak-key-hashtable-delete!)
  407. (%inline-wasm
  408. '(func (param $table (ref eq)) (param $key (ref eq))
  409. (call $weak-map-delete
  410. (struct.get $weak-table $val
  411. (ref.cast $weak-table (local.get $table)))
  412. (local.get $key))
  413. (drop))
  414. table key)))