Skip to content

Commit f2aa501

Browse files
committed
Expose that 'a Loc.t does not float
This allows slightly faster accesses of arrays of locations.
1 parent 61c1dac commit f2aa501

File tree

4 files changed

+65
-36
lines changed

4 files changed

+65
-36
lines changed

README.md

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ one first creates shared memory locations:
114114
# let a = Loc.make 0
115115
and b = Loc.make 0
116116
and x = Loc.make 0
117-
val a : int Loc.t = <abstr>
118-
val b : int Loc.t = <abstr>
119-
val x : int Loc.t = <abstr>
117+
val a : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
118+
val b : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
119+
val x : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
120120
```
121121

122122
One can then manipulate the locations individually:
@@ -300,7 +300,7 @@ transactions to `push` and `try_pop` elements:
300300

301301
```ocaml
302302
# let a_stack : int stack = stack ()
303-
val a_stack : int stack = <abstr>
303+
val a_stack : int stack = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
304304
305305
# Xt.commit { tx = push a_stack 101 }
306306
- : unit = ()
@@ -418,7 +418,9 @@ transactions to `enqueue` and `try_dequeue` elements:
418418

419419
```ocaml
420420
# let a_queue : int queue = queue ()
421-
val a_queue : int queue = {front = <abstr>; back = <abstr>}
421+
val a_queue : int queue =
422+
{front = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>};
423+
back = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}}
422424
423425
# Xt.commit { tx = enqueue a_queue 76 }
424426
- : unit = ()
@@ -533,10 +535,12 @@ To test them out, let's create a fresh stack and a queue
533535

534536
```ocaml
535537
# let a_stack : int stack = stack ()
536-
val a_stack : int stack = <abstr>
538+
val a_stack : int stack = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
537539
538540
# let a_queue : int queue = queue ()
539-
val a_queue : int queue = {front = <abstr>; back = <abstr>}
541+
val a_queue : int queue =
542+
{front = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>};
543+
back = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}}
540544
```
541545

542546
and then spawn a domain that tries to atomically both pop and dequeue:
@@ -759,7 +763,8 @@ and create a leftist heap:
759763

760764
```ocaml
761765
# let a_heap : int leftist Loc.t = leftist ()
762-
val a_heap : int leftist Loc.t = <abstr>
766+
val a_heap : int leftist Loc.t =
767+
Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
763768
```
764769

765770
To populate the heap we need to define a transaction passing function and pass
@@ -1018,7 +1023,8 @@ We can then test that the cache works as expected:
10181023
```ocaml
10191024
# let a_cache : (int, string) cache = cache 2
10201025
val a_cache : (int, string) cache =
1021-
{space = <abstr>; table = <abstr>; order = <abstr>}
1026+
{space = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>};
1027+
table = <abstr>; order = <abstr>}
10221028
10231029
# Xt.commit { tx = set_blocking a_cache 101 "basics" }
10241030
- : unit = ()
@@ -1266,7 +1272,8 @@ Consider the following example of computing the size of a stack:
12661272

12671273
```ocaml
12681274
# let a_stack = Loc.make [2; 3]
1269-
val a_stack : int list Loc.t = <abstr>
1275+
val a_stack : int list Loc.t =
1276+
Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
12701277
12711278
# let n_elems =
12721279
let tx ~xt =
@@ -1432,7 +1439,9 @@ Using the Michael-Scott style queue is as easy as any other transactional queue:
14321439

14331440
```ocaml
14341441
# let a_queue : int queue = queue ()
1435-
val a_queue : int queue = {head = <abstr>; tail = <abstr>}
1442+
val a_queue : int queue =
1443+
{head = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>};
1444+
tail = <abstr>}
14361445
14371446
# Xt.commit { tx = enqueue a_queue 19 }
14381447
- : unit = ()
@@ -1884,7 +1893,10 @@ for hash tables, we are ready to take it out for a spin:
18841893
```ocaml
18851894
# let a_hashtbl : (string, int) hashtbl = hashtbl ()
18861895
val a_hashtbl : (string, int) hashtbl =
1887-
{pending = <abstr>; basic = {size = <abstr>; data = <abstr>}}
1896+
{pending = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>};
1897+
basic =
1898+
{size = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>};
1899+
data = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}}}
18881900
18891901
# let assoc = [
18901902
("Intro", 101);
@@ -1999,7 +2011,9 @@ in the earlier example:
19992011

20002012
```ocaml
20012013
# let a_queue : int queue = queue ()
2002-
val a_queue : int queue = {head = <abstr>; tail = <abstr>}
2014+
val a_queue : int queue =
2015+
{head = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>};
2016+
tail = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}}
20032017
20042018
# let counter = ref 1_000
20052019
val counter : int ref = {contents = 1000}
@@ -2054,8 +2068,8 @@ locations. Let's just create two locations `a` and `b`:
20542068

20552069
```ocaml
20562070
# let a = Loc.make 0 and b = Loc.make 0
2057-
val a : int Loc.t = <abstr>
2058-
val b : int Loc.t = <abstr>
2071+
val a : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
2072+
val b : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
20592073
```
20602074

20612075
And create a helper that spawns a domain that repeatedly increments `a` and

doc/scheduler-interop.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ state in between, and then returns their sum:
131131

132132
```ocaml
133133
# let state = Loc.make 0
134-
val state : int Loc.t = <abstr>
134+
val state : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
135135
# let sync_to target =
136136
state
137137
|> Loc.get_as @@ fun current ->

src/kcas/kcas.ml

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -560,12 +560,15 @@ let inc x = x + 1
560560
let dec x = x - 1
561561

562562
module Loc = struct
563-
type 'a t = 'a loc
563+
type !'a t = Loc : { state : 'state; id : 'id } -> 'a t
564+
565+
external of_loc : 'a loc -> 'a t = "%identity"
566+
external to_loc : 'a t -> 'a loc = "%identity"
564567

565568
let make ?(padded = false) ?(mode = `Obstruction_free) after =
566569
let state = new_state after
567570
and id = if mode == `Obstruction_free then Id.nat_id () else Id.neg_id () in
568-
make_loc padded state id
571+
make_loc padded state id |> of_loc
569572

570573
let make_contended ?mode after = make ~padded:true ?mode after
571574

@@ -576,10 +579,10 @@ module Loc = struct
576579
(if mode == `Obstruction_free then Id.nat_ids n else Id.neg_ids n)
577580
- (n - 1)
578581
in
579-
Array.init n @@ fun i -> make_loc padded state (id + i)
582+
Array.init n @@ fun i -> make_loc padded state (id + i) |> of_loc
580583

581-
let[@inline] get_id loc = loc.id
582-
let get loc = eval (atomic_get (as_atomic loc))
584+
let[@inline] get_id loc = (to_loc loc).id
585+
let get loc = eval (atomic_get (as_atomic (to_loc loc)))
583586

584587
let rec get_as timeout f loc state =
585588
let before = eval state in
@@ -588,40 +591,45 @@ module Loc = struct
588591
Timeout.cancel timeout;
589592
value
590593
| exception Retry.Later ->
591-
block timeout loc before;
594+
block timeout (to_loc loc) before;
592595
(* Fenceless is safe as there was already a fence before. *)
593-
get_as timeout f loc (fenceless_get (as_atomic loc))
596+
get_as timeout f loc (fenceless_get (as_atomic (to_loc loc)))
594597
| exception exn ->
595598
Timeout.cancel timeout;
596599
raise exn
597600

598601
let[@inline] get_as ?timeoutf f loc =
599-
get_as (Timeout.alloc_opt timeoutf) f loc (atomic_get (as_atomic loc))
602+
get_as
603+
(Timeout.alloc_opt timeoutf)
604+
f loc
605+
(atomic_get (as_atomic (to_loc loc)))
600606

601607
let[@inline] get_mode loc =
602-
if loc.id < 0 then `Lock_free else `Obstruction_free
608+
if (to_loc loc).id < 0 then `Lock_free else `Obstruction_free
603609

604610
let compare_and_set ?(backoff = Backoff.default) loc before after =
605611
let state = new_state after in
606-
let state_old = atomic_get (as_atomic loc) in
607-
cas_with_state backoff loc before state state_old
612+
let state_old = atomic_get (as_atomic (to_loc loc)) in
613+
cas_with_state backoff (to_loc loc) before state state_old
608614

609615
let fenceless_update ?timeoutf ?(backoff = Backoff.default) loc f =
610616
let timeout = Timeout.alloc_opt timeoutf in
611-
update_with_state timeout backoff loc f (fenceless_get (as_atomic loc))
617+
update_with_state timeout backoff (to_loc loc) f
618+
(fenceless_get (as_atomic (to_loc loc)))
612619

613620
let[@inline] fenceless_modify ?timeoutf ?backoff loc f =
614621
fenceless_update ?timeoutf ?backoff loc f |> ignore
615622

616623
let update ?timeoutf ?(backoff = Backoff.default) loc f =
617624
let timeout = Timeout.alloc_opt timeoutf in
618-
update_with_state timeout backoff loc f (atomic_get (as_atomic loc))
625+
update_with_state timeout backoff (to_loc loc) f
626+
(atomic_get (as_atomic (to_loc loc)))
619627

620628
let[@inline] modify ?timeoutf ?backoff loc f =
621629
update ?timeoutf ?backoff loc f |> ignore
622630

623631
let exchange ?(backoff = Backoff.default) loc value =
624-
exchange_no_alloc backoff loc (new_state value)
632+
exchange_no_alloc backoff (to_loc loc) (new_state value)
625633

626634
let set ?backoff loc value = exchange ?backoff loc value |> ignore
627635

@@ -640,10 +648,10 @@ module Loc = struct
640648
fenceless_update ?backoff loc dec |> ignore
641649

642650
let has_awaiters loc =
643-
let state = atomic_get (as_atomic loc) in
651+
let state = atomic_get (as_atomic (to_loc loc)) in
644652
state.awaiters != []
645653

646-
let fenceless_get loc = eval (fenceless_get (as_atomic loc))
654+
let fenceless_get loc = eval (fenceless_get (as_atomic (to_loc loc)))
647655
end
648656

649657
module Xt = struct
@@ -712,6 +720,7 @@ module Xt = struct
712720
current
713721

714722
let[@inline] unsafe_update ~xt loc f =
723+
let loc = Loc.to_loc loc in
715724
maybe_validate_log xt;
716725
let x = loc.id in
717726
match !(tree_as_ref xt) with
@@ -764,6 +773,7 @@ module Xt = struct
764773
xt_r.post_commit <- Action.append action xt_r.post_commit
765774

766775
let validate ~xt loc =
776+
let loc = Loc.to_loc loc in
767777
let x = loc.id in
768778
match !(tree_as_ref xt) with
769779
| T Leaf -> ()
@@ -781,6 +791,7 @@ module Xt = struct
781791
end
782792

783793
let is_in_log ~xt loc =
794+
let loc = Loc.to_loc loc in
784795
let x = loc.id in
785796
match !(tree_as_ref xt) with
786797
| T Leaf -> false

src/kcas/kcas.mli

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@
4545
# let a = Loc.make 0
4646
and b = Loc.make 0
4747
and x = Loc.make 0
48-
val a : int Loc.t = <abstr>
49-
val b : int Loc.t = <abstr>
50-
val x : int Loc.t = <abstr>
48+
val a : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
49+
val b : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
50+
val x : int Loc.t = Kcas.Loc.Loc {Kcas.Loc.state = <poly>; id = <poly>}
5151
]}
5252
5353
One can then manipulate the locations individually:
@@ -170,8 +170,12 @@ end
170170

171171
(** Shared memory locations. *)
172172
module Loc : sig
173-
type !'a t
174173
(** Type of shared memory locations. *)
174+
type !'a t =
175+
| Loc : { state : 'state; id : 'id } -> 'a t
176+
(** The shape is transparent to allow the compiler to perform
177+
optimizations on array accesses. User code should treat this tyoe
178+
as abstract. *)
175179

176180
val make : ?padded:bool -> ?mode:Mode.t -> 'a -> 'a t
177181
(** [make initial] creates a new shared memory location with the [initial]

0 commit comments

Comments
 (0)