Skip to content

Register operations

Register operations describe logical vector and mask values within a non-nested pl.reg_scope() on A5 (Ascend950). The frontend accepts an unspecialized InCore function; after ExpandMixedKernel, a surviving region must be in an AIV function. An explicitly configured non-A5 target is rejected. Without a selected target, target validation is deferred until compilation selects one.

An explicit InCore scope in an Opaque, Orchestration, or Graph function also provides a device context before outlining, including when a graph scope is an ancestor. RegScope directly in orchestration code remains invalid. Orchestration scopes inside RegScope remain forbidden; existing InCore scope rules are unchanged.

@pl.function(type=pl.FunctionType.InCore)
def kernel(
    src: pl.Tile[[128], pl.FP32, pl.MemorySpace.Vec],
    dst: pl.Tile[[128], pl.FP32, pl.MemorySpace.Vec],
):
    with pl.reg_scope():
        x = pl.reg.load(src, offset=0, lanes=64)
        mask = pl.reg.active_mask(32, lanes=64)
        y = pl.reg.add(x, x, mask=mask, pmode="zero")
        out = pl.reg.store(dst, offset=0, value=y, mask=mask)
    z = pl.tile.add(out, out)

The scope has no captures argument, result list or yield. External Tile and read-only Scalar values are implicit inputs. Reg values and local Scalars belong to their defining region and cannot escape through subsequent uses, function returns, loop results or assignments to an outer variable. Tile inputs originate outside the region; only reg.store creates a Tile result inside it. Wrapping a local value in a tuple does not permit it to escape; nested tuple types are checked at function boundaries too.

Every register operation is a Call assigned with AssignStmt. A bare store is rejected. Store operand order is (tile, offset, value, mask?); its result has exactly the destination Tile type and reuses operand 0 storage. This is an alias, not a snapshot or a new allocation. Store does not enlarge valid_shape.

P0 schemas

Operations Inputs Result
load Vec Tile, element offset; lanes VecReg
store Vec Tile, element offset, VecReg, optional MaskReg destination Tile
broadcast, index_gen Scalar; lanes, dtype VecReg
full_mask lanes MaskReg
active_mask static active lane count; lanes MaskReg
add/sub/mul/div/minimum/maximum same-type VecRegs, optional MaskReg; pmode VecReg
convert VecReg; dtype, optional rounding, saturate VecReg
compare same-type VecRegs, seed MaskReg; predicate MaskReg
select MaskReg, same-type true/false VecRegs VecReg
mask_and/or/xor/not same-lane MaskRegs MaskReg

All lanes are static positive integers. Vec operands require identical dtype, signedness and lane count; there is no promotion or lane broadcasting. Masks use BOOL and must have the same lane count as controlled data. Arithmetic pmode is "zero"; "merge" is unsupported. index_gen order is "ascending". Comparison predicates are "eq", "ne", "lt", "le", "gt", "ge".

The common dtype set is FP16/BF16/FP32 and signed/unsigned 8/16/32-bit integers. mul excludes 8-bit integers; div accepts FP16/FP32 only; compare accepts FP16/FP32/INT32/UINT32; select excludes BF16. Existing Scalar SSA values must match the requested generator dtype. DSL literals are normalized to that dtype; raw IR and Builder literals must already have the requested dtype.

Conversion pairs are FP16→FP32, FP32→FP16, FP32→INT32, INT32→FP32, INT8→FP16, INT8→INT16, INT16→INT32, UINT8→UINT16, UINT16→UINT32 and INT32→INT8. Other pairs are rejected. Same-dtype DSL conversion returns the original value when no attributes are supplied. FP32→FP16 requires explicit saturate and accepts rounding R/A/H/Z (default R); FP32→INT32 requires explicit saturate and accepts R/A/F/C/Z (default Z). INT32→INT8 requires saturate=False and no rounding. Other pairs accept neither attribute.

Memory checks and effects

Offsets count elements and have INDEX dtype. Static offsets must be nonnegative and the entire interval [offset, offset + lanes) must fit the Tile capacity, even for masked stores. Loads additionally require the whole read interval to lie in initialized valid_shape; a mask on a later operation cannot protect an uninitialized load. One-dimensional Tiles are directly supported. Multidimensional Tiles require an explicit contiguous row-major View; padded, boxed or compact layouts are rejected. Static View offsets contribute to absolute effect ranges.

ir.get_reg_op_spec(name) reports the scope requirement and VMI emitter. ir.get_reg_memory_effect(call) reports the target, root allocation when known, Read/Write access, absolute element interval and optional mask. Dynamic offsets produce unknown endpoints, never an empty effect. They do not prove bounds and require later analysis before emission. A dynamic offset load requires complete valid-shape coverage. A mask is preserved without claiming whole-buffer initialization or disjointness.

Builder

Python uses with ib.reg_scope():, ib.emit_reg(name, op_name, args, kwargs) and ib.emit_reg_store(name, tile, value, offset=0, mask=None). C++ exposes BeginRegScope, EmitReg, EmitRegStore and EndRegScope. Both use the same schema deduction as the DSL; completing a function validates region ownership. RegScopeValid also validates manually constructed functions through the normal property verifier. Final AIV placement is checked by MixedKernelExpanded.

The Python text syntax accepts no scope metadata. Printing a manually constructed RegScope with a nonempty name_hint or attrs raises an error rather than losing those fields; binary serialization preserves them. Empty scopes print as a pass block.