Source file atomic.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
open! Base
open Basement

module Compare_failed_or_set_here = struct
  type t = Basement.Compare_failed_or_set_here.t =
    | Compare_failed
    | Set_here
  [@@deriving sexp_of ~stackify]
end

type ('a : value_or_null) t = 'a Basement.Portable_atomic.t

let make = Basement.Portable_atomic.make

external get
  : ('a : value_or_null).
  ('a t[@local_opt]) -> 'a @ contended portable
  @@ portable
  = "%atomic_load"

external exchange
  : ('a : value_or_null).
  ('a t[@local_opt]) -> 'a @ contended portable -> 'a @ contended portable
  @@ portable
  = "%atomic_exchange"

external set
  : ('a : value_or_null).
  ('a t[@local_opt]) -> 'a @ contended portable -> unit
  @@ portable
  = "%atomic_set"

external compare_and_set
  : ('a : value_or_null).
  ('a t[@local_opt])
  -> if_phys_equal_to:'a @ contended
  -> replace_with:'a @ contended portable
  -> Compare_failed_or_set_here.t
  @@ portable
  = "%atomic_cas"

external compare_exchange
  : ('a : value_or_null).
  ('a t[@local_opt])
  -> if_phys_equal_to:'a @ contended
  -> replace_with:'a @ contended portable
  -> 'a @ contended portable
  @@ portable
  = "%atomic_compare_exchange"

let[@inline] get_and_update t ~pure_f =
  let[@inline] rec aux backoff =
    let old = get t in
    let new_ = pure_f old in
    match compare_and_set t ~if_phys_equal_to:old ~replace_with:new_ with
    | Set_here -> old
    | Compare_failed -> aux (Backoff.once backoff)
  in
  aux Backoff.default [@nontail]
;;

let[@inline] update (type a : value_or_null) (t : a t) ~pure_f =
  Basement.Stdlib_shim.ignore_contended (get_and_update t ~pure_f : a)
;;

external fetch_and_add
  :  (int t[@local_opt])
  -> int
  -> int
  @@ portable
  = "%atomic_fetch_add"

external add : (int t[@local_opt]) -> int -> unit @@ portable = "%atomic_add"
external sub : (int t[@local_opt]) -> int -> unit @@ portable = "%atomic_sub"
external logand : (int t[@local_opt]) -> int -> unit @@ portable = "%atomic_land"
external logor : (int t[@local_opt]) -> int -> unit @@ portable = "%atomic_lor"
external logxor : (int t[@local_opt]) -> int -> unit @@ portable = "%atomic_lxor"

let incr r = add r 1
let decr r = sub r 1
let sexp_of_t sexp_of_a t = sexp_of_a (get t)
let t_of_sexp a_of_sexp sexp = make (a_of_sexp sexp)

module List = struct
  type nonrec ('a : value_or_null) t = 'a list t

  let push t x = update t ~pure_f:(fun xs -> x :: xs)

  let pop t =
    match
      get_and_update t ~pure_f:(function
        | [] -> []
        | _ :: xs -> xs)
    with
    | [] -> Null
    | x :: _ -> This x
  ;;

  let pop_opt t =
    match
      get_and_update t ~pure_f:(function
        | [] -> []
        | _ :: xs -> xs)
    with
    | [] -> None
    | x :: _ -> Some x
  ;;

  let pop_exn t =
    match
      get_and_update t ~pure_f:(function
        | [] -> []
        | _ :: xs -> xs)
    with
    | [] -> failwith "[Atomic.List.pop_exn]: Empty list"
    | x :: _ -> x
  ;;
end

module Loc = struct
  type ('a : value_or_null mod contended portable) t = 'a Stdlib.Atomic.Loc.t

  external get
    : ('a : value_or_null mod contended portable).
    ('a t[@local_opt]) @ contended -> 'a @ contended
    @@ portable
    = "%atomic_load_loc"

  external set
    : ('a : value_or_null mod contended portable).
    ('a t[@local_opt]) @ contended -> 'a @ contended -> unit
    @@ portable
    = "%atomic_set_loc"

  external exchange
    : ('a : value_or_null mod contended portable).
    ('a t[@local_opt]) @ contended -> 'a @ contended -> 'a @ contended
    @@ portable
    = "%atomic_exchange_loc"

  external compare_and_set
    : ('a : value_or_null mod contended portable).
    ('a t[@local_opt]) @ contended
    -> if_phys_equal_to:'a @ contended
    -> replace_with:'a @ contended
    -> Compare_failed_or_set_here.t
    @@ portable
    = "%atomic_cas_loc"

  external compare_exchange
    : ('a : value_or_null mod contended portable).
    ('a t[@local_opt]) @ contended
    -> if_phys_equal_to:'a @ contended
    -> replace_with:'a @ contended
    -> 'a @ contended
    @@ portable
    = "%atomic_compare_exchange_loc"

  let get_and_update t ~pure_f =
    let rec aux backoff =
      let old = get t in
      let new_ = pure_f old in
      match compare_and_set t ~if_phys_equal_to:old ~replace_with:new_ with
      | Set_here -> old
      | Compare_failed -> aux (Backoff.once backoff)
        [@@inline]
    in
    aux Backoff.default [@nontail]
  [@@inline]
  ;;

  let update t ~pure_f = Basement.Stdlib_shim.ignore_contended (get_and_update t ~pure_f)
  [@@inline]
  ;;

  external fetch_and_add
    :  (int t[@local_opt]) @ contended
    -> int
    -> int
    @@ portable
    = "%atomic_fetch_add_loc"

  external add
    :  (int t[@local_opt]) @ contended
    -> int
    -> unit
    @@ portable
    = "%atomic_add_loc"

  external sub
    :  (int t[@local_opt]) @ contended
    -> int
    -> unit
    @@ portable
    = "%atomic_sub_loc"

  external logand
    :  (int t[@local_opt]) @ contended
    -> int
    -> unit
    @@ portable
    = "%atomic_land_loc"

  external logor
    :  (int t[@local_opt]) @ contended
    -> int
    -> unit
    @@ portable
    = "%atomic_lor_loc"

  external logxor
    :  (int t[@local_opt]) @ contended
    -> int
    -> unit
    @@ portable
    = "%atomic_lxor_loc"

  let incr r = add r 1
  let decr r = sub r 1
  let sexp_of_t sexp_of_a t = sexp_of_a (get t)
end

let contents (t : _ t) = [%atomic.loc t.contended.Stdlib.Atomic.contents]

module Expert = struct
  (* This is subject to CSE. *)
  external fenceless_get_cse
    : ('a : value_or_null).
    ('a t[@local_opt]) -> 'a @ contended portable
    @@ portable
    = "%field0"

  let[@inline] fenceless_get t =
    (* We use [Sys.opaque_identity] to prevent CSE. *)
    fenceless_get_cse (Sys.opaque_identity t)
  ;;
end