Skip to content

Commit 20afe54

Browse files
committed
Revisit uses of fenceless_get
1 parent a2aefc5 commit 20afe54

File tree

7 files changed

+140
-47
lines changed

7 files changed

+140
-47
lines changed

src/kcas/kcas.ml

Lines changed: 80 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,14 @@ let rec verify casn = function
148148
| NIL -> `After
149149
| CASN { loc; state; lt; gt; _ } -> (
150150
if lt == NIL then
151+
(* Fenceless is safe as [finish] has a fence after. *)
151152
if is_cmp casn state && fenceless_get (as_atomic loc) != state then
152153
`Before
153154
else verify casn gt
154155
else
155156
match verify casn lt with
156157
| `After ->
158+
(* Fenceless is safe as [finish] has a fence after. *)
157159
if is_cmp casn state && fenceless_get (as_atomic loc) != state then
158160
`Before
159161
else verify casn gt
@@ -162,7 +164,9 @@ let rec verify casn = function
162164
let finish casn (`Undetermined cass as undetermined) (status : determined) =
163165
if Atomic.compare_and_set casn (undetermined :> status) (status :> status)
164166
then release casn cass status
165-
else fenceless_get casn == `After
167+
else
168+
(* Fenceless is safe as we have a fence above. *)
169+
fenceless_get casn == `After
166170

167171
let a_cmp = 1
168172
let a_cas = 2
@@ -190,6 +194,7 @@ let rec determine casn status = function
190194
&& (current.casn == casn_before || not (is_after current.casn))
191195
in
192196
if (not (is_cmp casn state)) && matches_expected () then
197+
(* Fenceless is safe as there are fences before and after. *)
193198
match fenceless_get casn with
194199
| `Undetermined _ ->
195200
(* We now know that the operation wasn't finished when we read
@@ -217,6 +222,7 @@ let rec determine casn status = function
217222
else -1
218223

219224
and is_after casn =
225+
(* Fenceless at most gives old [Undetermined] and causes extra work. *)
220226
match fenceless_get casn with
221227
| `Undetermined cass as undetermined -> (
222228
match determine casn 0 cass with
@@ -225,14 +231,17 @@ and is_after casn =
225231
(if a_cmp_followed_by_a_cas < status then verify casn cass
226232
else if 0 <= status then `After
227233
else `Before)
228-
| exception Exit -> fenceless_get casn == `After)
234+
| exception Exit ->
235+
(* Fenceless is safe as there was a fence before. *)
236+
fenceless_get casn == `After)
229237
| `After -> true
230238
| `Before -> false
231239

232240
let determine_for_owner casn cass =
233-
let undetermined = `Undetermined cass in
234241
(* The end result is a cyclic data structure, which is why we cannot
235242
initialize the [casn] atomic directly. *)
243+
let undetermined = `Undetermined cass in
244+
(* Fenceless is safe as [casn] is private at this point. *)
236245
fenceless_set casn undetermined;
237246
match determine casn 0 cass with
238247
| status ->
@@ -247,7 +256,9 @@ let determine_for_owner casn cass =
247256
else
248257
a_cmp = status
249258
|| finish casn undetermined (if 0 <= status then `After else `Before)
250-
| exception Exit -> fenceless_get casn == `After
259+
| exception Exit ->
260+
(* Fenceless is safe as there was a fence before. *)
261+
fenceless_get casn == `After
251262
[@@inline]
252263

253264
let impossible () = failwith "impossible" [@@inline never]
@@ -307,6 +318,7 @@ module Retry = struct
307318
end
308319

309320
let add_awaiter loc before awaiter =
321+
(* Fenceless is safe as we have fence after. *)
310322
let state_old = fenceless_get (as_atomic loc) in
311323
let state_new =
312324
let awaiters = awaiter :: state_old.awaiters in
@@ -322,6 +334,7 @@ let[@tail_mod_cons] rec remove_first x' removed = function
322334
| x :: xs -> if x == x' then xs else x :: remove_first x' removed xs
323335

324336
let rec remove_awaiter loc before awaiter =
337+
(* Fenceless is safe as we have fence after. *)
325338
let state_old = fenceless_get (as_atomic loc) in
326339
if before == eval state_old then
327340
let removed = ref true in
@@ -340,40 +353,56 @@ let block loc before =
340353
raise cancellation_exn)
341354

342355
let rec update_no_alloc backoff loc state f =
343-
let state' = fenceless_get (as_atomic loc) in
344-
let before = eval state' in
356+
(* Fenceless is safe as we have had a fence before if needed and there is a fence after. *)
357+
let state_old = fenceless_get (as_atomic loc) in
358+
let before = eval state_old in
345359
match f before with
346360
| after ->
347361
state.after <- after;
348362
if before == after then before
349-
else if Atomic.compare_and_set (as_atomic loc) state' state then (
363+
else if Atomic.compare_and_set (as_atomic loc) state_old state then (
350364
state.before <- after;
351-
resume_awaiters before state'.awaiters)
365+
resume_awaiters before state_old.awaiters)
352366
else update_no_alloc (Backoff.once backoff) loc state f
353367
| exception Retry.Later ->
354368
block loc before;
355369
update_no_alloc backoff loc state f
356370

371+
let update_with_state backoff loc f state_old =
372+
let before = eval state_old in
373+
match f before with
374+
| after ->
375+
if before == after then before
376+
else
377+
let state = new_state after in
378+
if Atomic.compare_and_set (as_atomic loc) state_old state then
379+
resume_awaiters before state_old.awaiters
380+
else update_no_alloc (Backoff.once backoff) loc state f
381+
| exception Retry.Later ->
382+
let state = new_state before in
383+
block loc before;
384+
update_no_alloc backoff loc state f
385+
357386
let rec exchange_no_alloc backoff loc state =
358-
let state' = fenceless_get (as_atomic loc) in
359-
let before = eval state' in
387+
let state_old = Atomic.get (as_atomic loc) in
388+
let before = eval state_old in
360389
if before == state.after then before
361-
else if Atomic.compare_and_set (as_atomic loc) state' state then
362-
resume_awaiters before state'.awaiters
390+
else if Atomic.compare_and_set (as_atomic loc) state_old state then
391+
resume_awaiters before state_old.awaiters
363392
else exchange_no_alloc (Backoff.once backoff) loc state
364393

365394
let is_obstruction_free casn loc =
395+
(* Fenceless is safe as we are accessing a private location. *)
366396
fenceless_get casn == (Mode.obstruction_free :> status) && 0 <= loc.id
367397
[@@inline]
368398

369-
let cas loc before state =
370-
let state' = fenceless_get (as_atomic loc) in
371-
let before' = state'.before and after' = state'.after in
399+
let cas_with_state loc before state state_old =
400+
let before' = state_old.before and after' = state_old.after in
372401
((before == before' && before == after')
373-
|| before == if is_after state'.casn then after' else before')
402+
|| before == if is_after state_old.casn then after' else before')
374403
&& (before == state.after
375-
|| Atomic.compare_and_set (as_atomic loc) state' state
376-
&& resume_awaiters true state'.awaiters)
404+
|| Atomic.compare_and_set (as_atomic loc) state_old state
405+
&& resume_awaiters true state_old.awaiters)
377406
[@@inline]
378407

379408
let inc x = x + 1
@@ -415,33 +444,39 @@ module Loc = struct
415444

416445
let compare_and_set loc before after =
417446
let state = new_state after in
418-
cas loc before state
447+
let state_old = Atomic.get (as_atomic loc) in
448+
cas_with_state loc before state state_old
449+
450+
let fenceless_update ?(backoff = Backoff.default) loc f =
451+
update_with_state backoff loc f (fenceless_get (as_atomic loc))
452+
453+
let fenceless_modify ?backoff loc f =
454+
fenceless_update ?backoff loc f |> ignore
455+
[@@inline]
419456

420457
let update ?(backoff = Backoff.default) loc f =
421-
let state' = fenceless_get (as_atomic loc) in
422-
let before = eval state' in
423-
match f before with
424-
| after ->
425-
if before == after then before
426-
else
427-
let state = new_state after in
428-
if Atomic.compare_and_set (as_atomic loc) state' state then
429-
resume_awaiters before state'.awaiters
430-
else update_no_alloc (Backoff.once backoff) loc state f
431-
| exception Retry.Later ->
432-
let state = new_state before in
433-
block loc before;
434-
update_no_alloc backoff loc state f
458+
update_with_state backoff loc f (Atomic.get (as_atomic loc))
435459

436460
let modify ?backoff loc f = update ?backoff loc f |> ignore [@@inline]
437461

438462
let exchange ?(backoff = Backoff.default) loc value =
439463
exchange_no_alloc backoff loc (new_state value)
440464

441465
let set ?backoff loc value = exchange ?backoff loc value |> ignore
442-
let fetch_and_add ?backoff loc n = update ?backoff loc (( + ) n)
443-
let incr ?backoff loc = update ?backoff loc inc |> ignore
444-
let decr ?backoff loc = update ?backoff loc dec |> ignore
466+
467+
let fetch_and_add ?backoff loc n =
468+
if n = 0 then get loc
469+
else
470+
(* Fenceless is safe as we always update. *)
471+
fenceless_update ?backoff loc (( + ) n)
472+
473+
let incr ?backoff loc =
474+
(* Fenceless is safe as we always update. *)
475+
fenceless_update ?backoff loc inc |> ignore
476+
477+
let decr ?backoff loc =
478+
(* Fenceless is safe as we always update. *)
479+
fenceless_update ?backoff loc dec |> ignore
445480

446481
let has_awaiters loc =
447482
let state = Atomic.get (as_atomic loc) in
@@ -489,6 +524,7 @@ module Op = struct
489524
| [] -> determine_for_owner casn cass
490525
| CAS (loc, before, after) :: rest ->
491526
if before == after && is_obstruction_free casn loc then
527+
(* Fenceless is safe as there are fences before or after. *)
492528
let state = fenceless_get (as_atomic loc) in
493529
before == eval state && run (insert cass loc state) rest
494530
else
@@ -498,7 +534,7 @@ module Op = struct
498534
in
499535
let (CAS (loc, before, after)) = first in
500536
if before == after && is_obstruction_free casn loc then
501-
let state = fenceless_get (as_atomic loc) in
537+
let state = Atomic.get (as_atomic loc) in
502538
before == eval state
503539
&& run (CASN { loc; state; lt = NIL; gt = NIL; awaiters = [] }) rest
504540
else
@@ -517,6 +553,7 @@ module Xt = struct
517553

518554
let validate_one casn loc state =
519555
let before = if is_cmp casn state then eval state else state.before in
556+
(* Fenceless is safe inside transactions as each log update has a fence. *)
520557
if before != eval (fenceless_get (as_atomic loc)) then Retry.invalid ()
521558
[@@inline]
522559

@@ -540,6 +577,7 @@ module Xt = struct
540577
[@@inline]
541578

542579
let update0 loc f xt lt gt =
580+
(* Fenceless is safe inside transactions as each log update has a fence. *)
543581
let state = fenceless_get (as_atomic loc) in
544582
let before = eval state in
545583
match f before with
@@ -667,6 +705,7 @@ module Xt = struct
667705
let state =
668706
if is_cmp casn state then state
669707
else
708+
(* Fenceless is safe inside transactions as each log update has a fence. *)
670709
let current = fenceless_get (as_atomic loc) in
671710
if state.before != eval current then Retry.invalid ()
672711
else current
@@ -745,7 +784,10 @@ module Xt = struct
745784
let before = state.before in
746785
state.before <- state.after;
747786
state.casn <- casn_after;
748-
if cas loc before state then Action.run xt.post_commit result
787+
(* Fenceless is safe inside transactions as each log update has a fence. *)
788+
let state_old = fenceless_get (as_atomic loc) in
789+
if cas_with_state loc before state state_old then
790+
Action.run xt.post_commit result
749791
else commit (Backoff.once backoff) mode (reset_quick xt) tx
750792
| cass -> (
751793
match determine_for_owner xt.casn cass with

src/kcas/kcas.mli

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ module Loc : sig
141141
val fenceless_get : 'a t -> 'a
142142
(** [fenceless_get r] is like [get r] except that [fenceless_get]s may be
143143
reordered. *)
144+
145+
val fenceless_update : ?backoff:Backoff.t -> 'a t -> ('a -> 'a) -> 'a
146+
(** [fenceless_update r f] is like [update r f] except that in case [f x == x]
147+
the update may be reordered. *)
148+
149+
val fenceless_modify : ?backoff:Backoff.t -> 'a t -> ('a -> 'a) -> unit
150+
(** [fenceless_modify r f] is like [modify r f] except that in case [f x == x]
151+
the modify may be reordered. *)
144152
end
145153

146154
(** {1 Manipulating multiple locations atomically}

src/kcas_data/hashtbl.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,18 +353,21 @@ end
353353

354354
let find_opt t k =
355355
let t = Loc.get t in
356+
(* Fenceless is safe as we have a fence above. *)
356357
t.buckets |> bucket_of t.hash k |> Loc.fenceless_get
357358
|> Assoc.find_opt t.equal k
358359

359360
let find_all t k =
360361
let t = Loc.get t in
362+
(* Fenceless is safe as we have a fence above. *)
361363
t.buckets |> bucket_of t.hash k |> Loc.fenceless_get
362364
|> Assoc.find_all t.equal k
363365

364366
let find t k = match find_opt t k with None -> raise Not_found | Some v -> v
365367

366368
let mem t k =
367369
let t = Loc.get t in
370+
(* Fenceless is safe as we have a fence above. *)
368371
t.buckets |> bucket_of t.hash k |> Loc.fenceless_get |> Assoc.mem t.equal k
369372

370373
let clear t = Kcas.Xt.commit { tx = Xt.clear t }
@@ -388,6 +391,7 @@ let snapshot ?length ?record t =
388391
in
389392
Kcas.Xt.commit { tx };
390393
Kcas.Xt.commit { tx = Xt.perform_pending t } |> ignore;
394+
(* Fenceless is safe as commit above has fences. *)
391395
Loc.fenceless_get snapshot
392396

393397
let to_seq t =
@@ -463,6 +467,7 @@ let filter_map_inplace fn t =
463467
in
464468
Kcas.Xt.commit { tx };
465469
Kcas.Xt.commit { tx = Xt.perform_pending t } |> ignore;
470+
(* Fenceless is safe as commit above has fences. *)
466471
match Loc.fenceless_get raised with Done -> () | exn -> raise exn
467472

468473
let stats t =

src/kcas_data/mvar.ml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@ module Xt = struct
2525
end
2626

2727
let is_empty mv = Magic_option.is_none (Loc.get mv)
28-
let put mv value = Loc.modify mv (Magic_option.put_or_retry value)
28+
29+
let put mv value =
30+
(* Fenceless is safe as we always update. *)
31+
Loc.fenceless_modify mv (Magic_option.put_or_retry value)
2932

3033
let try_put mv value =
3134
Loc.compare_and_set mv Magic_option.none (Magic_option.some value)
3235

33-
let take mv = Magic_option.get_unsafe (Loc.update mv Magic_option.take_or_retry)
36+
let take mv =
37+
(* Fenceless is safe as we always update. *)
38+
Magic_option.get_unsafe (Loc.fenceless_update mv Magic_option.take_or_retry)
39+
3440
let take_opt mv = Magic_option.to_option (Loc.exchange mv Magic_option.none)
3541
let peek mv = Loc.get_as Magic_option.get_or_retry mv
3642
let peek_opt mv = Magic_option.to_option (Loc.get mv)

src/kcas_data/queue.ml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,25 @@ end
122122

123123
let is_empty q = Kcas.Xt.commit { tx = Xt.is_empty q }
124124
let length q = Kcas.Xt.commit { tx = Xt.length q }
125-
let add x q = Loc.modify q.back @@ Elems.cons x
125+
126+
let add x q =
127+
(* Fenceless is safe as we always update. *)
128+
Loc.fenceless_modify q.back @@ Elems.cons x
129+
126130
let push = add
127131

128132
let take_opt q =
129-
match Loc.update q.front Elems.tl_safe |> Elems.hd_opt with
133+
(* Fenceless is safe as we revert to a transaction in case we didn't update. *)
134+
match Loc.fenceless_update q.front Elems.tl_safe |> Elems.hd_opt with
130135
| None -> Kcas.Xt.commit { tx = Xt.take_opt q }
131136
| some -> some
132137

133-
let take_blocking q = Kcas.Xt.commit { tx = Xt.take_blocking q }
138+
let take_blocking q =
139+
(* Fenceless is safe as we revert to a transaction in case we didn't update. *)
140+
match Loc.fenceless_update q.front Elems.tl_safe |> Elems.hd_opt with
141+
| None -> Kcas.Xt.commit { tx = Xt.take_blocking q }
142+
| Some elem -> elem
143+
134144
let take_all q = Kcas.Xt.commit { tx = Xt.take_all q }
135145

136146
let peek_opt q =

src/kcas_data/stack.ml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,18 @@ end
2525

2626
let length s = Loc.get s |> Elems.length
2727
let is_empty s = Loc.get s == Elems.empty
28-
let push x s = Loc.modify s @@ Elems.cons x
28+
29+
let push x s =
30+
(* Fenceless is safe as we always update. *)
31+
Loc.fenceless_modify s @@ Elems.cons x
32+
2933
let pop_opt s = Loc.update s Elems.tl_safe |> Elems.hd_opt
3034
let pop_all s = Loc.exchange s Elems.empty |> Elems.to_seq
31-
let pop_blocking s = Loc.update s Elems.tl_or_retry |> Elems.hd_unsafe
35+
36+
let pop_blocking s =
37+
(* Fenceless is safe as we always update. *)
38+
Loc.fenceless_update s Elems.tl_or_retry |> Elems.hd_unsafe
39+
3240
let top_opt s = Loc.get s |> Elems.hd_opt
3341
let top_blocking s = Loc.get_as Elems.hd_or_retry s
3442
let clear s = Loc.set s Elems.empty

0 commit comments

Comments
 (0)