···11+# Release notes
22+33+All notable changes to this project will be documented in this file.
44+55+## 1.0.0
66+77+- Use `inline never` for atomicity on old compilers (@polytypic)
88+- Use bit mixing (@polytypic)
99+- Change `find` to use `raise_notrace` for performance (@polytypic)
1010+- Change license to ISC from 0BSD (@tarides)
1111+1212+## 0.1.0
1313+1414+- Initial version of lock-free thread-safe integer keyed hash table (@polytypic)
+11
vendor/opam/thread-table/HACKING.md
···11+### Formatting
22+33+This project uses [ocamlformat](https://github.com/ocaml-ppx/ocamlformat) (for
44+OCaml) and [prettier](https://prettier.io/) (for Markdown).
55+66+### To make a new release
77+88+1. Update [CHANGES.md](CHANGES.md).
99+2. Run `dune-release tag VERSION` to create a tag for the new `VERSION`.
1010+3. Run `dune-release` to publish the new `VERSION`.
1111+4. Run `./update-gh-pages-for-tag VERSION` to update the online documentation.
···11+[API reference](https://ocaml-multicore.github.io/thread-table/doc/thread-table/Thread_table/index.html)
22+33+# **thread-table** — A lock-free thread-safe integer keyed hash table
44+55+A minimalist lock-free thread-safe integer keyed hash table with zero
66+synchronization overhead on lookups designed for associating thread specific
77+state with threads within a domain.
88+99+⚠️ This is not _parallelism-safe_ — only _thread-safe_ within a single
1010+domain.
+15
vendor/opam/thread-table/dune-project
···11+(lang dune 3.3)
22+(name thread-table)
33+(generate_opam_files true)
44+(source (github ocaml-multicore/thread-table))
55+(authors "Vesa Karvonen <vesa.a.j.k@gmail.com>")
66+(maintainers "Vesa Karvonen <vesa.a.j.k@gmail.com>")
77+(homepage "https://github.com/ocaml-multicore/thread-table")
88+(license "ISC")
99+(implicit_transitive_deps false)
1010+(package (name thread-table)
1111+ (synopsis "A lock-free thread-safe integer keyed hash table")
1212+ (description "A minimalist lock-free thread-safe integer keyed hash table with zero synchronization overhead on lookups designed for associating thread specific state with threads within a domain.")
1313+ (depends
1414+ (ocaml (>= 4.08))
1515+ (alcotest (and (>= 1.7.0) :with-test))))
···11+(* Mixing function proposed by "TheIronBorn" in a Github issue
22+33+ https://github.com/skeeto/hash-prospector/issues/19
44+55+ in the repository of Hash Prospector by Chris Wellons.
66+77+ Note that the mixing function was originally designed for 32-bit unsigned
88+ integers. *)
99+1010+let[@inline] int x =
1111+ let x = x lxor (x lsr 16) in
1212+ let x = x * 0x21f0aaad in
1313+ let x = x lxor (x lsr 15) in
1414+ let x = x * 0x735a2d97 in
1515+ x lxor (x lsr 15)
+13
vendor/opam/thread-table/src/mix.64.ml
···11+(* Mixing function proposed by Jon Maiga:
22+33+ https://jonkagstrom.com/mx3/mx3_rev2.html
44+55+ Note that the mixing function was originally designed for 64-bit unsigned
66+ integers. *)
77+88+let[@inline] int x =
99+ let x = x lxor (x lsr 32) in
1010+ let x = x * 0xe9846af9b1a615d in
1111+ let x = x lxor (x lsr 32) in
1212+ let x = x * 0xe9846af9b1a615d in
1313+ x lxor (x lsr 28)
+164
vendor/opam/thread-table/src/thread_table.ml
···11+type 'v bucket = Nil | Cons of int * 'v * 'v bucket
22+33+type 'v t = {
44+ mutable rehash : int;
55+ mutable buckets : 'v bucket array;
66+ mutable length : int;
77+}
88+99+let[@tail_mod_cons] rec remove_first removed k' = function
1010+ | Nil -> Nil
1111+ | Cons (k, v, kvs) ->
1212+ if k == k' then begin
1313+ removed := true;
1414+ kvs
1515+ end
1616+ else Cons (k, v, remove_first removed k' kvs)
1717+1818+let[@inline] remove_first removed k' = function
1919+ | Nil -> Nil
2020+ | Cons (k, v, kvs) ->
2121+ if k == k' then begin
2222+ removed := true;
2323+ kvs
2424+ end
2525+ else Cons (k, v, remove_first removed k' kvs)
2626+2727+let rec find k' = function
2828+ | Nil -> raise_notrace Not_found
2929+ | Cons (k, v, kvs) -> if k == k' then v else find k' kvs
3030+3131+let[@tail_mod_cons] rec filter bit chk = function
3232+ | Nil -> Nil
3333+ | Cons (k, v, kvs) ->
3434+ if Mix.int k land bit = chk then Cons (k, v, filter bit chk kvs)
3535+ else filter bit chk kvs
3636+3737+let[@inline] filter bit chk = function
3838+ | Nil -> Nil
3939+ | Cons (k, _, Nil) as kvs -> if Mix.int k land bit = chk then kvs else Nil
4040+ | Cons (k, v, kvs) ->
4141+ if Mix.int k land bit = chk then Cons (k, v, filter bit chk kvs)
4242+ else filter bit chk kvs
4343+4444+let[@tail_mod_cons] rec append kvs tail =
4545+ match kvs with Nil -> tail | Cons (k, v, kvs) -> Cons (k, v, append kvs tail)
4646+4747+let[@inline] append kvs tail =
4848+ match kvs with Nil -> tail | Cons (k, v, kvs) -> Cons (k, v, append kvs tail)
4949+5050+let min_buckets = 4
5151+and max_buckets_div_2 = (Sys.max_array_length + 1) asr 1
5252+5353+let create () = { rehash = 0; buckets = Array.make min_buckets Nil; length = 0 }
5454+let length t = t.length
5555+5656+let find t k' =
5757+ let h = Mix.int k' in
5858+ let buckets = t.buckets in
5959+ let n = Array.length buckets in
6060+ let i = h land (n - 1) in
6161+ find k' (Array.unsafe_get buckets i)
6262+6363+(* Below we use [@poll error] and [@inline never] to ensure that there are no
6464+ safe-points where thread switches might occur during critical sections. *)
6565+6666+let[@poll error] [@inline never] update_buckets_atomically t old_buckets
6767+ new_buckets =
6868+ t.buckets == old_buckets
6969+ && begin
7070+ t.buckets <- new_buckets;
7171+ t.rehash <- 0;
7272+ true
7373+ end
7474+7575+let rec maybe_rehash t =
7676+ let old_buckets = t.buckets in
7777+ let new_n = t.rehash in
7878+ if new_n <> 0 then
7979+ let old_n = Array.length old_buckets in
8080+ let new_buckets = Array.make new_n Nil in
8181+ if old_n * 2 = new_n then
8282+ let new_bit = new_n lsr 1 in
8383+ let rec loop i =
8484+ if t.buckets == old_buckets then
8585+ if old_n <= i then begin
8686+ if not (update_buckets_atomically t old_buckets new_buckets) then
8787+ maybe_rehash t
8888+ end
8989+ else begin
9090+ let kvs = Array.unsafe_get old_buckets i in
9191+ Array.unsafe_set new_buckets i (filter new_bit 0 kvs);
9292+ Array.unsafe_set new_buckets (i lor new_bit)
9393+ (filter new_bit new_bit kvs);
9494+ loop (i + 1)
9595+ end
9696+ else maybe_rehash t
9797+ in
9898+ loop 0
9999+ else if old_n = new_n * 2 then
100100+ let old_bit = old_n lsr 1 in
101101+ let rec loop i =
102102+ if t.buckets == old_buckets then
103103+ if new_n <= i then begin
104104+ if not (update_buckets_atomically t old_buckets new_buckets) then
105105+ maybe_rehash t
106106+ end
107107+ else begin
108108+ Array.unsafe_set new_buckets i
109109+ (append
110110+ (Array.unsafe_get old_buckets (i + old_bit))
111111+ (Array.unsafe_get old_buckets i));
112112+ loop (i + 1)
113113+ end
114114+ else maybe_rehash t
115115+ in
116116+ loop 0
117117+ else maybe_rehash t
118118+119119+let[@inline] maybe_rehash t = if t.rehash <> 0 then maybe_rehash t
120120+121121+let[@poll error] [@inline never] add_atomically t buckets n i before after =
122122+ t.rehash = 0 && buckets == t.buckets
123123+ && before == Array.unsafe_get buckets i
124124+ && begin
125125+ Array.unsafe_set buckets i after;
126126+ let length = t.length + 1 in
127127+ t.length <- length;
128128+ if n < length && n < max_buckets_div_2 then t.rehash <- n * 2;
129129+ true
130130+ end
131131+132132+let rec add t k' v' =
133133+ let h = Mix.int k' in
134134+ maybe_rehash t;
135135+ let buckets = t.buckets in
136136+ let n = Array.length buckets in
137137+ let i = h land (n - 1) in
138138+ let before = Array.unsafe_get buckets i in
139139+ let after = Cons (k', v', before) in
140140+ if not (add_atomically t buckets n i before after) then add t k' v'
141141+142142+let[@poll error] [@inline never] remove_atomically t buckets n i before after
143143+ removed =
144144+ t.rehash = 0 && buckets == t.buckets
145145+ && before == Array.unsafe_get buckets i
146146+ && ((not !removed)
147147+ || begin
148148+ Array.unsafe_set buckets i after;
149149+ let length = t.length - 1 in
150150+ t.length <- length;
151151+ if length * 4 < n && min_buckets < n then t.rehash <- n asr 1;
152152+ true
153153+ end)
154154+155155+let rec remove t k' =
156156+ let h = Mix.int k' in
157157+ let removed = ref false in
158158+ maybe_rehash t;
159159+ let buckets = t.buckets in
160160+ let n = Array.length buckets in
161161+ let i = h land (n - 1) in
162162+ let before = Array.unsafe_get buckets i in
163163+ let after = remove_first removed k' before in
164164+ if not (remove_atomically t buckets n i before after removed) then remove t k'
+35
vendor/opam/thread-table/src/thread_table.mli
···11+(** A lock-free thread-safe [int]eger keyed hash table.
22+33+ This is designed for associating thread specific state with threads within a
44+ domain.
55+66+ ⚠️ This is not {i parallelism-safe} — only {i thread-safe} within a single
77+ domain. *)
88+99+type 'v t
1010+(** A lock-free thread-safe [int]eger keyed hash table. *)
1111+1212+val create : unit -> 'v t
1313+(** [create ()] returns a new lock-free thread-safe [int]eger keyed hash table.
1414+ The hash table is automatically resized. *)
1515+1616+val length : 'v t -> int
1717+(** [length t] returns the number of {i bindings} in the hash table [t].
1818+1919+ ⚠️ The returned value may be greater than the number of {i distinct keys} in
2020+ the hash table. *)
2121+2222+val find : 'v t -> int -> 'v
2323+(** [find t k] returns the current binding of [k] in hash table [t], or raises
2424+ [Not_found] if no such binding exists.
2525+2626+ ⚠️ This may use [raise_notrace] for performance reasons. *)
2727+2828+val add : 'v t -> int -> 'v -> unit
2929+(** [add t k v] adds a binding of key [k] to value [v] to the hash table
3030+ shadowing the previous binding of the key [k], if any. *)
3131+3232+val remove : 'v t -> int -> unit
3333+(** [remove t k] removes the most recent existing binding of key [k], if any,
3434+ from the hash table [t] thereby revealing the earlier binding of [k], if
3535+ any. *)
···11+let basics () =
22+ let n_threads = 10 in
33+ let n_items_per_thread = 100_000 in
44+ let t = Thread_table.create () in
55+66+ let threads =
77+ Array.init n_threads @@ fun i ->
88+ ()
99+ |> Thread.create @@ fun () ->
1010+ for i = i * n_items_per_thread to ((i + 1) * n_items_per_thread) - 1 do
1111+ Thread_table.add t i (-i)
1212+ done
1313+ in
1414+ Array.iter Thread.join threads;
1515+1616+ Alcotest.check' ~msg:"length" Alcotest.int
1717+ ~expected:(n_threads * n_items_per_thread)
1818+ ~actual:(Thread_table.length t);
1919+2020+ for i = 0 to (n_threads * n_items_per_thread) - 1 do
2121+ if Thread_table.find t i <> -i then raise Not_found
2222+ done;
2323+2424+ let threads =
2525+ Array.init n_threads @@ fun i ->
2626+ ()
2727+ |> Thread.create @@ fun () ->
2828+ for
2929+ i = ((i + 1) * n_items_per_thread) - 1 downto i * n_items_per_thread
3030+ do
3131+ Thread_table.remove t i
3232+ done
3333+ in
3434+ Array.iter Thread.join threads;
3535+3636+ for i = 0 to (n_threads * n_items_per_thread) - 1 do
3737+ match Thread_table.find t i with
3838+ | _ -> raise Not_found
3939+ | exception Not_found -> ()
4040+ done;
4141+4242+ Alcotest.check' ~msg:"length" Alcotest.int ~expected:0
4343+ ~actual:(Thread_table.length t);
4444+4545+ ()
4646+4747+let () =
4848+ Alcotest.run "Thread_table"
4949+ [ ("basics", [ Alcotest.test_case "" `Quick basics ]) ]
+31
vendor/opam/thread-table/thread-table.opam
···11+# This file is generated by dune, edit dune-project instead
22+opam-version: "2.0"
33+synopsis: "A lock-free thread-safe integer keyed hash table"
44+description:
55+ "A minimalist lock-free thread-safe integer keyed hash table with zero synchronization overhead on lookups designed for associating thread specific state with threads within a domain."
66+maintainer: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
77+authors: ["Vesa Karvonen <vesa.a.j.k@gmail.com>"]
88+license: "ISC"
99+homepage: "https://github.com/ocaml-multicore/thread-table"
1010+bug-reports: "https://github.com/ocaml-multicore/thread-table/issues"
1111+depends: [
1212+ "dune" {>= "3.3"}
1313+ "ocaml" {>= "4.08"}
1414+ "alcotest" {>= "1.7.0" & with-test}
1515+ "odoc" {with-doc}
1616+]
1717+build: [
1818+ ["dune" "subst"] {dev}
1919+ [
2020+ "dune"
2121+ "build"
2222+ "-p"
2323+ name
2424+ "-j"
2525+ jobs
2626+ "@install"
2727+ "@runtest" {with-test}
2828+ "@doc" {with-doc}
2929+ ]
3030+]
3131+dev-repo: "git+https://github.com/ocaml-multicore/thread-table.git"
+75
vendor/opam/thread-table/update-gh-pages-for-tag
···11+#!/bin/bash
22+33+set -xeuo pipefail
44+55+TMP=tmp
66+NAME=thread-table
77+MAIN=doc
88+GIT="git@github.com:ocaml-multicore/$NAME.git"
99+DOC="_build/default/_doc/_html"
1010+GH_PAGES=gh-pages
1111+1212+TAG="$1"
1313+1414+if ! [ -e $NAME.opam ] || [ $# -ne 1 ] || \
1515+ { [ "$TAG" != main ] && ! [ "$(git tag -l "$TAG")" ]; }; then
1616+ CMD="${0##*/}"
1717+ cat << EOF
1818+Usage: $CMD tag-name-or-main
1919+2020+This script
2121+- clones the repository into a temporary directory ($TMP/$NAME),
2222+- builds the documentation for the specified tag or main,
2323+- updates $GH_PAGES branch with the documentation in directory for the tag,
2424+- prompts whether to also update the main documentation in $MAIN directory, and
2525+- prompts whether to push changes to $GH_PAGES.
2626+2727+EOF
2828+ exit 1
2929+fi
3030+3131+mkdir $TMP
3232+cd $TMP
3333+3434+git clone $GIT
3535+cd $NAME
3636+3737+git checkout "$TAG"
3838+dune build @doc --root=.
3939+4040+git checkout $GH_PAGES
4141+if [ "$TAG" != main ]; then
4242+ echo "Updating the $TAG doc."
4343+ if [ -e "$TAG" ]; then
4444+ git rm -rf "$TAG"
4545+ fi
4646+ cp -r $DOC "$TAG"
4747+ git add "$TAG"
4848+fi
4949+5050+read -p "Update the main doc? (y/N) " -n 1 -r
5151+echo
5252+if [[ $REPLY =~ ^[Yy]$ ]]; then
5353+ if [ -e $MAIN ]; then
5454+ git rm -rf $MAIN
5555+ fi
5656+ cp -r $DOC $MAIN
5757+ git add $MAIN
5858+else
5959+ echo "Skipped main doc update."
6060+fi
6161+6262+git commit -m "Update $NAME doc for $TAG"
6363+6464+read -p "Push changes to $GH_PAGES? (y/N) " -n 1 -r
6565+echo
6666+if ! [[ $REPLY =~ ^[Yy]$ ]]; then
6767+ echo "Leaving $TMP for you to examine."
6868+ exit 1
6969+fi
7070+7171+git push
7272+7373+cd ..
7474+cd ..
7575+rm -rf $TMP