From 1ad36b13466191ae871aa2ed4649907229491130 Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Tue, 25 Jun 2024 18:40:23 +0200 Subject: [PATCH 1/9] Create a multicore-magic-dscheck library. --- dune-project | 10 + multicore-magic-dscheck.opam | 29 ++ src/dscheck/dune | 4 + .../multicore_magic_atomic_array_dscheck.ml | 20 ++ src/dscheck/multicore_magic_dscheck.ml | 14 + src/dscheck/multicore_magic_dscheck.mli | 258 ++++++++++++++++++ src/dscheck/transparent_atomic_dscheck.ml | 3 + 7 files changed, 338 insertions(+) create mode 100644 multicore-magic-dscheck.opam create mode 100644 src/dscheck/dune create mode 100644 src/dscheck/multicore_magic_atomic_array_dscheck.ml create mode 100644 src/dscheck/multicore_magic_dscheck.ml create mode 100644 src/dscheck/multicore_magic_dscheck.mli create mode 100644 src/dscheck/transparent_atomic_dscheck.ml diff --git a/dune-project b/dune-project index d6d4ea9..2492432 100644 --- a/dune-project +++ b/dune-project @@ -41,3 +41,13 @@ (and (>= 2.4.1) :with-doc)))) + +(package + (name multicore-magic-dscheck) + (synopsis "Low-level multiscore utilities for OCaml") + (depends + (ocaml + (>= 4.12.0)) + ;; Test dependencies + (multicore-magic + (= :version)))) diff --git a/multicore-magic-dscheck.opam b/multicore-magic-dscheck.opam new file mode 100644 index 0000000..3d2c095 --- /dev/null +++ b/multicore-magic-dscheck.opam @@ -0,0 +1,29 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "Low-level multiscore utilities for OCaml" +maintainer: ["Vesa Karvonen "] +authors: ["Vesa Karvonen "] +license: "ISC" +homepage: "https://github.com/ocaml-multicore/multicore-magic" +bug-reports: "https://github.com/ocaml-multicore/multicore-magic/issues" +depends: [ + "dune" {>= "3.14"} + "ocaml" {>= "4.12.0"} + "multicore-magic" {= version} + "odoc" {with-doc} +] +build: [ + ["dune" "subst"] {dev} + [ + "dune" + "build" + "-p" + name + "-j" + jobs + "@install" + "@runtest" {with-test} + "@doc" {with-doc} + ] +] +dev-repo: "git+https://github.com/ocaml-multicore/multicore-magic.git" diff --git a/src/dscheck/dune b/src/dscheck/dune new file mode 100644 index 0000000..af2bb7e --- /dev/null +++ b/src/dscheck/dune @@ -0,0 +1,4 @@ +(library + (name multicore_magic_dscheck) + (package multicore-magic-dscheck) + (libraries multicore-magic dscheck)) diff --git a/src/dscheck/multicore_magic_atomic_array_dscheck.ml b/src/dscheck/multicore_magic_atomic_array_dscheck.ml new file mode 100644 index 0000000..7389b23 --- /dev/null +++ b/src/dscheck/multicore_magic_atomic_array_dscheck.ml @@ -0,0 +1,20 @@ +module Atomic = Dscheck.TracedAtomic + +type 'a t = 'a Atomic.t array + +let[@inline] at (type a) (xs : a t) i : a Atomic.t = + (* ['a t] does not contain [float]s. *) + Obj.magic (Array.unsafe_get (Obj.magic xs : a ref array) i) + +let[@inline] make n v = Array.init n @@ fun _ -> Atomic.make v +let[@inline] init n fn = Array.init n @@ fun i -> Atomic.make (fn i) +let[@inline] of_array xs = init (Array.length xs) (Array.unsafe_get xs) + +external length : 'a array -> int = "%array_length" + +let[@inline] unsafe_fenceless_set xs i v = Obj.magic (at xs i) := v +let[@inline] unsafe_fenceless_get xs i = !(Obj.magic (at xs i)) + +let[@inline] unsafe_compare_and_set xs i b a = + Atomic.compare_and_set (at xs i) b a + diff --git a/src/dscheck/multicore_magic_dscheck.ml b/src/dscheck/multicore_magic_dscheck.ml new file mode 100644 index 0000000..c8c2201 --- /dev/null +++ b/src/dscheck/multicore_magic_dscheck.ml @@ -0,0 +1,14 @@ +include Multicore_magic + + +let[@inline] fenceless_get (atomic : 'a Atomic.t) = + !(Sys.opaque_identity (Obj.magic atomic : 'a ref)) + +let[@inline] fenceless_set (atomic : 'a Atomic.t) value = + (Obj.magic atomic : 'a ref) := value + +let[@inline] fence atomic = Atomic.fetch_and_add atomic 0 |> ignore + + +module Transparent_atomic = Transparent_atomic_dscheck +module Atomic_array = Multicore_magic_atomic_array_dscheck diff --git a/src/dscheck/multicore_magic_dscheck.mli b/src/dscheck/multicore_magic_dscheck.mli new file mode 100644 index 0000000..619b09e --- /dev/null +++ b/src/dscheck/multicore_magic_dscheck.mli @@ -0,0 +1,258 @@ +(** This is a library of magic multicore utilities intended for experts for + extracting the best possible performance from multicore OCaml. + + Hopefully future releases of multicore OCaml will make this library + obsolete! *) + +(** {1 Helpers for using padding to avoid false sharing} *) + +val copy_as_padded : 'a -> 'a +(** Depending on the object, either creates a shallow clone of it or returns it + as is. When cloned, the clone will have extra padding words added after the + last used word. + + This is designed to help avoid + {{:https://en.wikipedia.org/wiki/False_sharing} false sharing}. False + sharing has a negative impact on multicore performance. Accesses of both + atomic and non-atomic locations, whether read-only or read-write, may suffer + from false sharing. + + The intended use case for this is to pad all long lived objects that are + being accessed highly frequently (read or written). + + Many kinds of objects can be padded, for example: + + {[ + let padded_atomic = Multicore_magic.copy_as_padded (Atomic.make 101) + + let padded_ref = Multicore_magic.copy_as_padded (ref 42) + + let padded_record = Multicore_magic.copy_as_padded { + number = 76; + pointer = 1 :: 2 :: 3 :: []; + } + + let padded_variant = Multicore_magic.copy_as_padded (Some 1) + ]} + + Padding changes the length of an array. If you need to pad an array, use + {!make_padded_array}. *) + +val make_padded_array : int -> 'a -> 'a array +(** Creates a padded array. The length of the returned array includes padding. + Use {!length_of_padded_array} to get the unpadded length. *) + +val length_of_padded_array : 'a array -> int +(** Returns the length of an array created by {!make_padded_array} without the + padding. + + {b WARNING}: This is not guaranteed to work with {!copy_as_padded}. *) + +val length_of_padded_array_minus_1 : 'a array -> int +(** Returns the length of an array created by {!make_padded_array} without the + padding minus 1. + + {b WARNING}: This is not guaranteed to work with {!copy_as_padded}. *) + +(** {1 Missing [Atomic] operations} *) + +val fenceless_get : 'a Atomic.t -> 'a +(** Get a value from the atomic without performing an acquire fence. + + Consider the following prototypical example of a lock-free algorithm: + + {[ + let rec prototypical_lock_free_algorithm () = + let expected = Atomic.get atomic in + let desired = (* computed from expected *) in + if not (Atomic.compare_and_set atomic expected desired) then + (* failure, maybe retry *) + else + (* success *) + ]} + + A potential performance problem with the above example is that it performs + two acquire fences. Both the [Atomic.get] and the [Atomic.compare_and_set] + perform an acquire fence. This may have a negative impact on performance. + + Assuming the first fence is not necessary, we can rewrite the example using + {!fenceless_get} as follows: + + {[ + let rec prototypical_lock_free_algorithm () = + let expected = Multicore_magic.fenceless_get atomic in + let desired = (* computed from expected *) in + if not (Atomic.compare_and_set atomic expected desired) then + (* failure, maybe retry *) + else + (* success *) + ]} + + Now only a single acquire fence is performed by [Atomic.compare_and_set] and + performance may be improved. *) + +val fenceless_set : 'a Atomic.t -> 'a -> unit +(** Set the value of an atomic without performing a full fence. + + Consider the following example: + + {[ + let new_atomic = Atomic.make dummy_value in + (* prepare data_structure referring to new_atomic *) + Atomic.set new_atomic data_structure; + (* publish the data_structure: *) + Atomic.exchance old_atomic data_structure + ]} + + A potential performance problem with the above example is that it performs + two full fences. Both the [Atomic.set] used to initialize the data + structure and the [Atomic.exchange] used to publish the data structure + perform a full fence. The same would also apply in cases where + [Atomic.compare_and_set] or [Atomic.set] would be used to publish the data + structure. This may have a negative impact on performance. + + Using {!fenceless_set} we can rewrite the example as follows: + + {[ + let new_atomic = Atomic.make dummy_value in + (* prepare data_structure referring to new_atomic *) + Multicore_magic.fenceless_set new_atomic data_structure; + (* publish the data_structure: *) + Atomic.exchance old_atomic data_structure + ]} + + Now only a single full fence is performed by [Atomic.exchange] and + performance may be improved. *) + +val fence : int Atomic.t -> unit +(** Perform a full acquire-release fence on the given atomic. + + [fence atomic] is equivalent to [ignore (Atomic.fetch_and_add atomic 0)]. *) + +(** {1 Fixes and workarounds} *) + +module Transparent_atomic : sig + (** A replacement for [Stdlib.Atomic] with fixes and performance improvements + + [Stdlib.Atomic.get] is incorrectly subject to CSE optimization in OCaml + 5.0.0 and 5.1.0. This can result in code being generated that can produce + results that cannot be explained with the OCaml memory model. It can also + sometimes result in code being generated where a manual optimization to + avoid writing to memory is defeated by the compiler as the compiler + eliminates a (repeated) read access. This module implements {!get} such + that argument to [Stdlib.Atomic.get] is passed through + [Sys.opaque_identity], which prevents the compiler from applying the CSE + optimization. + + OCaml 5 generates inefficient accesses of ['a Stdlib.Atomic.t array]s + assuming that the array might be an array of [float]ing point numbers. + That is because the [Stdlib.Atomic.t] type constructor is opaque, which + means that the compiler cannot assume that [_ Stdlib.Atomic.t] is not the + same as [float]. This module defines {{!t} the type} as [private 'a ref], + which allows the compiler to know that it cannot be the same as [float], + which allows the compiler to generate more efficient array accesses. This + can both improve performance and reduce size of generated code when using + arrays of atomics. *) + + type !'a t + + val make : 'a -> 'a t + val make_contended : 'a -> 'a t + val get : 'a t -> 'a + val fenceless_get : 'a t -> 'a + val set : 'a t -> 'a -> unit + val fenceless_set : 'a t -> 'a -> unit + val exchange : 'a t -> 'a -> 'a + val compare_and_set : 'a t -> 'a -> 'a -> bool + val fetch_and_add : int t -> int -> int + val incr : int t -> unit + val decr : int t -> unit +end + +(** {1 Missing functionality} *) + +module Atomic_array : sig + (** Array of (potentially unboxed) atomic locations. + + Where available, this uses an undocumented operation exported by the OCaml + 5 runtime, + {{:https://github.com/ocaml/ocaml/blob/7a5d882d22cdd32b6319e9be680bd1a3d67377a9/runtime/memory.c#L313-L338} + [caml_atomic_cas_field]}, which makes it possible to perform sequentially + consistent atomic updates of record fields and array elements. + + Hopefully a future version of OCaml provides more comprehensive and even + more efficient support for both sequentially consistent and relaxed atomic + operations on records and arrays. *) + + type !'a t + (** Represents an array of atomic locations. *) + + val make : int -> 'a -> 'a t + (** [make n value] creates a new array of [n] atomic locations having given + [value]. *) + + val of_array : 'a array -> 'a t + (** [of_array non_atomic_array] create a new array of atomic locations as a + copy of the given [non_atomic_array]. *) + + val init : int -> (int -> 'a) -> 'a t + (** [init n fn] is equivalent to {{!of_array} [of_array (Array.init n fn)]}. *) + + val length : 'a t -> int + (** [length atomic_array] returns the length of the [atomic_array]. *) + + val unsafe_fenceless_get : 'a t -> int -> 'a + (** [unsafe_fenceless_get atomic_array index] reads and returns the value at + the specified [index] of the [atomic_array]. + + ⚠️ The read is {i relaxed} and may be reordered with respect to other reads + and writes in program order. + + ⚠️ No bounds checking is performed. *) + + val unsafe_fenceless_set : 'a t -> int -> 'a -> unit + (** [unsafe_fenceless_set atomic_array index value] writes the given [value] + to the specified [index] of the [atomic_array]. + + ⚠️ The write is {i relaxed} and may be reordered with respect to other + reads and (non-initializing) writes in program order. + + ⚠️ No bounds checking is performed. *) + + val unsafe_compare_and_set : 'a t -> int -> 'a -> 'a -> bool + (** [unsafe_compare_and_set atomic_array index before after] atomically + updates the specified [index] of the [atomic_array] to the [after] value + in case it had the [before] value and returns a boolean indicating whether + that was the case. This operation is {i sequentially consistent} and may + not be reordered with respect to other reads and writes in program order. + + ⚠️ No bounds checking is performed. *) +end + +(** {1 Avoiding contention} *) + +val instantaneous_domain_index : unit -> int +(** [instantaneous_domain_index ()] potentially (re)allocates and returns a + non-negative integer "index" for the current domain. The indices are + guaranteed to be unique among the domains that exist at a point in time. + Each call of [instantaneous_domain_index ()] may return a different index. + + The intention is that the returned value can be used as an index into a + contention avoiding parallelism safe data structure. For example, a naïve + scalable increment of one counter from an array of counters could be done as + follows: + + {[ + let incr counters = + (* Assuming length of [counters] is a power of two and larger than + the number of domains. *) + let mask = Array.length counters - 1 in + let index = instantaneous_domain_index () in + Atomic.incr counters.(index land mask) + ]} + + The implementation ensures that the indices are allocated as densely as + possible at any given moment. This should allow allocating as many counters + as needed and essentially eliminate contention. + + On OCaml 4 [instantaneous_domain_index ()] will always return [0]. *) diff --git a/src/dscheck/transparent_atomic_dscheck.ml b/src/dscheck/transparent_atomic_dscheck.ml new file mode 100644 index 0000000..a17cc84 --- /dev/null +++ b/src/dscheck/transparent_atomic_dscheck.ml @@ -0,0 +1,3 @@ +include Dscheck.TracedAtomic +let fenceless_get = get +let fenceless_set = set \ No newline at end of file From d9fa5c7f4137dd52796a52a2e3714b1b2064b950 Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Mon, 1 Jul 2024 18:48:29 +0200 Subject: [PATCH 2/9] Add a pin-depends to dscheck + formatting. --- dune-project | 2 +- multicore-magic-dscheck.opam | 7 ++++++- multicore-magic-dscheck.opam.template | 5 +++++ src/dscheck/multicore_magic_atomic_array_dscheck.ml | 1 - src/dscheck/multicore_magic_dscheck.ml | 2 -- src/dscheck/transparent_atomic_dscheck.ml | 3 ++- 6 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 multicore-magic-dscheck.opam.template diff --git a/dune-project b/dune-project index 2492432..3501c51 100644 --- a/dune-project +++ b/dune-project @@ -50,4 +50,4 @@ (>= 4.12.0)) ;; Test dependencies (multicore-magic - (= :version)))) + (>= 2.2.0)))) diff --git a/multicore-magic-dscheck.opam b/multicore-magic-dscheck.opam index 3d2c095..06c13fb 100644 --- a/multicore-magic-dscheck.opam +++ b/multicore-magic-dscheck.opam @@ -9,7 +9,7 @@ bug-reports: "https://github.com/ocaml-multicore/multicore-magic/issues" depends: [ "dune" {>= "3.14"} "ocaml" {>= "4.12.0"} - "multicore-magic" {= version} + "multicore-magic" {>= "2.2.0"} "odoc" {with-doc} ] build: [ @@ -27,3 +27,8 @@ build: [ ] ] dev-repo: "git+https://github.com/ocaml-multicore/multicore-magic.git" +pin-depends: + [ + "dscheck.~dev" + "git+https://github.com/lyrm/dscheck.git#make_contended" + ] \ No newline at end of file diff --git a/multicore-magic-dscheck.opam.template b/multicore-magic-dscheck.opam.template new file mode 100644 index 0000000..589e962 --- /dev/null +++ b/multicore-magic-dscheck.opam.template @@ -0,0 +1,5 @@ +pin-depends: + [ + "dscheck.~dev" + "git+https://github.com/lyrm/dscheck.git#make_contended" + ] \ No newline at end of file diff --git a/src/dscheck/multicore_magic_atomic_array_dscheck.ml b/src/dscheck/multicore_magic_atomic_array_dscheck.ml index 7389b23..8a3cdfb 100644 --- a/src/dscheck/multicore_magic_atomic_array_dscheck.ml +++ b/src/dscheck/multicore_magic_atomic_array_dscheck.ml @@ -17,4 +17,3 @@ let[@inline] unsafe_fenceless_get xs i = !(Obj.magic (at xs i)) let[@inline] unsafe_compare_and_set xs i b a = Atomic.compare_and_set (at xs i) b a - diff --git a/src/dscheck/multicore_magic_dscheck.ml b/src/dscheck/multicore_magic_dscheck.ml index c8c2201..06311c6 100644 --- a/src/dscheck/multicore_magic_dscheck.ml +++ b/src/dscheck/multicore_magic_dscheck.ml @@ -1,6 +1,5 @@ include Multicore_magic - let[@inline] fenceless_get (atomic : 'a Atomic.t) = !(Sys.opaque_identity (Obj.magic atomic : 'a ref)) @@ -9,6 +8,5 @@ let[@inline] fenceless_set (atomic : 'a Atomic.t) value = let[@inline] fence atomic = Atomic.fetch_and_add atomic 0 |> ignore - module Transparent_atomic = Transparent_atomic_dscheck module Atomic_array = Multicore_magic_atomic_array_dscheck diff --git a/src/dscheck/transparent_atomic_dscheck.ml b/src/dscheck/transparent_atomic_dscheck.ml index a17cc84..d01086d 100644 --- a/src/dscheck/transparent_atomic_dscheck.ml +++ b/src/dscheck/transparent_atomic_dscheck.ml @@ -1,3 +1,4 @@ include Dscheck.TracedAtomic + let fenceless_get = get -let fenceless_set = set \ No newline at end of file +let fenceless_set = set From 44221971975a7ea797df450957773d4efe19276e Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Tue, 2 Jul 2024 12:01:38 +0200 Subject: [PATCH 3/9] WIP : Trying with unwrapped lib and a module named multicore_magic. Cycling deps issue. --- src/dscheck/dune | 6 +- src/dscheck/multicore_magic.ml | 37 +++ .../multicore_magic_atomic_array_dscheck.ml | 19 -- src/dscheck/multicore_magic_dscheck.ml | 12 - src/dscheck/multicore_magic_dscheck.mli | 258 ------------------ src/dscheck/transparent_atomic_dscheck.ml | 4 - 6 files changed, 41 insertions(+), 295 deletions(-) create mode 100644 src/dscheck/multicore_magic.ml delete mode 100644 src/dscheck/multicore_magic_atomic_array_dscheck.ml delete mode 100644 src/dscheck/multicore_magic_dscheck.ml delete mode 100644 src/dscheck/multicore_magic_dscheck.mli delete mode 100644 src/dscheck/transparent_atomic_dscheck.ml diff --git a/src/dscheck/dune b/src/dscheck/dune index af2bb7e..b649616 100644 --- a/src/dscheck/dune +++ b/src/dscheck/dune @@ -1,4 +1,6 @@ (library (name multicore_magic_dscheck) - (package multicore-magic-dscheck) - (libraries multicore-magic dscheck)) + (public_name multicore-magic-dscheck) + (wrapped false) + (root_module Deps) + (libraries multicore-magic dscheck)) \ No newline at end of file diff --git a/src/dscheck/multicore_magic.ml b/src/dscheck/multicore_magic.ml new file mode 100644 index 0000000..600a2ae --- /dev/null +++ b/src/dscheck/multicore_magic.ml @@ -0,0 +1,37 @@ +include Deps.Multicore_magic +module Atomic = Deps.Dscheck.TracedAtomic + +module Transparent_atomic = struct + include Atomic + + let fenceless_get = get + let fenceless_set = set +end + +let[@inline] fenceless_get (atomic : 'a Atomic.t) = + !(Sys.opaque_identity (Obj.magic atomic : 'a ref)) + +let[@inline] fenceless_set (atomic : 'a Atomic.t) value = + (Obj.magic atomic : 'a ref) := value + +let[@inline] fence atomic = Atomic.fetch_and_add atomic 0 |> ignore + +module Atomic_array = struct + type 'a t = 'a Atomic.t array + + let[@inline] at (type a) (xs : a t) i : a Atomic.t = + (* ['a t] does not contain [float]s. *) + Obj.magic (Array.unsafe_get (Obj.magic xs : a ref array) i) + + let[@inline] make n v = Array.init n @@ fun _ -> Atomic.make v + let[@inline] init n fn = Array.init n @@ fun i -> Atomic.make (fn i) + let[@inline] of_array xs = init (Array.length xs) (Array.unsafe_get xs) + + external length : 'a array -> int = "%array_length" + + let[@inline] unsafe_fenceless_set xs i v = Obj.magic (at xs i) := v + let[@inline] unsafe_fenceless_get xs i = !(Obj.magic (at xs i)) + + let[@inline] unsafe_compare_and_set xs i b a = + Atomic.compare_and_set (at xs i) b a +end diff --git a/src/dscheck/multicore_magic_atomic_array_dscheck.ml b/src/dscheck/multicore_magic_atomic_array_dscheck.ml deleted file mode 100644 index 8a3cdfb..0000000 --- a/src/dscheck/multicore_magic_atomic_array_dscheck.ml +++ /dev/null @@ -1,19 +0,0 @@ -module Atomic = Dscheck.TracedAtomic - -type 'a t = 'a Atomic.t array - -let[@inline] at (type a) (xs : a t) i : a Atomic.t = - (* ['a t] does not contain [float]s. *) - Obj.magic (Array.unsafe_get (Obj.magic xs : a ref array) i) - -let[@inline] make n v = Array.init n @@ fun _ -> Atomic.make v -let[@inline] init n fn = Array.init n @@ fun i -> Atomic.make (fn i) -let[@inline] of_array xs = init (Array.length xs) (Array.unsafe_get xs) - -external length : 'a array -> int = "%array_length" - -let[@inline] unsafe_fenceless_set xs i v = Obj.magic (at xs i) := v -let[@inline] unsafe_fenceless_get xs i = !(Obj.magic (at xs i)) - -let[@inline] unsafe_compare_and_set xs i b a = - Atomic.compare_and_set (at xs i) b a diff --git a/src/dscheck/multicore_magic_dscheck.ml b/src/dscheck/multicore_magic_dscheck.ml deleted file mode 100644 index 06311c6..0000000 --- a/src/dscheck/multicore_magic_dscheck.ml +++ /dev/null @@ -1,12 +0,0 @@ -include Multicore_magic - -let[@inline] fenceless_get (atomic : 'a Atomic.t) = - !(Sys.opaque_identity (Obj.magic atomic : 'a ref)) - -let[@inline] fenceless_set (atomic : 'a Atomic.t) value = - (Obj.magic atomic : 'a ref) := value - -let[@inline] fence atomic = Atomic.fetch_and_add atomic 0 |> ignore - -module Transparent_atomic = Transparent_atomic_dscheck -module Atomic_array = Multicore_magic_atomic_array_dscheck diff --git a/src/dscheck/multicore_magic_dscheck.mli b/src/dscheck/multicore_magic_dscheck.mli deleted file mode 100644 index 619b09e..0000000 --- a/src/dscheck/multicore_magic_dscheck.mli +++ /dev/null @@ -1,258 +0,0 @@ -(** This is a library of magic multicore utilities intended for experts for - extracting the best possible performance from multicore OCaml. - - Hopefully future releases of multicore OCaml will make this library - obsolete! *) - -(** {1 Helpers for using padding to avoid false sharing} *) - -val copy_as_padded : 'a -> 'a -(** Depending on the object, either creates a shallow clone of it or returns it - as is. When cloned, the clone will have extra padding words added after the - last used word. - - This is designed to help avoid - {{:https://en.wikipedia.org/wiki/False_sharing} false sharing}. False - sharing has a negative impact on multicore performance. Accesses of both - atomic and non-atomic locations, whether read-only or read-write, may suffer - from false sharing. - - The intended use case for this is to pad all long lived objects that are - being accessed highly frequently (read or written). - - Many kinds of objects can be padded, for example: - - {[ - let padded_atomic = Multicore_magic.copy_as_padded (Atomic.make 101) - - let padded_ref = Multicore_magic.copy_as_padded (ref 42) - - let padded_record = Multicore_magic.copy_as_padded { - number = 76; - pointer = 1 :: 2 :: 3 :: []; - } - - let padded_variant = Multicore_magic.copy_as_padded (Some 1) - ]} - - Padding changes the length of an array. If you need to pad an array, use - {!make_padded_array}. *) - -val make_padded_array : int -> 'a -> 'a array -(** Creates a padded array. The length of the returned array includes padding. - Use {!length_of_padded_array} to get the unpadded length. *) - -val length_of_padded_array : 'a array -> int -(** Returns the length of an array created by {!make_padded_array} without the - padding. - - {b WARNING}: This is not guaranteed to work with {!copy_as_padded}. *) - -val length_of_padded_array_minus_1 : 'a array -> int -(** Returns the length of an array created by {!make_padded_array} without the - padding minus 1. - - {b WARNING}: This is not guaranteed to work with {!copy_as_padded}. *) - -(** {1 Missing [Atomic] operations} *) - -val fenceless_get : 'a Atomic.t -> 'a -(** Get a value from the atomic without performing an acquire fence. - - Consider the following prototypical example of a lock-free algorithm: - - {[ - let rec prototypical_lock_free_algorithm () = - let expected = Atomic.get atomic in - let desired = (* computed from expected *) in - if not (Atomic.compare_and_set atomic expected desired) then - (* failure, maybe retry *) - else - (* success *) - ]} - - A potential performance problem with the above example is that it performs - two acquire fences. Both the [Atomic.get] and the [Atomic.compare_and_set] - perform an acquire fence. This may have a negative impact on performance. - - Assuming the first fence is not necessary, we can rewrite the example using - {!fenceless_get} as follows: - - {[ - let rec prototypical_lock_free_algorithm () = - let expected = Multicore_magic.fenceless_get atomic in - let desired = (* computed from expected *) in - if not (Atomic.compare_and_set atomic expected desired) then - (* failure, maybe retry *) - else - (* success *) - ]} - - Now only a single acquire fence is performed by [Atomic.compare_and_set] and - performance may be improved. *) - -val fenceless_set : 'a Atomic.t -> 'a -> unit -(** Set the value of an atomic without performing a full fence. - - Consider the following example: - - {[ - let new_atomic = Atomic.make dummy_value in - (* prepare data_structure referring to new_atomic *) - Atomic.set new_atomic data_structure; - (* publish the data_structure: *) - Atomic.exchance old_atomic data_structure - ]} - - A potential performance problem with the above example is that it performs - two full fences. Both the [Atomic.set] used to initialize the data - structure and the [Atomic.exchange] used to publish the data structure - perform a full fence. The same would also apply in cases where - [Atomic.compare_and_set] or [Atomic.set] would be used to publish the data - structure. This may have a negative impact on performance. - - Using {!fenceless_set} we can rewrite the example as follows: - - {[ - let new_atomic = Atomic.make dummy_value in - (* prepare data_structure referring to new_atomic *) - Multicore_magic.fenceless_set new_atomic data_structure; - (* publish the data_structure: *) - Atomic.exchance old_atomic data_structure - ]} - - Now only a single full fence is performed by [Atomic.exchange] and - performance may be improved. *) - -val fence : int Atomic.t -> unit -(** Perform a full acquire-release fence on the given atomic. - - [fence atomic] is equivalent to [ignore (Atomic.fetch_and_add atomic 0)]. *) - -(** {1 Fixes and workarounds} *) - -module Transparent_atomic : sig - (** A replacement for [Stdlib.Atomic] with fixes and performance improvements - - [Stdlib.Atomic.get] is incorrectly subject to CSE optimization in OCaml - 5.0.0 and 5.1.0. This can result in code being generated that can produce - results that cannot be explained with the OCaml memory model. It can also - sometimes result in code being generated where a manual optimization to - avoid writing to memory is defeated by the compiler as the compiler - eliminates a (repeated) read access. This module implements {!get} such - that argument to [Stdlib.Atomic.get] is passed through - [Sys.opaque_identity], which prevents the compiler from applying the CSE - optimization. - - OCaml 5 generates inefficient accesses of ['a Stdlib.Atomic.t array]s - assuming that the array might be an array of [float]ing point numbers. - That is because the [Stdlib.Atomic.t] type constructor is opaque, which - means that the compiler cannot assume that [_ Stdlib.Atomic.t] is not the - same as [float]. This module defines {{!t} the type} as [private 'a ref], - which allows the compiler to know that it cannot be the same as [float], - which allows the compiler to generate more efficient array accesses. This - can both improve performance and reduce size of generated code when using - arrays of atomics. *) - - type !'a t - - val make : 'a -> 'a t - val make_contended : 'a -> 'a t - val get : 'a t -> 'a - val fenceless_get : 'a t -> 'a - val set : 'a t -> 'a -> unit - val fenceless_set : 'a t -> 'a -> unit - val exchange : 'a t -> 'a -> 'a - val compare_and_set : 'a t -> 'a -> 'a -> bool - val fetch_and_add : int t -> int -> int - val incr : int t -> unit - val decr : int t -> unit -end - -(** {1 Missing functionality} *) - -module Atomic_array : sig - (** Array of (potentially unboxed) atomic locations. - - Where available, this uses an undocumented operation exported by the OCaml - 5 runtime, - {{:https://github.com/ocaml/ocaml/blob/7a5d882d22cdd32b6319e9be680bd1a3d67377a9/runtime/memory.c#L313-L338} - [caml_atomic_cas_field]}, which makes it possible to perform sequentially - consistent atomic updates of record fields and array elements. - - Hopefully a future version of OCaml provides more comprehensive and even - more efficient support for both sequentially consistent and relaxed atomic - operations on records and arrays. *) - - type !'a t - (** Represents an array of atomic locations. *) - - val make : int -> 'a -> 'a t - (** [make n value] creates a new array of [n] atomic locations having given - [value]. *) - - val of_array : 'a array -> 'a t - (** [of_array non_atomic_array] create a new array of atomic locations as a - copy of the given [non_atomic_array]. *) - - val init : int -> (int -> 'a) -> 'a t - (** [init n fn] is equivalent to {{!of_array} [of_array (Array.init n fn)]}. *) - - val length : 'a t -> int - (** [length atomic_array] returns the length of the [atomic_array]. *) - - val unsafe_fenceless_get : 'a t -> int -> 'a - (** [unsafe_fenceless_get atomic_array index] reads and returns the value at - the specified [index] of the [atomic_array]. - - ⚠️ The read is {i relaxed} and may be reordered with respect to other reads - and writes in program order. - - ⚠️ No bounds checking is performed. *) - - val unsafe_fenceless_set : 'a t -> int -> 'a -> unit - (** [unsafe_fenceless_set atomic_array index value] writes the given [value] - to the specified [index] of the [atomic_array]. - - ⚠️ The write is {i relaxed} and may be reordered with respect to other - reads and (non-initializing) writes in program order. - - ⚠️ No bounds checking is performed. *) - - val unsafe_compare_and_set : 'a t -> int -> 'a -> 'a -> bool - (** [unsafe_compare_and_set atomic_array index before after] atomically - updates the specified [index] of the [atomic_array] to the [after] value - in case it had the [before] value and returns a boolean indicating whether - that was the case. This operation is {i sequentially consistent} and may - not be reordered with respect to other reads and writes in program order. - - ⚠️ No bounds checking is performed. *) -end - -(** {1 Avoiding contention} *) - -val instantaneous_domain_index : unit -> int -(** [instantaneous_domain_index ()] potentially (re)allocates and returns a - non-negative integer "index" for the current domain. The indices are - guaranteed to be unique among the domains that exist at a point in time. - Each call of [instantaneous_domain_index ()] may return a different index. - - The intention is that the returned value can be used as an index into a - contention avoiding parallelism safe data structure. For example, a naïve - scalable increment of one counter from an array of counters could be done as - follows: - - {[ - let incr counters = - (* Assuming length of [counters] is a power of two and larger than - the number of domains. *) - let mask = Array.length counters - 1 in - let index = instantaneous_domain_index () in - Atomic.incr counters.(index land mask) - ]} - - The implementation ensures that the indices are allocated as densely as - possible at any given moment. This should allow allocating as many counters - as needed and essentially eliminate contention. - - On OCaml 4 [instantaneous_domain_index ()] will always return [0]. *) diff --git a/src/dscheck/transparent_atomic_dscheck.ml b/src/dscheck/transparent_atomic_dscheck.ml deleted file mode 100644 index d01086d..0000000 --- a/src/dscheck/transparent_atomic_dscheck.ml +++ /dev/null @@ -1,4 +0,0 @@ -include Dscheck.TracedAtomic - -let fenceless_get = get -let fenceless_set = set From 35b0b1ebc7dafae191c8c8af22afa963772d7ae2 Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Tue, 2 Jul 2024 16:42:48 +0200 Subject: [PATCH 4/9] Back to the classic multicore-magic-dscheck way as cyclic deps are a real thing apparently. --- dune-project | 3 +- src/dscheck/dune | 4 +- ...re_magic.ml => multicore_magic_dscheck.ml} | 11 +++++- src/dscheck/multicore_magic_dscheck.mli | 39 +++++++++++++++++++ 4 files changed, 51 insertions(+), 6 deletions(-) rename src/dscheck/{multicore_magic.ml => multicore_magic_dscheck.ml} (80%) create mode 100644 src/dscheck/multicore_magic_dscheck.mli diff --git a/dune-project b/dune-project index 3501c51..d2d9438 100644 --- a/dune-project +++ b/dune-project @@ -44,7 +44,8 @@ (package (name multicore-magic-dscheck) - (synopsis "Low-level multiscore utilities for OCaml") + (synopsis + "A implementation of multicore-magic API using the atomic module of DScheck to make DScheck tests possible in libraries using multicore-magic.") (depends (ocaml (>= 4.12.0)) diff --git a/src/dscheck/dune b/src/dscheck/dune index b649616..86455e9 100644 --- a/src/dscheck/dune +++ b/src/dscheck/dune @@ -1,6 +1,4 @@ (library (name multicore_magic_dscheck) (public_name multicore-magic-dscheck) - (wrapped false) - (root_module Deps) - (libraries multicore-magic dscheck)) \ No newline at end of file + (libraries multicore-magic dscheck)) diff --git a/src/dscheck/multicore_magic.ml b/src/dscheck/multicore_magic_dscheck.ml similarity index 80% rename from src/dscheck/multicore_magic.ml rename to src/dscheck/multicore_magic_dscheck.ml index 600a2ae..b47354a 100644 --- a/src/dscheck/multicore_magic.ml +++ b/src/dscheck/multicore_magic_dscheck.ml @@ -1,9 +1,14 @@ -include Deps.Multicore_magic -module Atomic = Deps.Dscheck.TracedAtomic +module Atomic = Dscheck.TracedAtomic + +let copy_as_padded = Fun.id +let make_padded_array = Array.make +let length_of_padded_array = Array.length +let length_of_padded_array_minus_1 xs = Array.length xs - 1 module Transparent_atomic = struct include Atomic + let make_contended = make let fenceless_get = get let fenceless_set = set end @@ -35,3 +40,5 @@ module Atomic_array = struct let[@inline] unsafe_compare_and_set xs i b a = Atomic.compare_and_set (at xs i) b a end + +let instantaneous_domain_index () = 0 diff --git a/src/dscheck/multicore_magic_dscheck.mli b/src/dscheck/multicore_magic_dscheck.mli new file mode 100644 index 0000000..faa3163 --- /dev/null +++ b/src/dscheck/multicore_magic_dscheck.mli @@ -0,0 +1,39 @@ +module Atomic = Dscheck.TracedAtomic + +val copy_as_padded : 'a -> 'a +val make_padded_array : int -> 'a -> 'a array +val length_of_padded_array : 'a array -> int +val length_of_padded_array_minus_1 : 'a array -> int +val fenceless_get : 'a Atomic.t -> 'a +val fenceless_set : 'a Atomic.t -> 'a -> unit +val fence : int Atomic.t -> unit + +module Transparent_atomic : sig + type !'a t = 'a Atomic.t + + val make : 'a -> 'a t + val make_contended : 'a -> 'a t + val get : 'a t -> 'a + val fenceless_get : 'a t -> 'a + val set : 'a t -> 'a -> unit + val fenceless_set : 'a t -> 'a -> unit + val exchange : 'a t -> 'a -> 'a + val compare_and_set : 'a t -> 'a -> 'a -> bool + val fetch_and_add : int t -> int -> int + val incr : int t -> unit + val decr : int t -> unit +end + +module Atomic_array : sig + type !'a t + + val make : int -> 'a -> 'a t + val of_array : 'a array -> 'a t + val init : int -> (int -> 'a) -> 'a t + val length : 'a t -> int + val unsafe_fenceless_get : 'a t -> int -> 'a + val unsafe_fenceless_set : 'a t -> int -> 'a -> unit + val unsafe_compare_and_set : 'a t -> int -> 'a -> 'a -> bool +end + +val instantaneous_domain_index : unit -> int From 49474cd1795d2f105a8608cb7f849b573bd90bf5 Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Tue, 2 Jul 2024 17:07:45 +0200 Subject: [PATCH 5/9] Maybe a solution with the -open dune flags ?. --- dune-project | 2 +- multicore-magic-dscheck.opam | 3 ++- src/dscheck/{multicore_magic_dscheck.ml => multicore_magic.ml} | 0 .../{multicore_magic_dscheck.mli => multicore_magic.mli} | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename src/dscheck/{multicore_magic_dscheck.ml => multicore_magic.ml} (100%) rename src/dscheck/{multicore_magic_dscheck.mli => multicore_magic.mli} (100%) diff --git a/dune-project b/dune-project index d2d9438..651e739 100644 --- a/dune-project +++ b/dune-project @@ -45,7 +45,7 @@ (package (name multicore-magic-dscheck) (synopsis - "A implementation of multicore-magic API using the atomic module of DScheck to make DScheck tests possible in libraries using multicore-magic.") + "A implementation of multicore-magic API using the atomic module of DScheck to make DScheck tests possible in libraries using multicore-magic") (depends (ocaml (>= 4.12.0)) diff --git a/multicore-magic-dscheck.opam b/multicore-magic-dscheck.opam index 06c13fb..c4557e2 100644 --- a/multicore-magic-dscheck.opam +++ b/multicore-magic-dscheck.opam @@ -1,6 +1,7 @@ # This file is generated by dune, edit dune-project instead opam-version: "2.0" -synopsis: "Low-level multiscore utilities for OCaml" +synopsis: + "A implementation of multicore-magic API using the atomic module of DScheck to make DScheck tests possible in libraries using multicore-magic." maintainer: ["Vesa Karvonen "] authors: ["Vesa Karvonen "] license: "ISC" diff --git a/src/dscheck/multicore_magic_dscheck.ml b/src/dscheck/multicore_magic.ml similarity index 100% rename from src/dscheck/multicore_magic_dscheck.ml rename to src/dscheck/multicore_magic.ml diff --git a/src/dscheck/multicore_magic_dscheck.mli b/src/dscheck/multicore_magic.mli similarity index 100% rename from src/dscheck/multicore_magic_dscheck.mli rename to src/dscheck/multicore_magic.mli From 36b8f5c646703b72c1ca9a098254f7e6209241cd Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Tue, 2 Jul 2024 17:51:27 +0200 Subject: [PATCH 6/9] dscheck deps. --- dune-project | 3 ++- multicore-magic-dscheck.opam | 7 ++++--- multicore-magic-dscheck.opam.template | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dune-project b/dune-project index 651e739..7b64a9a 100644 --- a/dune-project +++ b/dune-project @@ -51,4 +51,5 @@ (>= 4.12.0)) ;; Test dependencies (multicore-magic - (>= 2.2.0)))) + (>= 2.2.0)) + (dscheck (= dev)))) diff --git a/multicore-magic-dscheck.opam b/multicore-magic-dscheck.opam index c4557e2..2b2727d 100644 --- a/multicore-magic-dscheck.opam +++ b/multicore-magic-dscheck.opam @@ -1,7 +1,7 @@ # This file is generated by dune, edit dune-project instead opam-version: "2.0" synopsis: - "A implementation of multicore-magic API using the atomic module of DScheck to make DScheck tests possible in libraries using multicore-magic." + "A implementation of multicore-magic API using the atomic module of DScheck to make DScheck tests possible in libraries using multicore-magic" maintainer: ["Vesa Karvonen "] authors: ["Vesa Karvonen "] license: "ISC" @@ -11,6 +11,7 @@ depends: [ "dune" {>= "3.14"} "ocaml" {>= "4.12.0"} "multicore-magic" {>= "2.2.0"} + "dscheck" {= "dev"} "odoc" {with-doc} ] build: [ @@ -30,6 +31,6 @@ build: [ dev-repo: "git+https://github.com/ocaml-multicore/multicore-magic.git" pin-depends: [ - "dscheck.~dev" - "git+https://github.com/lyrm/dscheck.git#make_contended" + "dscheck.dev" + "git+https://github.com/lyrm/dscheck.git#d325890d36267dc0d4d76cbd38860ee67cee731e" ] \ No newline at end of file diff --git a/multicore-magic-dscheck.opam.template b/multicore-magic-dscheck.opam.template index 589e962..3d6318b 100644 --- a/multicore-magic-dscheck.opam.template +++ b/multicore-magic-dscheck.opam.template @@ -1,5 +1,5 @@ pin-depends: [ - "dscheck.~dev" - "git+https://github.com/lyrm/dscheck.git#make_contended" + "dscheck.dev" + "git+https://github.com/lyrm/dscheck.git#d325890d36267dc0d4d76cbd38860ee67cee731e" ] \ No newline at end of file From c8c980d66bb98468f2da8a833e2a1b635b711329 Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Thu, 4 Jul 2024 10:54:51 +0200 Subject: [PATCH 7/9] Make it work with ocaml < 5.0 --- dune-project | 4 +++- src/dscheck/dune | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dune-project b/dune-project index 7b64a9a..4070162 100644 --- a/dune-project +++ b/dune-project @@ -46,10 +46,12 @@ (name multicore-magic-dscheck) (synopsis "A implementation of multicore-magic API using the atomic module of DScheck to make DScheck tests possible in libraries using multicore-magic") + (allow_empty) (depends (ocaml (>= 4.12.0)) ;; Test dependencies (multicore-magic (>= 2.2.0)) - (dscheck (= dev)))) + (dscheck + (= dev)))) diff --git a/src/dscheck/dune b/src/dscheck/dune index 86455e9..ca2144d 100644 --- a/src/dscheck/dune +++ b/src/dscheck/dune @@ -1,4 +1,6 @@ (library (name multicore_magic_dscheck) (public_name multicore-magic-dscheck) - (libraries multicore-magic dscheck)) + (libraries multicore-magic dscheck) + (enabled_if + (>= %{ocaml_version} 5))) From e7f16b2e0514f58c2083f715750403a9fa842c55 Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Thu, 4 Jul 2024 16:20:51 +0200 Subject: [PATCH 8/9] Apply review comments. --- dune-project | 5 +---- multicore-magic-dscheck.opam | 3 +-- src/dscheck/dune | 2 +- src/dscheck/multicore_magic.ml | 20 +++++++------------- src/dscheck/multicore_magic.mli | 1 + 5 files changed, 11 insertions(+), 20 deletions(-) diff --git a/dune-project b/dune-project index 4070162..66ab1bc 100644 --- a/dune-project +++ b/dune-project @@ -50,8 +50,5 @@ (depends (ocaml (>= 4.12.0)) - ;; Test dependencies - (multicore-magic - (>= 2.2.0)) (dscheck - (= dev)))) + (>= 0.5.0)))) diff --git a/multicore-magic-dscheck.opam b/multicore-magic-dscheck.opam index 2b2727d..32bc643 100644 --- a/multicore-magic-dscheck.opam +++ b/multicore-magic-dscheck.opam @@ -10,8 +10,7 @@ bug-reports: "https://github.com/ocaml-multicore/multicore-magic/issues" depends: [ "dune" {>= "3.14"} "ocaml" {>= "4.12.0"} - "multicore-magic" {>= "2.2.0"} - "dscheck" {= "dev"} + "dscheck" {>= "0.5.0"} "odoc" {with-doc} ] build: [ diff --git a/src/dscheck/dune b/src/dscheck/dune index ca2144d..b4de21a 100644 --- a/src/dscheck/dune +++ b/src/dscheck/dune @@ -1,6 +1,6 @@ (library (name multicore_magic_dscheck) (public_name multicore-magic-dscheck) - (libraries multicore-magic dscheck) + (libraries dscheck) (enabled_if (>= %{ocaml_version} 5))) diff --git a/src/dscheck/multicore_magic.ml b/src/dscheck/multicore_magic.ml index b47354a..0af3718 100644 --- a/src/dscheck/multicore_magic.ml +++ b/src/dscheck/multicore_magic.ml @@ -1,6 +1,7 @@ module Atomic = Dscheck.TracedAtomic let copy_as_padded = Fun.id +let copy_as ?padded:_ x = x let make_padded_array = Array.make let length_of_padded_array = Array.length let length_of_padded_array_minus_1 xs = Array.length xs - 1 @@ -13,29 +14,22 @@ module Transparent_atomic = struct let fenceless_set = set end -let[@inline] fenceless_get (atomic : 'a Atomic.t) = - !(Sys.opaque_identity (Obj.magic atomic : 'a ref)) - -let[@inline] fenceless_set (atomic : 'a Atomic.t) value = - (Obj.magic atomic : 'a ref) := value - +let fenceless_get = Atomic.get +let fenceless_set = Atomic.set let[@inline] fence atomic = Atomic.fetch_and_add atomic 0 |> ignore module Atomic_array = struct type 'a t = 'a Atomic.t array - let[@inline] at (type a) (xs : a t) i : a Atomic.t = - (* ['a t] does not contain [float]s. *) - Obj.magic (Array.unsafe_get (Obj.magic xs : a ref array) i) - + let[@inline] at (xs : 'a t) i : 'a Atomic.t = Array.get xs i let[@inline] make n v = Array.init n @@ fun _ -> Atomic.make v let[@inline] init n fn = Array.init n @@ fun i -> Atomic.make (fn i) - let[@inline] of_array xs = init (Array.length xs) (Array.unsafe_get xs) + let[@inline] of_array xs = init (Array.length xs) (Array.get xs) external length : 'a array -> int = "%array_length" - let[@inline] unsafe_fenceless_set xs i v = Obj.magic (at xs i) := v - let[@inline] unsafe_fenceless_get xs i = !(Obj.magic (at xs i)) + let unsafe_fenceless_set xs i v = Atomic.set xs.(i) v + let unsafe_fenceless_get xs i = Atomic.get xs.(i) let[@inline] unsafe_compare_and_set xs i b a = Atomic.compare_and_set (at xs i) b a diff --git a/src/dscheck/multicore_magic.mli b/src/dscheck/multicore_magic.mli index faa3163..fc7e9b2 100644 --- a/src/dscheck/multicore_magic.mli +++ b/src/dscheck/multicore_magic.mli @@ -1,6 +1,7 @@ module Atomic = Dscheck.TracedAtomic val copy_as_padded : 'a -> 'a +val copy_as : ?padded:bool -> 'a -> 'a val make_padded_array : int -> 'a -> 'a array val length_of_padded_array : 'a array -> int val length_of_padded_array_minus_1 : 'a array -> int From 1a24f5917bc69261f0a9ecda07a2a217b3db1812 Mon Sep 17 00:00:00 2001 From: Carine Morel Date: Tue, 9 Jul 2024 15:00:34 +0200 Subject: [PATCH 9/9] Remove dscheck pin --- multicore-magic-dscheck.opam | 5 ----- multicore-magic-dscheck.opam.template | 5 ----- test/dune | 1 + 3 files changed, 1 insertion(+), 10 deletions(-) delete mode 100644 multicore-magic-dscheck.opam.template diff --git a/multicore-magic-dscheck.opam b/multicore-magic-dscheck.opam index 32bc643..016751d 100644 --- a/multicore-magic-dscheck.opam +++ b/multicore-magic-dscheck.opam @@ -28,8 +28,3 @@ build: [ ] ] dev-repo: "git+https://github.com/ocaml-multicore/multicore-magic.git" -pin-depends: - [ - "dscheck.dev" - "git+https://github.com/lyrm/dscheck.git#d325890d36267dc0d4d76cbd38860ee67cee731e" - ] \ No newline at end of file diff --git a/multicore-magic-dscheck.opam.template b/multicore-magic-dscheck.opam.template deleted file mode 100644 index 3d6318b..0000000 --- a/multicore-magic-dscheck.opam.template +++ /dev/null @@ -1,5 +0,0 @@ -pin-depends: - [ - "dscheck.dev" - "git+https://github.com/lyrm/dscheck.git#d325890d36267dc0d4d76cbd38860ee67cee731e" - ] \ No newline at end of file diff --git a/test/dune b/test/dune index eb86f37..69509fb 100644 --- a/test/dune +++ b/test/dune @@ -1,4 +1,5 @@ (test (name Multicore_magic_test) + (package multicore-magic) (modules Multicore_magic_test) (libraries Multicore_magic alcotest domain_shims threads.posix unix))