Skip to content

[RFC][Tile] Lower aligned Tile Array partitions as indexed region/session tokens #74

Description

@zhoubot

Semantic naming update: TPARTVIEW/TASSEMBLY is the active model; see #74 (comment). The older as_subtiles / public commit() spelling below is superseded.

Status

Compiler RFC. Proposed only; no implementation exists.

This is the LLVM companion for:

PTO range implementation remains blocked on PTO-ISA/pto-spec#166, which must reconcile the Local B.IOT SizeCode-12 contract/ASL/AVS contradiction.

Motivation

The programming model should expose an aligned architectural parent Tile as a homogeneous SubTile array:

auto score =
    as_subtiles<ScoreSubTile, 1, 4>(score_parent);

for (int j = 0; j < 4; ++j)
    TROWMAX(local_max, score[0][j]);

Assembly uses a symmetric output array:

auto probability =
    make_subtile_assembly<ProbParent, ProbSubTile, 1, 4>(scope);

for (int j = 0; j < 4; ++j)
    TCVT(probability[0][j], normalized[j]);

ProbParent whole = probability.commit();

The proxy arrays are not arrays of independent architectural Tiles. They borrow or contribute ranges of one parent identity.

This must be kept distinct from LLVM issue #60, where a real Local Tile array plus runtime indexing enters dynamic Tile-register selection, descriptor mismatch, PHI/live-out, and ICE paths. This RFC is intended to avoid those paths entirely.

Source semantic contract

as_subtiles<SubTile,R,C>(parent) returns a compiler-recognized, non-addressable partition view.

parts[i][j] carries:

  • one immutable parent identity and frozen descriptor;
  • SSA indices i and j;
  • a constant partition descriptor (SubTile, R, C, layout order);
  • no independent Tile payload or allocation.

It is not a SubTile&, cannot expose data(), cannot be stored or passed through ABI, and cannot escape the consuming Tile operation.

make_subtile_assembly<Parent,SubTile,R,C>() owns one linear assembly session. A destination-aware TileOP consumes the previous session and produces the successor session. commit() consumes the final session and returns the complete parent value/handle.

Required static legality

The frontend/verifier must require:

  1. SubTile capacity is at least 128 B and has an assigned range SizeCode.
  2. ParentBytes == R * C * SubTileBytes.
  3. Parent/SubTile dtype, layout, location, role, physical shape, and valid shape are compatible.
  4. Every partition slot maps to one contiguous interval in PTO CELL order.
  5. The partition is a bijection over parent CELL coverage: no gaps, duplicates, or overlaps.
  6. Index bounds are proved: 0 <= i < R, 0 <= j < C.
  7. Every assembly slot is written exactly once before commit.
  8. The selected operation schema accepts the source/destination role and generated effect class.

The compiler must reject instead of falling back to a real Tile array, memory, raw range wrapper, or inline assembly.

Proposed LLVM IR model

Types

Use non-storable target extension types equivalent to:

%linx.tile = type target("linx.tile")
%linx.tile.partition = type target("linx.tile.partition")
%linx.tile.region = type target("linx.tile.region")
%linx.tile.assembly = type target("linx.tile.assembly")

partition, region, and assembly values cannot be allocated, loaded, stored, bitcast, frozen, selected, passed through varargs/unknown calls, or exposed in external ABI.

Partition creation/access

Illustrative contract:

%partition = call %linx.tile.partition
    @llvm.linx.tile.partition.create(
        %linx.tile %parent,
        i32 immPartitionRows,
        i32 immPartitionCols,
        i32 immSubTileDescriptor,
        i32 immLayoutOrder)

%region = call %linx.tile.region
    @llvm.linx.tile.partition.get(
        %linx.tile.partition %partition,
        i32 %i,
        i32 %j)

A frontend may fold create into get, provided parent identity and constants remain explicit in IR.

The resulting offset is:

ordinal = layout_partition_index(i, j)
offset_cells = ordinal * SubTileCells

The extent/SizeCode is constant. Only the origin is an SSA value.

Region-aware consumers

A region token may be consumed only by the operation-schema-approved Tile source role, for example:

%result = call %linx.tile
    @llvm.linx.sfu.trowmax.region(
        %linx.tile.region %source,
        <semantic attributes>)

The region must fuse into the consuming binder's immediately contiguous B.SUBVIEW. It must never become a new Tile definition.

Indexed assembly producers

Prefer a combined producer/session intrinsic so destination identity cannot detach from the producing TileOP:

%next = call %linx.tile.assembly
    @llvm.linx.tileop.tcvt.write.indexed(
        %linx.tile.assembly %session,
        %linx.tile %source,
        i32 %i,
        i32 %j,
        i32 immPartitionDescriptor,
        <semantic TCVT attributes>)

Multi-output operations use generated writeN forms that consume all destination sessions and return an immediately extracted successor-session struct atomically.

Convergence and optimizer contract

Session operations, Shared partition reads, and region-aware TileOP consumers must be convergent and noduplicate and carry the same LLVM convergencectrl domain.

The verifier must reject:

  • partition/region/session escape;
  • CSE, clone, speculation, or DCE that changes linear contribution count;
  • arbitrary select/freeze/cast/call;
  • session PHI unless incoming parent/scope/descriptor/generation/coverage agree;
  • region use by a scalar/descriptor/wrong binder role;
  • detached fragment plus unrelated assembly destination.

Run the verifier after Clang CodeGen, after the main scalar optimizer, and before ISel.

Loop lowering

Source partition loop

B.SUBVIEW has constant SizeCode and dynamic affine origin, so a canonical bounded loop may remain a loop:

for j in [0,C):
    offset_cells = j * SubTileCells
    consume B.SUBVIEW(parent, offset_cells, constant_size)

ScalarEvolution or equivalent must prove bounds and no overflow.

Assembly partition loop

B.ASSEMBLE INIT/LAST are encoded constants. For C > 1, LLVM must either fully unroll or transform a canonical complete-coverage loop to:

slot 0       -> INIT
slot 1..C-2  -> MIDDLE loop
slot C-1     -> LAST
commit       -> no independent instruction

For one slot, emit INIT_LAST. The source language does not expose phase flags.

Conditional skip, duplicate index, unknown trip count, non-canonical traversal, or unproved coverage is rejected in v1.

MachineIR and Blockify integration

Partition/region/session facts must be explicit MachineInstr operands, not discardable debug metadata.

Extend the current pipeline so that:

  1. ISel creates region-aware Tile pseudos with parent, SSA offset, constant extent, binder-local role, and optional destination session.
  2. LinxISATileSSABalance preserves session/region operands and does not convert a partition into ordinary Tile copies.
  3. MachineVerifier checks parent/descriptor/session identity after PHI/copy handling.
  4. LinxISABlockify emits each B.IOT/B.IOS binder together with its own immediately contiguous modifiers.
  5. Shared source binders always use binder-local SrcSelect=0, independent of global operation ordinal.
  6. RegSrc is allocated from R0..R23 and affine CELL offsets are decomposed exactly into GPR + unsigned uimm11 without truncation.

Diagnostics

Add stable diagnostics for at least:

  • unsupported target feature/release;
  • invalid Parent/SubTile capacity ratio;
  • SubTile smaller than one CELL;
  • unassigned SizeCode;
  • incompatible dtype/layout/location/role/valid shape;
  • partition slot discontiguous in CELL order;
  • index not proven in bounds;
  • integer overflow in ordinal/offset computation;
  • duplicate or missing assembly slot;
  • session escape or ambiguous merge;
  • dynamic/conditional assembly traversal unsupported in v1;
  • operation role/effect class incompatible with range lowering.

Minimum test matrix

Clang

  • C++17 proxy types and parts[i][j] syntax;
  • plain-C opaque partition/session builtins;
  • forbidden address/sizeof/aggregate/ABI operations;
  • constant descriptor and dynamic affine index CodeGen.

LLVM IR/verifier

  • partition create/get round trip;
  • structured region and session PHI;
  • select/freeze/escape/clone/CSE/DCE negatives;
  • index range and overflow proofs;
  • multi-output atomic session progression.

CodeGen/MC

  • standalone llvm-mc and integrated Clang assembler;
  • B.SUBVIEW with constant extent and GPR-derived indexed origin;
  • INIT/MIDDLE/LAST/INIT_LAST assembly loops;
  • per-binder modifier adjacency and B.IOT.L;
  • object and llvm-objdump confirmation;
  • linx32 and linx64 lanes.

Runtime/model/workload

  • Local and Shared partition layout maps;
  • per-PE Shared indexed source offsets;
  • coverage/overlap/readiness/rollback/publication;
  • exact 32x64 -> [1][4] 32x16 FlashAttention sentinel from TileOP RFC Issue: TMATMUL_ACC 每迭代插入两条冗余 TCOPY(TMOV) #36;
  • first QK^T TMATMUL, indexed softmax passes, assembled probability parent, and second probabilityV TMATMUL;
  • exact pin/external SHA manifest and LTO/LLD mixed-identity rejection.

Acceptance boundary

This issue is ready for implementation only after:

  1. [0.58.4.1 blocker] Reconcile Local B.IOT SizeCode 12 across contract, ASL, and AVS PTO-ISA/pto-spec#166 is closed at one exact candidate/release head.
  2. docs: define compiler-derived Tile regions linx-isa#187 approves the target types, role/effect schema, and object identity.
  3. Linx-TileOP-API#36 accepts the aligned Tile Array source surface.

Do not add a header-only approximation that emits opaque inline assembly or materializes a real Tile array.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestquestionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions