Module Rig.Claim
Claims on memory.
A function may donate an input that dies with the call, such as an array operation's. A donation is safe only if nothing else reads the memory, and claims record what does. A claim lasts as long as its holder keeps it, and every view and borrow of an allocation shares the allocation's claims.
- A read claim marks an allocation as being read. The host holds one during
Buffer.readandBuffer.write, andClaim.with_on every buffer it is given. A hold (Claim.hold) is a read claim that lasts untilClaim.releaseends it or its value is unreachable, and that no consumption ends.submitholds one on every allocation the work uses until it returns, and a copy, blit or fill (Copying) on its buffers until it returns. Work on a device holds none, since the stamps order a write in place after that work. - Only
Claim.with_holds memory exclusive, for an input its call donates, until it consumes the memory. Every other claim is a read claim. - Memory that something outside rig can read or write is outside the claims and never exclusive. This holds for memory
Buffer.of_bigarrayandBuffer.of_iomake, memoryBuffer.exportexported, memoryBuffer.dmadescribed or refused, and memoryClaim.sharemarked. Memory that admits onlyRead(Buffer.access) is never exclusive either. Claim.consumeconsumes an allocation. A call that uses a dead buffer's bytes raisesInvalid_argumentwith the reason given to the consumption. Consuming frees nothing, and the memory lives on in the buffer thatClaim.consumereturns. Where the memory was held exclusive, that buffer admits writes, its holder may write it in place, and no holder of a buffer made before the consumption sees the write. Code that the holder hands the new buffer sees the write, so the holder must order that code's accesses as for a buffer it made. Otherwise another claim reads the memory, and the buffer admits onlyRead(Buffer.access), so the bytes that claim reads are never written through it.
Taking a claim never waits. A read claim on memory held exclusive raises Invalid_argument, and a donated input whose memory another claim holds gets a read claim only.
Which inputs a function donates is part of that function's contract, and the function decides alone. A caller that passes a buffer to such a function and still needs its bytes holds a read claim on it across the call. The donation then stays a read claim, the function writes its result elsewhere, and the bytes stay as they were. Whether the caller's buffer stays live is the function's contract too: a function that consumes its input kills the buffer under any claim.
A host reader of memory that another domain may donate uses Buffer.read, which claims before it waits, since a claim taken after Buffer.wait returns leaves a moment in which the donation writes the memory. Host code over several buffers waits for them (Buffer.wait) inside a Claim.with_ that reads them. Where no other domain may donate the memory, the reader may use Buffer.wait.
A prepared submission holds its fixed memory and the buffers its parts name for its life (Submission.make). It claims the buffers of a run only while a submit runs. A donation between two submits can consume one of them, and the next submit with it then raises Invalid_argument for the dead buffer. A library that keeps buffers it does not own over its submits holds them (Claim.hold) for as long as it keeps them, or passes their memory to Claim.share.
A donor takes its inputs with Claim.with_. It consumes an input it holds exclusive before it uses it, since every call that touches the bytes of memory held exclusive raises Invalid_argument. While its function runs, the donor uses the buffer that Claim.consume returns as a buffer it made. It may write it from the host, copy into it, submit work on it, export it or share it. A donor of one input, or of an input whose buffers lie on several devices, consumes it only where it holds every buffer exclusive:
Claim.with_ ~read:[] ~donate:bs (fun c -> if List.for_all (Claim.exclusive c) bs then Some (List.map (Claim.consume c ~why) bs) else None)
The buffers Claim.consume returns may be written in place after Claim.with_ returns.
share b puts b's memory outside the claims for good, through every view and borrow. A library calls it before it hands out a value over the memory that its own claims do not track, so that no donation writes what that value reads. Buffer.export does the same to the memory it exports.
Raises Invalid_argument if b is dead, or a with_ holds its memory exclusive.
with_ ~read ~donate f takes claims c and is f c. It takes a read claim on the memory of every buffer in read and donate. The claim of a buffer of donate becomes exclusive if the buffer Buffer.spans its memory and no other claim holds that memory. An input whose buffers lie on several devices, such as the shards of an array split across GPUs, is donated when each of them is exclusive.
Every claim is released when f returns or raises, and c then holds nothing. A consume on another domain that overlaps the return completes before the release or raises Invalid_argument. Any other use of c on another domain must finish before f returns. rig does not detect one that overlaps the return, which races the memory's next holder.
Raises Invalid_argument before calling f, releasing what it took, if a buffer is dead, an allocation is held exclusive, or a buffer of donate overlaps another of read or donate.
consume c ~why b consumes b's memory with the reason why, so b and every buffer made over that memory until then are dead. It is a live buffer over the same memory, the only one. It admits writes if c held the memory exclusive (exclusive), and only Read otherwise (Buffer.access). c no longer holds the memory exclusive, and the buffer is claimed as any other.
Raises Invalid_argument if c's with_ has returned or raised, c does not claim b's memory, b is dead or does not Buffer.spans its memory, or a hold holds the memory.
consumable c b is true iff c's with_ is running, c claims b's memory, b is live and Buffer.spans it, and no hold holds it: consume c b then succeeds, unless another domain holds the memory meanwhile.
type holdThe type for claims that keep buffers in use until they are released or their value is unreachable.
hold bs takes a read claim on the memory of each of bs that no donation writes and no consume ends (consumable). The claims last until release ends them or, once the hold is unreachable, until the first call that moves collected memory after its collection (Reclamation). A library that keeps buffers it does not own over calls, such as a linked program's inputs, holds them.
Raises Invalid_argument, holding none of bs, if a buffer is dead or its memory is held exclusive.
release h ends h's claims now, so a with_ that starts after it returns finds none of them. A later release of h, and h's collection, then end nothing. It takes no lock, so it may run on any domain and in a callback inside a lock (Domains). If an asynchronous exception interrupts it, the claims it has not ended last until a later release or h's collection.