hashtables.scm 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  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. make-eq-weak-key-hashtable
  43. make-eqv-weak-key-hashtable
  44. weak-key-hashtable?
  45. weak-key-hashtable-hash
  46. weak-key-hashtable-equiv
  47. weak-key-hashtable-size
  48. weak-key-hashtable-ref
  49. weak-key-hashtable-set!
  50. weak-key-hashtable-delete!
  51. weak-key-hashtable-clear!
  52. weak-key-hashtable-contains?
  53. weak-key-hashtable-copy
  54. weak-key-hashtable-keys
  55. weak-key-hashtable-values
  56. weak-key-hashtable-for-each
  57. weak-key-hashtable-fold
  58. make-weak-value-hashtable
  59. make-eq-weak-value-hashtable
  60. make-eqv-weak-value-hashtable
  61. weak-value-hashtable?
  62. weak-value-hashtable-hash
  63. weak-value-hashtable-equiv
  64. weak-value-hashtable-size
  65. weak-value-hashtable-ref
  66. weak-value-hashtable-set!
  67. weak-value-hashtable-delete!
  68. weak-value-hashtable-clear!
  69. weak-value-hashtable-contains?
  70. weak-value-hashtable-copy
  71. weak-value-hashtable-keys
  72. weak-value-hashtable-values
  73. weak-value-hashtable-for-each
  74. weak-value-hashtable-fold
  75. make-doubly-weak-hashtable
  76. make-eq-doubly-weak-hashtable
  77. make-eqv-doubly-weak-hashtable
  78. doubly-weak-hashtable?
  79. doubly-weak-hashtable-hash
  80. doubly-weak-hashtable-equiv
  81. doubly-weak-hashtable-size
  82. doubly-weak-hashtable-ref
  83. doubly-weak-hashtable-set!
  84. doubly-weak-hashtable-delete!
  85. doubly-weak-hashtable-clear!
  86. doubly-weak-hashtable-contains?
  87. doubly-weak-hashtable-copy
  88. doubly-weak-hashtable-keys
  89. doubly-weak-hashtable-values
  90. doubly-weak-hashtable-for-each
  91. doubly-weak-hashtable-fold)
  92. (import (only (hoot primitives)
  93. %struct-ref %struct-vtable
  94. guile:hashq guile:hashv guile:hash)
  95. (hoot bitwise)
  96. (hoot bitvectors)
  97. (hoot bytevectors)
  98. (hoot cond-expand)
  99. (hoot eq)
  100. (hoot equal)
  101. (hoot errors)
  102. (hoot ffi)
  103. (hoot finalization)
  104. (hoot inline-wasm)
  105. (hoot lists)
  106. (hoot match)
  107. (hoot pairs)
  108. (hoot procedures)
  109. (hoot not)
  110. (hoot numbers)
  111. (hoot records)
  112. (hoot strings)
  113. (hoot symbols)
  114. (hoot syntax)
  115. (hoot values)
  116. (hoot vectors)
  117. (hoot write)
  118. (hoot weak-refs))
  119. (cond-expand
  120. (guile-vm
  121. (define (hashq key size) (guile:hashq key size))
  122. (define (hashv key size) (guile:hashv key size))
  123. (define (hash key size) (guile:hash key size)))
  124. (hoot
  125. (define (string-hash str)
  126. (%inline-wasm
  127. '(func (param $str (ref eq)) (result i64)
  128. (i64.extend_i32_u
  129. (call $string-hash
  130. (struct.get $string $str
  131. (ref.cast $string (local.get $str))))))
  132. str))
  133. (define (%hashq key)
  134. (%inline-wasm
  135. '(func (param $key (ref eq)) (result i64)
  136. (i64.extend_i32_u
  137. (call $hashq (local.get $key))))
  138. key))
  139. (define (%hashv key)
  140. (if (number? key)
  141. ;; Use hashq for integers, otherwise convert to a string and
  142. ;; hash that.
  143. (if (integer? key)
  144. (if (exact? key)
  145. (%hashq key)
  146. (%hashq (exact key)))
  147. (string-hash (number->string key)))
  148. (%hashq key)))
  149. (define (%hash key)
  150. ;; Simple, non-commutative hash code combiner.
  151. (define (combine-hashes h1 h2)
  152. (logxor (ash h1 5) h2))
  153. ;; For hashing records:
  154. (define (assq-ref alist k)
  155. (and (pair? alist)
  156. (if (eq? (caar alist) k)
  157. (cdar alist)
  158. (assq-ref (cdr alist) k))))
  159. (define (record-nfields record)
  160. (%struct-ref (%struct-vtable record) 0))
  161. (define (record-properties record)
  162. (%struct-ref (%struct-vtable record) 4))
  163. (define (record-opaque? record)
  164. (assq-ref (record-properties record) 'opaque))
  165. ;; This recursive hashing algorithm with effort limit is inspired
  166. ;; by Chez Scheme.
  167. (define (hash key k)
  168. (let ((k (- k 1)))
  169. (cond
  170. ((<= k 0) ; out of hash juice :(
  171. (values (%hashv key) 0))
  172. ((string? key)
  173. (values (string-hash key) k))
  174. ((pair? key)
  175. (let ((k/2 (ash (+ k 1) -1)))
  176. (call-with-values (lambda () (hash (car key) k/2))
  177. (lambda (h1 k*)
  178. (call-with-values (lambda () (hash (cdr key) (+ (- k k/2) k*)))
  179. (lambda (h2 k)
  180. (values (combine-hashes h1 h2) k)))))))
  181. ((vector? key)
  182. (let ((seed #xbeadcafe))
  183. (let lp ((i 0) (h seed) (k k))
  184. (if (and (< i (vector-length key)) (> k 0))
  185. (let ((k/2 (ash (+ k 1) -1)))
  186. (call-with-values (lambda () (hash (vector-ref key i) k/2))
  187. (lambda (h* k*)
  188. (lp (+ i 1) (combine-hashes h h*) (+ (- k k/2) k*)))))
  189. (values h k)))))
  190. ((bytevector? key)
  191. (values (%inline-wasm
  192. '(func (param $bv (ref eq)) (result i64)
  193. (i64.extend_i32_u
  194. (call $hash-bytevector
  195. (ref.cast $bytevector (local.get $bv)))))
  196. key)
  197. k))
  198. ((bitvector? key)
  199. (values (%inline-wasm
  200. '(func (param $bv (ref eq)) (result i64)
  201. (i64.extend_i32_u
  202. (call $hash-bitvector
  203. (ref.cast $bitvector (local.get $bv)))))
  204. key)
  205. k))
  206. ((record? key)
  207. (if (record-opaque? key)
  208. (values (%hashq key) k)
  209. (let ((nfields (record-nfields key))
  210. (seed #xfacefeed))
  211. (let lp ((i 0) (h seed) (k k))
  212. (if (and (< i nfields) (> k 0))
  213. (let ((k/2 (ash k -1)))
  214. (call-with-values (lambda ()
  215. (hash (%struct-ref key i) k/2))
  216. (lambda (h* k*)
  217. (lp (+ i 1) (combine-hashes h h*) (+ (- k k/2) k*)))))
  218. (values h k))))))
  219. (else
  220. (values (%hashv key) k)))))
  221. (call-with-values (lambda () (hash key 64))
  222. (lambda (hash-code k)
  223. hash-code)))
  224. (define max-hash-size (1- (ash 1 32)))
  225. (define (hashq key size)
  226. (check-size size max-hash-size 'hashq)
  227. (modulo (%hashq key) size))
  228. (define (hashv key size)
  229. (check-size size max-hash-size 'hashv)
  230. (modulo (%hashv key) size))
  231. (define (hash key size)
  232. (check-size size max-hash-size 'hash)
  233. (modulo (%hash key) size))))
  234. ;; Numbers taken from https://planetmath.org/goodhashtableprimes
  235. (define %bucket-sizes
  236. #(53 97 193 389 769 1543 3079 6151 12289 24593 98317 196613 393241 786433
  237. 1572869 3145739 6291469 12582917 25165843 50331653 100663319
  238. 201326611 402653189 805306457 1610612741))
  239. (define %min-buckets 53)
  240. (define (lower-bound k)
  241. (quotient k 8))
  242. (define (upper-bound k)
  243. (quotient (* k 9) 10))
  244. (define (optimal-buckets k)
  245. (let ((last (- (vector-length %bucket-sizes) 1)))
  246. (let lp ((idx 0))
  247. (if (= idx last)
  248. (vector-ref %bucket-sizes last)
  249. (let ((size (vector-ref %bucket-sizes idx)))
  250. (if (> k (upper-bound size))
  251. (lp (1+ idx))
  252. size))))))
  253. (define-syntax define-hashtable-impl
  254. (syntax-rules ()
  255. ((_ <hashtable>
  256. %make-hashtable make-hashtable
  257. make-eq-hashtable make-eqv-hashtable
  258. hashtable?
  259. hashtable-hash
  260. hashtable-equiv
  261. hashtable-size set-hashtable-size!
  262. hashtable-buckets set-hashtable-buckets!
  263. hashtable-lower set-hashtable-lower!
  264. hashtable-upper set-hashtable-upper!
  265. hashtable-ref hashtable-contains?
  266. hashtable-set! hashtable-delete! hashtable-clear!
  267. hashtable-resize-maybe!
  268. hashtable-copy hashtable-keys hashtable-values
  269. hashtable-for-each hashtable-fold
  270. ((extra-field . extra-field-accessor) ...)
  271. constructor
  272. key-box key-unbox key-empty?
  273. val-box val-unbox val-empty?
  274. on-add on-delete on-replace)
  275. (begin
  276. (define-record-type <hashtable>
  277. ;; Strip the <> characters from the name when printing.
  278. #:printer (let* ((name (symbol->string '<hashtable>))
  279. (name* (substring name 1 (1- (string-length name)))))
  280. (lambda (table port)
  281. (display "#<" port)
  282. (display name* port)
  283. (display " size: " port)
  284. (display (hashtable-size table) port)
  285. (display ">" port)))
  286. (%make-hashtable hash equiv size buckets lower upper extra-field ...)
  287. hashtable?
  288. (hash hashtable-hash)
  289. (equiv hashtable-equiv)
  290. (size hashtable-size set-hashtable-size!)
  291. (buckets hashtable-buckets set-hashtable-buckets!)
  292. (lower hashtable-lower set-hashtable-lower!)
  293. (upper hashtable-upper set-hashtable-upper!)
  294. (extra-field . extra-field-accessor) ...)
  295. (define* (make-hashtable #:optional (hash hash) (equiv equal?))
  296. "Return a new, empty hashtable that uses the hash procedure @var{hash}
  297. and equivalence procedure @var{equiv}."
  298. (constructor hash equiv 0 (make-vector %min-buckets '())
  299. 0 (upper-bound %min-buckets)))
  300. (define (make-eq-hashtable)
  301. "Return a new, empty hashtable that uses @code{eq?} as the equivalence
  302. function and hashes keys accordingly."
  303. (make-hashtable hashq eq?))
  304. (define (make-eqv-hashtable)
  305. "Return a new, empty hashtable that uses @code{eqv?} as the equivalence
  306. function and hashes keys accordingly."
  307. (make-hashtable hashv eqv?))
  308. (define* (hashtable-ref table key #:optional default)
  309. "Return the value associated with @var{key} in @var{table}, or
  310. @var{default} if there is no such association."
  311. (let ((hash (hashtable-hash table))
  312. (equiv? (hashtable-equiv table))
  313. (buckets (hashtable-buckets table)))
  314. (let lp ((chain (vector-ref buckets (hash key (vector-length buckets)))))
  315. (match chain
  316. (() default)
  317. (((other-key . val) . rest)
  318. (cond
  319. ;; Skip dead pairs in weak tables.
  320. ((or (key-empty? other-key) (val-empty? val))
  321. (lp rest))
  322. ((equiv? key (key-unbox other-key))
  323. (val-unbox val))
  324. (else (lp rest))))))))
  325. (define (hashtable-resize! table k)
  326. (let ((old (hashtable-buckets table))
  327. (new (make-vector k '()))
  328. (hash (hashtable-hash table)))
  329. (set-hashtable-lower! table (if (eq? k %min-buckets) 0 (lower-bound k)))
  330. (set-hashtable-upper! table (upper-bound k))
  331. (set-hashtable-buckets! table new)
  332. ;; Rehash all key/value pairs.
  333. (do ((idx 0 (1+ idx)))
  334. ((= idx (vector-length old)))
  335. (let lp ((chain (vector-ref old idx)))
  336. (match chain
  337. (() (values))
  338. (((and link (key . val)) . rest)
  339. (cond
  340. ;; Prune dead pairs in weak tables.
  341. ((or (key-empty? key) (val-empty? val))
  342. (on-delete table key val)
  343. (lp rest))
  344. (else
  345. (let ((new-idx (hash (key-unbox key) k)))
  346. ;; For weak tables, we unregister the old
  347. ;; bucket index from the finalization registry
  348. ;; then register the new one.
  349. (on-delete table key val)
  350. (on-add table key val new-idx)
  351. (vector-set! new new-idx (cons link (vector-ref new new-idx)))
  352. (lp rest))))))))))
  353. (define (hashtable-resize-maybe! table)
  354. (let ((size (hashtable-size table))
  355. (lower (hashtable-lower table))
  356. (upper (hashtable-upper table)))
  357. (when (or (< size lower) (> size upper))
  358. (hashtable-resize! table (optimal-buckets size)))))
  359. (define (hashtable-set! table key val)
  360. "Associate @{val} with @var{key} in @var{table}, potentially
  361. overwriting any previous association with @var{key}."
  362. (let* ((hash (hashtable-hash table))
  363. (equiv? (hashtable-equiv table))
  364. (buckets (hashtable-buckets table))
  365. (idx (hash key (vector-length buckets))))
  366. (define (increment-size!)
  367. (set-hashtable-size! table (1+ (hashtable-size table))))
  368. (define (decrement-size!)
  369. (set-hashtable-size! table (1- (hashtable-size table))))
  370. (vector-set! buckets idx
  371. (let lp ((chain (vector-ref buckets idx)))
  372. (match chain
  373. (()
  374. (let ((key* (key-box key))
  375. (val* (val-box val)))
  376. (on-add table key* val* idx)
  377. (increment-size!)
  378. (list (cons key* val*))))
  379. (((and link (other-key . other-val)) . rest)
  380. (cond
  381. ;; Prune dead pairs in weak tables.
  382. ((or (key-empty? other-key) (val-empty? other-val))
  383. (decrement-size!)
  384. (on-delete table other-key other-val)
  385. (lp rest))
  386. ((equiv? key (key-unbox other-key))
  387. (let ((val* (val-box val)))
  388. (on-replace table other-key val* other-val idx)
  389. (set-cdr! link val*)
  390. chain))
  391. (else (cons link (lp rest))))))))
  392. (hashtable-resize-maybe! table)
  393. (values)))
  394. (define (hashtable-delete! table key)
  395. "Remove the association with @var{key} in @var{table}, if one exists."
  396. (let* ((hash (hashtable-hash table))
  397. (equiv? (hashtable-equiv table))
  398. (buckets (hashtable-buckets table))
  399. (idx (hash key (vector-length buckets))))
  400. (define (decrement-size!)
  401. (set-hashtable-size! table (1- (hashtable-size table))))
  402. (vector-set! buckets idx
  403. (let lp ((chain (vector-ref buckets idx)))
  404. (match chain
  405. (() '())
  406. (((and link (other-key . val)) . rest)
  407. (cond
  408. ;; Prune dead pairs in weak tables.
  409. ((or (key-empty? other-key) (val-empty? val))
  410. (on-delete table other-key val)
  411. (decrement-size!)
  412. (lp rest))
  413. ((equiv? key (key-unbox other-key))
  414. (on-delete table other-key val)
  415. (decrement-size!)
  416. rest)
  417. (else (cons link (lp rest))))))))
  418. (hashtable-resize-maybe! table)
  419. (values)))
  420. (define* (hashtable-clear! table)
  421. "Remove all items from @var{table}."
  422. (let ((buckets (hashtable-buckets table)))
  423. (do ((idx 0 (1+ idx)))
  424. ((= idx (vector-length buckets)))
  425. (let lp ((chain (vector-ref buckets idx)))
  426. (match chain
  427. (() (values))
  428. (((key . val) . rest)
  429. (on-delete table key val)
  430. (lp rest)))))
  431. (vector-fill! buckets '())
  432. (set-hashtable-size! table 0)
  433. (values)))
  434. (define (hashtable-contains? table key)
  435. "Return #t if @var{key} has an associated value in @var{table}."
  436. (let ((hash (hashtable-hash table))
  437. (equiv? (hashtable-equiv table))
  438. (buckets (hashtable-buckets table)))
  439. (let lp ((chain (vector-ref buckets (hash key (vector-length buckets)))))
  440. (match chain
  441. (() #f)
  442. (((other-key . val) . rest)
  443. (cond
  444. ;; Skip dead pairs in weak tables.
  445. ((or (key-empty? other-key) (val-empty? val))
  446. (lp rest))
  447. ((equiv? key (key-unbox other-key)) #t)
  448. (else (lp rest))))))))
  449. (define* (hashtable-copy table)
  450. "Return a copy of @var{table}."
  451. (let* ((buckets (hashtable-buckets table))
  452. (k (vector-length buckets))
  453. (buckets* (make-vector k))
  454. (table* (constructor (hashtable-hash table)
  455. (hashtable-equiv table)
  456. (hashtable-size table)
  457. buckets*
  458. (hashtable-lower table)
  459. (hashtable-upper table))))
  460. (define (decrement-size!)
  461. (set-hashtable-size! table* (1- (hashtable-size table*))))
  462. (do ((i 0 (1+ i)))
  463. ((= i k))
  464. (vector-set! buckets* i
  465. (let lp ((chain (vector-ref buckets i)))
  466. (match chain
  467. (() '())
  468. (((key . val) . rest)
  469. (cond
  470. ;; Skip dead pairs in weak tables.
  471. ((or (key-empty? key) (val-empty? val))
  472. (decrement-size!)
  473. (lp rest))
  474. (else
  475. (on-add table key val i)
  476. (cons (cons key val) (lp rest)))))))))
  477. (hashtable-resize-maybe! table*)
  478. table*))
  479. (define (hashtable-keys table)
  480. "Return a list of keys in @var{table}."
  481. (hashtable-fold (lambda (key val result)
  482. (cons key result))
  483. '() table))
  484. (define (hashtable-values table)
  485. "Return a list of values in @var{table}."
  486. (hashtable-fold (lambda (key val result)
  487. (cons val result))
  488. '() table))
  489. (define (hashtable-for-each proc table)
  490. "Apply @var{proc} to each key/value association in @var{table}.
  491. Each call is of the form @code{(proc key value)}."
  492. (let ((buckets (hashtable-buckets table)))
  493. (do ((idx 0 (1+ idx)))
  494. ((= idx (vector-length buckets)))
  495. (let lp ((chain (vector-ref buckets idx)))
  496. (match chain
  497. (() (values))
  498. (((key . val) . rest)
  499. ;; Skip dead pairs in weak tables.
  500. (unless (or (key-empty? key) (val-empty? val))
  501. (proc (key-unbox key) (val-unbox val)))
  502. (lp rest)))))))
  503. (define (hashtable-fold proc init table)
  504. "Accumulate a result by applying @var{proc} with each key/value
  505. association in @var{table} and the result of the previous @var{proc}
  506. call. Each call is of the form @code{(proc key value prev)}. For the
  507. first call, @code{prev} is the initial value @var{init}."
  508. (let ((buckets (hashtable-buckets table)))
  509. (let bucket-lp ((idx 0) (result init))
  510. (if (< idx (vector-length buckets))
  511. (bucket-lp (1+ idx)
  512. (let chain-lp ((chain (vector-ref buckets idx))
  513. (result result))
  514. (match chain
  515. (() result)
  516. (((key . val) . rest)
  517. ;; Skip dead pairs in weak tables.
  518. (if (or (key-empty? key) (val-empty? val))
  519. (chain-lp rest result)
  520. (let ((k (key-unbox key))
  521. (v (val-unbox val)))
  522. (chain-lp rest (proc k v result))))))))
  523. result))))))))
  524. (define-hashtable-impl <hashtable>
  525. %make-hashtable make-hashtable
  526. make-eq-hashtable make-eqv-hashtable
  527. hashtable?
  528. hashtable-hash
  529. hashtable-equiv
  530. hashtable-size set-hashtable-size!
  531. hashtable-buckets set-hashtable-buckets!
  532. hashtable-lower set-hashtable-lower!
  533. hashtable-upper set-hashtable-upper!
  534. hashtable-ref hashtable-contains?
  535. hashtable-set! hashtable-delete! hashtable-clear!
  536. hashtable-resize-maybe!
  537. hashtable-copy hashtable-keys hashtable-values
  538. hashtable-for-each hashtable-fold
  539. () ; no extra fields
  540. %make-hashtable
  541. ;; Keys and values are unboxed.
  542. (lambda (k) k) (lambda (k) k) (lambda (k) #f)
  543. (lambda (v) v) (lambda (v) v) (lambda (v) #f)
  544. ;; on-add, on-delete, on-replace are all no-ops.
  545. (lambda (table k v i) (values))
  546. (lambda (table k v) (values))
  547. (lambda (table k v v* i) (values)))
  548. ;; Weak refs cannot store immediates, so here are some wrappers that
  549. ;; will allow immediates to be stored in weak tables anyway.
  550. (define (immediate? x)
  551. (%inline-wasm
  552. '(func (param $x (ref eq)) (result (ref eq))
  553. (if (ref eq)
  554. (ref.test i31 (local.get $x))
  555. (then (ref.i31 (i32.const 17)))
  556. (else (ref.i31 (i32.const 1)))))
  557. x))
  558. (define (make-weak-ref* x)
  559. (if (immediate? x) x (make-weak-ref x)))
  560. (define (weak-ref-deref* x)
  561. (if (weak-ref? x) (weak-ref-deref x) x))
  562. (define (weak-ref-empty? x)
  563. (if (weak-ref? x) (weak-ref-null? (weak-ref-deref x)) #f))
  564. (define (maybe-register! registry ref held-value)
  565. (when (weak-ref? ref)
  566. (finalization-registry-register! registry (weak-ref-deref ref)
  567. held-value ref)))
  568. (define (maybe-unregister! registry ref)
  569. (when (weak-ref? ref)
  570. (finalization-registry-unregister! registry ref)))
  571. (define-syntax-rule (weak-hashtable-constructor %make-hashtable
  572. hashtable-buckets
  573. hashtable-size set-hashtable-size!
  574. hashtable-resize-maybe!
  575. dead?)
  576. (lambda (hash equiv size buckets min max)
  577. ;; When we are notified of a key/value being GC'd, we clean up
  578. ;; all empty weak refs in the associated bucket.
  579. (define (cleanup idx)
  580. (let ((buckets (hashtable-buckets table)))
  581. (define (decrement-size!)
  582. (set-hashtable-size! table (1- (hashtable-size table))))
  583. ;; Try as we might to unregister old values when we resize
  584. ;; the table, it is possible for a bucket index that is no
  585. ;; longer valid to sneak through. We just have to ignore
  586. ;; those.
  587. (when (< idx (vector-length buckets))
  588. (vector-set! buckets idx
  589. (let lp ((chain (vector-ref buckets idx)))
  590. (match chain
  591. (() '())
  592. (((and link (key . val)) . rest)
  593. (cond
  594. ((dead? table key val)
  595. (decrement-size!)
  596. (lp rest))
  597. (else
  598. (cons link (lp rest)))))))))
  599. (hashtable-resize-maybe! table)))
  600. (define registry (make-finalization-registry cleanup))
  601. (define table
  602. (%make-hashtable hash equiv size buckets min max registry))
  603. table))
  604. (define-hashtable-impl <weak-key-hashtable>
  605. %make-weak-key-hashtable make-weak-key-hashtable
  606. make-eq-weak-key-hashtable make-eqv-weak-key-hashtable
  607. weak-key-hashtable?
  608. weak-key-hashtable-hash
  609. weak-key-hashtable-equiv
  610. weak-key-hashtable-size set-weak-key-hashtable-size!
  611. weak-key-hashtable-buckets set-weak-key-hashtable-buckets!
  612. weak-key-hashtable-lower set-weak-key-hashtable-lower!
  613. weak-key-hashtable-upper set-weak-key-hashtable-upper!
  614. weak-key-hashtable-ref weak-key-hashtable-contains?
  615. weak-key-hashtable-set! weak-key-hashtable-delete! weak-key-hashtable-clear!
  616. weak-key-hashtable-resize-maybe!
  617. weak-key-hashtable-copy weak-key-hashtable-keys weak-key-hashtable-values
  618. weak-key-hashtable-for-each weak-key-hashtable-fold
  619. ;; Extra field:
  620. ((registry weak-key-hashtable-registry))
  621. ;; Constructor:
  622. (weak-hashtable-constructor %make-weak-key-hashtable
  623. weak-key-hashtable-buckets
  624. weak-key-hashtable-size set-weak-key-hashtable-size!
  625. weak-key-hashtable-resize-maybe!
  626. (lambda (table key val) (weak-ref-empty? key)))
  627. ;; Keys are boxed in weak refs.
  628. make-weak-ref* weak-ref-deref* weak-ref-empty?
  629. ;; Values are unboxed.
  630. (lambda (v) v) (lambda (v) v) (lambda (v) #f)
  631. ;; Add:
  632. (lambda (table key val idx)
  633. (maybe-register! (weak-key-hashtable-registry table) key idx))
  634. ;; Remove:
  635. (lambda (table key val)
  636. (maybe-unregister! (weak-key-hashtable-registry table) key))
  637. ;; Replace is a no-op.
  638. (lambda (table key val old-val idx) (values)))
  639. (define-hashtable-impl <weak-value-hashtable>
  640. %make-weak-value-hashtable make-weak-value-hashtable
  641. make-eq-weak-value-hashtable make-eqv-weak-value-hashtable
  642. weak-value-hashtable?
  643. weak-value-hashtable-hash
  644. weak-value-hashtable-equiv
  645. weak-value-hashtable-size set-weak-value-hashtable-size!
  646. weak-value-hashtable-buckets set-weak-value-hashtable-buckets!
  647. weak-value-hashtable-lower set-weak-value-hashtable-lower!
  648. weak-value-hashtable-upper set-weak-value-hashtable-upper!
  649. weak-value-hashtable-ref weak-value-hashtable-contains?
  650. weak-value-hashtable-set! weak-value-hashtable-delete! weak-value-hashtable-clear!
  651. weak-value-hashtable-resize-maybe!
  652. weak-value-hashtable-copy weak-value-hashtable-keys weak-value-hashtable-values
  653. weak-value-hashtable-for-each weak-value-hashtable-fold
  654. ;; Extra field:
  655. ((registry weak-value-hashtable-registry))
  656. ;; Constructor:
  657. (weak-hashtable-constructor %make-weak-value-hashtable
  658. weak-value-hashtable-buckets
  659. weak-value-hashtable-size set-weak-value-hashtable-size!
  660. weak-value-hashtable-resize-maybe!
  661. (lambda (table key val) (weak-ref-empty? val)))
  662. ;; Keys are unboxed.
  663. (lambda (v) v) (lambda (v) v) (lambda (v) #f)
  664. ;; Values are boxed in weak refs.
  665. make-weak-ref* weak-ref-deref* weak-ref-empty?
  666. ;; Add:
  667. (lambda (table key val idx)
  668. (maybe-register! (weak-value-hashtable-registry table) val idx))
  669. ;; Remove:
  670. (lambda (table key val)
  671. (maybe-unregister! (weak-value-hashtable-registry table) val))
  672. ;; Replace:
  673. (lambda (table key val old-val idx)
  674. (maybe-unregister! (weak-value-hashtable-registry table) old-val)
  675. (maybe-register! (weak-value-hashtable-registry table) val idx)))
  676. (define-hashtable-impl <doubly-weak-hashtable>
  677. %make-doubly-weak-hashtable make-doubly-weak-hashtable
  678. make-eq-doubly-weak-hashtable make-eqv-doubly-weak-hashtable
  679. doubly-weak-hashtable?
  680. doubly-weak-hashtable-hash
  681. doubly-weak-hashtable-equiv
  682. doubly-weak-hashtable-size set-doubly-weak-hashtable-size!
  683. doubly-weak-hashtable-buckets set-doubly-weak-hashtable-buckets!
  684. doubly-weak-hashtable-lower set-doubly-weak-hashtable-lower!
  685. doubly-weak-hashtable-upper set-doubly-weak-hashtable-upper!
  686. doubly-weak-hashtable-ref doubly-weak-hashtable-contains?
  687. doubly-weak-hashtable-set! doubly-weak-hashtable-delete! doubly-weak-hashtable-clear!
  688. doubly-weak-hashtable-resize-maybe!
  689. doubly-weak-hashtable-copy doubly-weak-hashtable-keys doubly-weak-hashtable-values
  690. doubly-weak-hashtable-for-each doubly-weak-hashtable-fold
  691. ;; Extra field:
  692. ((registry doubly-weak-hashtable-registry))
  693. ;; Constructor:
  694. (weak-hashtable-constructor %make-doubly-weak-hashtable
  695. doubly-weak-hashtable-buckets
  696. doubly-weak-hashtable-size set-doubly-weak-hashtable-size!
  697. doubly-weak-hashtable-resize-maybe!
  698. (lambda (table key val)
  699. (let ((registry (doubly-weak-hashtable-registry table)))
  700. (cond
  701. ((weak-ref-empty? key)
  702. (maybe-unregister! registry val)
  703. #t)
  704. ((weak-ref-empty? val)
  705. (maybe-unregister! registry key)
  706. #t)
  707. (else #f)))))
  708. ;; Both keys and values are boxed in weak refs.
  709. make-weak-ref* weak-ref-deref* weak-ref-empty?
  710. make-weak-ref* weak-ref-deref* weak-ref-empty?
  711. ;; Add:
  712. (lambda (table key val idx)
  713. (maybe-register! (doubly-weak-hashtable-registry table) key idx)
  714. (maybe-register! (doubly-weak-hashtable-registry table) val idx))
  715. ;; Remove:
  716. (lambda (table key val)
  717. (maybe-unregister! (doubly-weak-hashtable-registry table) key)
  718. (maybe-unregister! (doubly-weak-hashtable-registry table) val))
  719. ;; Replace:
  720. (lambda (table key val old-val idx)
  721. (maybe-unregister! (doubly-weak-hashtable-registry table) old-val)
  722. (maybe-register! (doubly-weak-hashtable-registry table) val idx))))