A native fast path for repeatedly executing a compiled program on inputs of the same signature, intended to be used in anvl. It owns an executable cache keyed on the inputs' structure and abstract values, calling back into R to compile only on a cache miss.
dispatcher() creates a dispatcher for one program family;
dispatch() runs a call through it and returns the call's result.
Usage
dispatcher(
capacity,
compile,
static = character(),
backend = "pjrt",
move_inputs = FALSE,
default_device = NULL,
extractor = NULL
)Arguments
- capacity
(
integer(1))
Maximum number of compiled executables to cache.- compile
(
function)
Cache-miss callback, called ascompile(info).infocarries what the dispatch already derived from the call, so that the callback need not classify the inputs a second time:args: the dispatched argument list,in_tree: itsRTree(seebuild_tree),leaves: the flat leaf list (seeflatten),is_static: alogical()mask overleaves,avals: per leaf,NULLif static, else thelist(dtype, shape, ambiguous)the cache key was built from.dtypeis a canonical dtype string ("f32","i64", ...),shapeaninteger(), empty for a scalar,default_device: the device this call resolved because no array input named one – the device the cache key was built on, socompilemust compile for it rather than resolve a default of its own.NULLwhen an array named the device, or undermove_inputs.
For
backend = "pjrt"it must return a named list with:exec: apjrt_compiled executable,client,device: thepjrt_clientand the device the entry is compiled for,out_tree: theRTreeof the outputs (seebuild_tree),out_avals: one aval per output leaf ofout_tree, each alist(dtype = <string>, shape = <integer>, ambiguous = <logical(1)>)(ambiguousis optional and defaults toFALSE). The outputs are wrapped from these.const_arrays(optional): buffers prepended to the inputs,phantom_specs(optional): a list oflist(dtype = <string>, shape = <integer>)donation-output buffers to allocate fresh per call.
For any other
backendit must return a named list with:r_fun: a function called with the list of the call's dynamic leaves, in order and with an array leaf contributing its$data, returning the call's finished value. Static leaves are not passed: they are constants of the closurecompilejust built, and a cache hit already proves the call's statics areidentical()to the ones it was built from.
- static
(
character())
Names of top-level arguments that are static (not arrays). Static values are part of the cache key and are excluded from execution. Defaults to none. They are compared withidentical(num.eq = FALSE), i.e. numbers compare bitwise, which is what keeps abit64::integer64NA– stored as the bit pattern of-0– from sharing a cache entry with0.- backend
(
character(1))
The$backendtag every"AnvlArray"input must carry, and the tag stamped on wrapped outputs. It also selects the execution engine (see Backends); anvl's quickr backend passes"quickr".- move_inputs
(
logical(1))
IfTRUE, each cache entry has a target device – thedeviceitscompilecall returned – and the engine places every input on it at execute time; the cache key then carries no device, so inputs may arrive from any device. DefaultFALSE: the first array's device is the call's device, and a conflicting input is an error.It is a policy flag, not a device: which device an entry targets is the
compilecallback's business, and need not be the same for every entry (anvl'sjit(device = )fixes one up front, itsdevice_arg()derives one per static argument value).Placing an input is the engine's business, since only it knows what
$dataholds. Withbackend = "pjrt"an input living elsewhere is copied to the entry's device. With any other backend pjrt does nothing, sor_funmust place its own inputs – it receives only their$data, not their$device, so the placing has to be idempotent.- default_device
(
function|NULL)
Called with no arguments to get the backend's current default device, whenever a call has no array input to read a device from. Its result is part of the cache key, so an entry compiled under one default device is never served after the default changes. Required unlessmove_inputs = TRUE, which fixes the device per entry.Devices are compared by object identity first and by
identical()as a fallback, so equal-but-distinct device objects count as one device. Interning them (one object per device, alive for the session, asas_pjrt_device()does) is therefore not required, but is recommended: an interned device resolves in one pointer comparison, and every distinct object a backend hands out stays alive for the dispatcher's lifetime.- extractor
(
function|NULL)
Reads a non-"pjrt"array's metadata via the backend's accessors, called asextractor(leaf)and returninglist(aval = list(dtype, shape, ambiguous), device, backend)–dtypea tengenDataType,shapeaninteger(). Required for any backend other than"pjrt"; ignored for"pjrt"(see Backends).
Details
Each dispatch() call flattens the inputs and builds a cache key: a
dynamic leaf contributes its dtype, shape and ambiguous flag, a static leaf
its value (compared with identical()). On a hit the cached executable runs
immediately; on a miss compile is called to produce a new cache entry.
Inputs are validated before the cache is probed, and a rejection names the
offending argument by its path in the argument tree. An input must be an
"AnvlArray" of the dispatcher's backend, a length-1 atomic scalar, or an
is.array() value. compile is therefore never asked to validate: it is
called only on a cache miss, and only for inputs already known to be
executable.
Backends
The backend selects the execution engine, and everything backend-specific
sits behind it:
backend = "pjrt"executes a compiled PJRT executable natively: array inputs contribute their$databuffer, bare R literals and arrays are uploaded with the same dtype defaults aspjrt_scalar()/pjrt_buffer(), and the outputs are wrapped back into"AnvlArray"s – lists of$data,$dtype,$shape,$device,$ambiguousand$backend– and re-nested viaout_tree, all without leaving C++.any other
backendcalls the compiled R closurecompilereturned, which returns the call's finished value. Execution, output wrapping and input placement therefore stay under the backend's control. This is the path for any non-PJRT backend (e.g. anvl's"quickr").
Of an array input, only $data is ever assumed: for "pjrt" its dtype, shape
and device are read off the PJRTBuffer directly, and for any other backend
they come from extractor. A backend is free to store them as fields or to
compute them on demand.