Module Tolk.Uop_weak
Committing weak data types to widths.
A node of a weak type (Dtype.t.Weak_int, Dtype.t.Weak_float) holds a value whose width is not decided yet: literals, loop variables and index arithmetic start weak and take the width of what they meet. Before a kernel is rendered, every weak node gets a width, decided in this order:
- by a peer: an operation whose sources mix weak and committed types commits the weak ones at their least upper type, and a store commits its value at its destination's type (
pm_commit_weak); - by a cast: a cast to a committed type over a weak expression of the same kind states the width the expression computes at, widened to what its bounds and its sources' bounds need, never narrowed (
pm_commit_weak); - by default, for the rest (
pm_lower_weak): a weak float takesDtype.default_float, and a weak integer the first ofDtype.t.Int32,Dtype.t.Int64andDtype.t.Uint64that holds its bounds (Ops.commit_dtype).
A weak constant whose consumer derives its type from its other sources stays a bare literal, so rules keyed on constant values keep matching it; pm_cast_const states the width of every remaining literal last.
commit_weak_consts u dt is u with each of its sources that is a weak constant converted to dt (Ops.ccast), and u if dt is None. A rewrite that builds a node from weak literals next to an operand of type dt states their width with it.
val pm_commit_weak : (unit, Ops.t) Ops.Pattern_matcher.tpm_commit_weak commits weak sources whose width something states:
- a broadcastable node (
Op.Set.broadcastable) with a weak source, whose sources' least upper typedtis committed, converts its weak sources todt. A weak constant stays a weak literal, its value converted todt(Dtype.const), when the node's type does not depend on it: the other sources already derive a committed type for the node. A shift's count is a number of bits, so it never wraps atdt: a count ofDtype.bitsizedtor more shifts every bit out, so a weak count saturates at that width. An expression whose boundsdtdoes not hold saturates at its own committed type (Ops.commit_dtype) and then converts todt. An expression whose bounds no 64-bit integer holds (Ops.fits_no_int) is the width if its bounds are all past the width, and otherwise raisesInvalid_argumentwhen the rule runs: wrapped, it would shift by the wrong number. An integer power's exponent is a number too: it stays weak, its value whole; - a
Op.t.Storeof a weak value converts it to the destination's type; - a cast to a committed type
dtover a weak arithmetic nodeuofdt's kind commitsu's weak sources at the least upper type ofdt,u's committed type and each weak source's committed type, a shift's count and an integer power's exponent apart, and casts the result todt. Where that type is a float, as it is for a 64-bit unsigneddtover a signed width, they commit atDtype.t.Int64instead, or atDtype.t.Uint64where one of the committed types is: an integer cast computes in integers.
It runs with every rewrite that can build a weak constant, and must reach its fixed point before pm_lower_weak gives defaults.
val pm_lower_weak : (unit, Ops.t) Ops.Pattern_matcher.tpm_lower_weak gives every weak node that nothing committed its default width (Ops.commit_dtype, with Dtype.t.Int32 as the default integer):
- a node's sources lose the weak casts over them, which only restated a width the node restates; a weak integer cast of a boolean or a float is a conversion, and commits to its default instead;
- a weak constant whose consumer derives no committed type from its sources takes its default;
- once no source is a weak expression, a unary, binary,
Op.t.Where,Op.t.Range,Op.t.StackorOp.t.Specialnode computes at one committed type: for a binary node, the least upper type of its sources and of its own default, and otherwise the type it derives. Its sources are converted to it, and the node is cast back to its own type, which its consumers absorb in turn; - two stacked weak casts over a committed value are two conversions, each at the default of its kind;
- a weak integer scalar parameter or buffer is stored at its default, cast back to
Dtype.t.Weak_int; - an
Op.t.IndexorOp.t.Shrinkwhose index is anDtype.t.Int64guarded by a condition, andOps.invalidelsewhere, indexes through anDtype.t.Int32when the storage's greatest index fits one: the values outside the guard are discarded.
val pm_cast_const : (unit, Ops.t) Ops.Pattern_matcher.tpm_cast_const states the width of every constant still weak, at each consumer: a broadcastable consumer that derives a committed type from its sources converts its weak constants to their least upper type, and every remaining bare constant becomes a cast of the literal to its committed type (Ops.cconst), booleans included. Ops.invalid stays bare.