addrspacecast ISel
本页中的每个地址、偏移、intrinsic ID 和字符串,均从
libtpu-0.0.40-cp314wheel 中的libtpu.so逐字节读取(构建libtpu_lts_20260413_b_RC00,BuildID md589edbbe81c5b328a958fe628a9f2207d)。其他版本会不同。地址是二进制自身的 VMA(text/rodata VMA == 文件偏移)。
摘要
SparseCore 需要十六种不同的地址空间重新标记操作,每一种对应它的指针可能落入的一个 (engine-scope, on-tile-pool) 组合。MLIR sc_tpu 层把每一种都表达为一个 @llvm.tpu.addrspacecast.<tag> intrinsic:一种保持值不变的 cast,其唯一效果是改变指针的 LLVM addrspace(N),而位本身保持相同。本页记录这十六个 intrinsic 在指令选择阶段的行为:它们在哪里(以及是否)转换为通用 ISD::ADDRSPACECAST(0xf4) SelectionDAG 节点,哪个 SelectCode MatcherTable 分支匹配它们,以及全部十六个 cast 的完整 from→to 地址空间映射。
最重要、也最反直觉的发现是:SparseCore cast intrinsic 不会转换为 ISD::ADDRSPACECAST(0xf4)。这个二进制中的 0xf4 节点恰好只来自一个来源:真正的 IR addrspacecast 指令,由标准 SelectionDAGBuilder::visitAddrSpaceCast 降低。十六个 SparseCore cast 会作为真正的 intrinsic 调用 存活进 LLVM-IR(call ptr addrspace(dst) @llvm.tpu.addrspacecast.X(ptr addrspace(src) %p [, i32 %tileid])),并作为由整数 intrinsic ID 键控的 ISD::INTRINSIC_WO_CHAIN 节点抵达 DAG。0xf4/ISD::ADDRSPACECAST 路径(经由 TPUTargetLowering::LowerADDRSPACECAST → 保持值不变的 0xf3 寄存器拷贝节点)服务于 TensorCore / 通用前端的真正 addrspacecast 指令,是一个与 SC intrinsic 家族分离的机制。把 SC cast intrinsic 接入 LowerADDRSPACECAST 的重新实现者会产出一个永远无法匹配、并以 CannotYetSelect 陷落的后端。
本页负责三件事:IR→ISD 转换点(getAddrSpaceCast → getNode(0xf4) 生产者及其唯一真实指令调用方)、cast INTRINSIC_WO_CHAIN 节点落入的 MatcherTable / SelectCode 分支,以及 16 个 intrinsic 的 from→to AS 映射。这些 cast 重新标记的 160/128/192-bit fat-pointer struct 见 Fat Pointers (AS7/8/9);双操作数 (base, tileid) cast 主体见 Tile-ID Cast — 本页只链接,不重新推导二者。
对于重新实现,契约是:
- 十六个 cast 是 LLVM intrinsic,不是
addrspacecast指令。 Intrinsic ID0x33b0..0x33bf(13232..13247)连续、按字母排序,夹在llvm.tpu.addcarry(0x33af)和llvm.tpu.alloca.dreg(0x33c0)之间。它们作为真正的 intrinsic 调用 存活过翻译。 ISD::ADDRSPACECAST是 opcode244(0xf4),只由SelectionDAG::getAddrSpaceCast(0x192E2360)产生,而它唯一的真实调用方是 IR 指令处理器visitAddrSpaceCast(0x19333020)。没有 SC 代码发出 IRaddrspacecast或0xf4节点。- cast ID 会进入 matcher 的
INTRINSIC_WO_CHAIN(opcode-48)分支,但那里没有它们的 pattern。 在TPUDAGToDAGISel::Select(0x13B69640)中,全部十六个都会落入SelectCodeCommon(MatcherTable 大小0x37CAC);matcher 没有 cast-ID 分支,因此如果 cast 节点未被展开而抵达 matcher,就会CannotYetSelect。 - from→to 映射编码在 intrinsic 名称后缀中。 每个
.<tag>(以及.<src>.tile.<tag>多段形式)命名目标 engine-scope / tile pool;该 cast 会把指针的addrspace(N)重新标记到该 pool 的 LLVM 地址空间。 - 操作数个数由目标 sequencer scope 决定,而不是由
.tile.中缀决定。 十六个中有九个,也就是目标为**可访问 tile 的 SC engine(TEC 或 TAC)**的 cast,带有NOperands<2u>trait 并接收第二个i32 tileid操作数:七个 TEC 作用域的(.smem,.spmem,.tec,.smem.tile.tec,.sflag.tile.tec,.sflag.tile.sflag.tec,.tec.sflag.tec)加上两个 TAC 作用域的(.tac,.sflag.tile.tac)。另外七个(SCS/TC 作用域的 cast,包括带.tile.中缀的.sflag.tile.scs/.smem.tile.scs/.sflag.tile.sflag.scs)带有OneOperand,是单操作数重新标记。.tile.token 不会增加操作数;TEC 和 TAC 作用域才会。
| Cast intrinsic ID | 0x33b0..0x33bf(13232..13247),16 个连续、按字母顺序 |
| Intrinsic 名称范围 | llvm.tpu.addrspacecast … llvm.tpu.addrspacecast.tec.sflag.tec |
| MLIR cast 来源 | MemorySpaceCastOpLowering::matchAndRewrite(0x135A5C20)— elide-or-emit |
| IR→intrinsic 发出 | convertOperationImpl(0x15140240,IDA VMA)→ createIntrinsicCall(0x1683F440) |
ISD::ADDRSPACECAST | opcode 244(0xf4);生产者 SelectionDAG::getAddrSpaceCast(0x192E2360) |
唯一 0xf4 调用方 | SelectionDAGBuilder::visitAddrSpaceCast(0x19333020)— 真实 IR 指令处理器 |
| TPU Select | TPUDAGToDAGISel::Select(0x13B69640);cast ID → SelectCodeCommon 默认路径 |
| MatcherTable | 大小 0x37CAC(228 524 B);opcode-48 = ISD::INTRINSIC_WO_CHAIN(无 cast 分支) |
0xf4 lowering | LowerOperation(0x13B70AA0)opcode 244 → LowerADDRSPACECAST(0x13B70480) |
两种 Address-Space-Cast 机制
目的
SparseCore addrspacecast ISel 的定义性事实是,存在两种互不相关的机制都携带 “address space cast” 这几个词,而混淆它们是本页唯一的陷阱。重新实现者必须从一开始就把它们分开,所以本单元在进入细节前先命名二者。
两条路径
MECHANISM A — the SparseCore cast-intrinsic family (THIS PAGE owns the ISel)
─────────────────────────────────────────────────────────────────────────────
sc_tpu.<op> MLIR ScDialect op
│ LowerToSparseCoreLlvm / LlvmTpu dialect
▼
@llvm.tpu.addrspacecast.<tag>(ptr %p [, i32 %tid]) REAL IR intrinsic CALL
│ (16 IDs 0x33b0..0x33bf — survives into LLVM-IR; NOT an IR addrspacecast)
▼
ISD::INTRINSIC_WO_CHAIN (DAG node keyed by intrinsic ID)
│ TPUDAGToDAGISel::Select opcode-'0' (=48) arm
▼
SelectCodeCommon (no cast-ID matcher pattern → consumed/folded, see below)
MECHANISM B — a generic IR `addrspacecast` instruction (SEPARATE; not the SC intrinsic)
─────────────────────────────────────────────────────────────────────────────
addrspacecast ptr addrspace(s) %p to ptr addrspace(d) real IR instruction
│ SelectionDAGBuilder::visitAddrSpaceCast 0x19333020
▼
SelectionDAG::getAddrSpaceCast 0x192E2360 → getNode( opcode 244 = 0xf4 )
│ TPUTargetLowering::LowerOperation 0x13B70AA0 (else if v7 == 244)
▼
TPUTargetLowering::LowerADDRSPACECAST 0x13B70480 → value-preserving 0xf3 reg-copy
```text
> **GOTCHA —** MLIR `MemorySpaceCastOpLowering`(`0x135A5C20`)是通用 `llvm.addrspacecast` *指令*进入 SparseCore 流水线的*唯一*位置,并且只有在它的失败边上才会如此(§Conversion Site)。十六个 `@llvm.tpu.addrspacecast.*` *intrinsic* 由完全不同的机制发出,即 LlvmTpu 方言翻译,并且永远不会变成 `0xf4` 节点。把 cast intrinsic 通过 `LowerADDRSPACECAST` 降低的后端,是把机制 B 接到了只会走机制 A 的输入上。
### 为什么有两种机制
这种分裂存在,是因为两种 cast 含义不同。机制 B 在会*改变指针表示*的 LLVM 地址空间之间重新标记指针(例如 fat-pointer struct 与扁平整数),因此需要一个会物化表示变化的 lowering,也就是带 `MVT::i32` 载体、保持值不变的 `0xf3` 寄存器拷贝。机制 A 的十六个 cast 是*类型系统记账*:它们在表示完全相同的 SparseCore engine-scope / tile pool 之间重新标记指针,所以该 cast 是一个由消费它的 load/store 吸收的 no-op。把它们保留为不透明 intrinsic(而不是 `addrspacecast` 指令),使 SC 后端能在优化期间携带 engine-scope tag,而不被通用 `InferAddressSpaces` / `addrspacecast` 机制过早折叠掉。
---
## 转换点 — MLIR Op → Intrinsic,以及 `0xf4` 断开
### 目的
“IR→`ISD::ADDRSPACECAST` 转换点”有两半需要重新实现者追踪:(1)MLIR cast op 在哪里变成存活的 `@llvm.tpu.addrspacecast.*` intrinsic 调用,(2)证明这个调用*永远不会*转换为 `ISD::ADDRSPACECAST(0xf4)`。二者都在下方以字节地址锚定。
### MLIR cast op — elide 或 fall through
ScDialect cast op(`memref.memory_space_cast`,从 SparseCore 类型系统视角看)由 `MemorySpaceCastOpLowering::matchAndRewrite`(`0x135A5C20`)重写。它的主体是一个已按字节确认的三行 elide-or-emit 决策:
```c
function MemorySpaceCastOpLowering_matchAndRewrite(op, adaptor, rewriter): // 0x135A5C20
src_llvm = TypeConverter::convertType(srcPointerType) // 0x135A5C20:22
dst_llvm = TypeConverter::convertType(resultType) // 0x135A5C20:23
if src_llvm != dst_llvm:
return failure // → generic pattern emits a REAL llvm.addrspacecast
rewriter.replaceOp(op, adaptor.operand) // ELIDE: identical LLVM ptr type → drop the cast
return successNOTE — 这个 MLIR pattern 是机制 B 的入口,而不是机制 A 的入口。当
convertType(src) == convertType(dst)(两个 SC 空间折叠为同一个!llvm.ptr)时,cast 会被直接消去。当二者不同时,该 pattern 失败,通用ConvertOpToLLVMPattern会发出一个真正的llvm.addrspacecast指令,随后它走机制 B 到0xf4。十六个@llvm.tpu.addrspacecast.*intrinsic 不源自这里;它们由 LlvmTpu 方言翻译发出,该翻译把每个sc_tpuop 降低为它注册的 intrinsic。逐 ID 的convertType折叠映射(21 个 SC 地址空间 ID 中哪些共享一个 LLVM addrspace)由 Tile-ID Cast 负责。
Intrinsic 发出点(链接到逐 op 分派器)
十六个 SparseCore cast 会在 LlvmTpu 方言的 op→IR 翻译中变成真正的 IR intrinsic 调用。convertOperationImpl(IDA 重基址 dump 中为 0x15140240)是一个约 1349 分支的 op-identity 分派器;每个 cast 分支都会尾调用共享跳板,该跳板用该 cast 的 intrinsic ID 调用 mlir::LLVM::detail::createIntrinsicCall(0x1683F440)。createIntrinsicCall 是统一的 op→intrinsic-call 发出器,已按字节确认会物化真正的声明 + 调用:
function createIntrinsicCall(builder, modTrans, op, intrinsicID, numResults, ...): // 0x1683F440
operands = ModuleTranslation::lookupValues(op.operands) // 0x1683F440:348 / :422 (base [, i32 tileid])
decl = Intrinsic::getOrInsertDeclaration(Module, intrinsicID, overloadTypes) // 0x1683F440:656
call = IRBuilderBase::CreateCall(builder, decl, operands) // 0x1683F440:669
return call // %r = call ptr addrspace(dst) @llvm.tpu.addrspacecast.X(ptr addrspace(src) %base [, i32 %tid])
```text
> **NOTE —** op→intrinsic-ID 分派器机制(全部 1349 个分支、跳板、操作数/结果映射)属于方言翻译问题,不属于 ISel 问题;本页只引用它来确立 cast 会作为 `INTRINSIC_WO_CHAIN` 调用抵达 DAG,而不是 IR `addrspacecast`。双操作数 `(base, i32 tileid)` 调用,即九个 TEC 和 TAC 作用域的 cast,详见 [Tile-ID Cast](tile-id-cast.md)。
### `0xf4` 生产者及其唯一调用方
`ISD::ADDRSPACECAST` 是 SelectionDAG opcode **244**(`0xf4`)。唯一生产者是 `SelectionDAG::getAddrSpaceCast`(`0x192E2360`),已按字节确认会构造带该 opcode 的节点:
```c
function SelectionDAG_getAddrSpaceCast(loc, vt, ptr, srcAS, dstAS): // 0x192E2360
AddNodeIDNode(&id, 244 /* ISD::ADDRSPACECAST */, vtlist, &ops, 1) // 0x192E2360:30
... // CSE lookup
node->opcode = 244 // 0x192E2360:70
return node它唯一有意义的调用方是 SelectionDAGBuilder::visitAddrSpaceCast(0x19333020),也就是真正 IR addrspacecast 指令的处理器。没有 TPU/SparseCore 代码路径调用 getAddrSpaceCast、构造 AddrSpaceCastInst 或发出 0xf4 节点。因此,SparseCore cast intrinsic 只有在某个 pass 先把它重写成 IR addrspacecast 指令时,才可能变成 0xf4 节点,而没有任何 pass 会这样做。
0xf4 lowering 分支(机制 B 的目的地)
当真正的 addrspacecast 指令确实抵达 DAG 时,TPUTargetLowering::LowerOperation(0x13B70AA0)会捕获 opcode 244 并转发它,已按字节确认:
function TPUTargetLowering_LowerOperation(op, dag): // 0x13B70AA0
v7 = op.getOpcode()
...
else if (v7 == 244) // 0x13B70AA0:73 ISD::ADDRSPACECAST
return TPUTargetLowering::LowerADDRSPACECAST(op, dag) // 0x13B70480
```text
`LowerADDRSPACECAST`(`0x13B70480`)把 `0xf4` 节点重写为 SparseCore 中保持值不变的 `0xf3` 寄存器拷贝节点(合法性矩阵和 `0xf3` 寄存器拷贝 matcher 分支属于 TensorCore 前端问题,超出本页范围)。该分支存在;它只是永远不会被 cast-intrinsic 家族抵达。
### 函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
| `MemorySpaceCastOpLowering::matchAndRewrite` | `0x135A5C20` | MLIR cast:若 `convertType` 相等则消去,否则失败 → 通用 `llvm.addrspacecast` |
| `convertOperationImpl` | `0x15140240`(IDA VMA) | LlvmTpu op→IR 分派器;cast 分支 → `createIntrinsicCall` |
| `mlir::LLVM::detail::createIntrinsicCall` | `0x1683F440` | 发出真正的 intrinsic 调用(`getOrInsertDeclaration` + `CreateCall`) |
| `SelectionDAG::getAddrSpaceCast` | `0x192E2360` | 唯一的 `getNode(244)` / `0xf4` 生产者 |
| `SelectionDAGBuilder::visitAddrSpaceCast` | `0x19333020` | `getAddrSpaceCast` 的唯一调用方 — 真实 IR 指令处理器 |
| `TPUTargetLowering::LowerOperation` | `0x13B70AA0` | opcode `244` → `LowerADDRSPACECAST`;opcode 48 不在此 lowering |
| `TPUTargetLowering::LowerADDRSPACECAST` | `0x13B70480` | `0xf4` → 保持值不变的 `0xf3` 寄存器拷贝节点 |
---
## MatcherTable / `SelectCode` 分支
### 目的
SparseCore cast intrinsic 会作为 `ISD::INTRINSIC_WO_CHAIN` 节点抵达 DAG,其 child-0 是整数 intrinsic ID。本单元记录该节点进入 `TPUDAGToDAGISel::Select` 的哪个分支,以及接下来发生什么:答案是 “`SelectCodeCommon` 默认路径,而那里没有 matcher pattern 认领它”。
### 入口点
```text
TPUDAGToDAGISel::Select (0x13B69640) ── per-node ISel entry
└─ switch on node opcode (low byte)
case '0' (= 48 = ISD::INTRINSIC_WO_CHAIN)
└─ switch on intrinsic ID v68 (= node->op0 constant)
├─ 13216..13222 ── ReplaceAllUses / cross-lane reg-class (special handlers)
├─ 13223..13465 ── (the big default group) ── goto LABEL_118
│ ▲ the 16 cast IDs 13232..13247 live HERE
├─ 13227/13229/13293/13297/... ── selectCrossLane / selectCMask / selectErf (special)
└─ default ── selectCrossLaneIntrinsic / selectErfIntrinsic
LABEL_118: SelectCodeCommon(this, node, MatcherTable, 0x37CAC, OperandLists)算法 — cast ID 落入 SelectCode 默认路径
TPUDAGToDAGISel::Select(0x13B69640)首先按节点 opcode 分派(反编译器把 INTRINSIC_WO_CHAIN 分支渲染为 case '0',即 opcode 48 的低字节 ASCII)。在该分支内部,它读取 intrinsic ID 并对其 switch。十六个 cast ID 13232..13247(0x33b0..0x33bf)位于一个大型连续 fall-through 块中(cases 13225/13226/13231/13232 … 13465),其在 0x13B69640:422 的终止语句是 goto LABEL_118,也就是 SelectCodeCommon 尾调用:
function TPUDAGToDAGISel_Select(node): // 0x13B69640
switch (node.opcode_low_byte):
case 48 /* ISD::INTRINSIC_WO_CHAIN */: // 0x13B69640:160 ('0')
intNo = node.op0_constant // :167 v68
switch (intNo):
case 13216..13220: ReplaceAllUsesOfValueWith(...); return // :171 special
case 13221/13222: reg_class = (cond ? XRFPR2 : XRFPR0);
goto selectCrossLane // :178
// --- the big default group, INCLUDING all 16 cast IDs ---
case 13225/13226/13231/13232/.../13247/.../13465: // :221..:421
goto LABEL_118 // :422
case 13227/13229: ... selectCrossLane // :423 special
case 13293: selectCMask(node); return // :431 special
...
LABEL_118:
SelectCodeCommon(this, node,
MatcherTable /* &GOT - 521848896 */, 0x37CAC, // :509
TPUDAGToDAGISel::SelectCode::OperandLists)
```text
因此,每个存活到 `Select` 的 cast intrinsic 节点都会交给通用 `SelectCodeCommon` MatcherTable 解释器,也就是处理每个没有专用 `Select` C++ handler 的 TPU intrinsic 的同一分支。cast ID **没有**任何特殊 C++ 处理(不同于例如 `13221/13222` cross-lane、`13293` `selectCMask` 或 `13227/13229`)。
### Matcher 没有 cast-ID pattern
`SelectCodeCommon` 遍历 MatcherTable(大小 `0x37CAC` = 228 524 字节)。其顶层 `OPC_SwitchOpcode` 有一个 opcode 48(`ISD::INTRINSIC_WO_CHAIN`)分支,会按 child-0 整数 ID 匹配约 150 个 TPU load/store/vector/scalar/sync intrinsic,但十六个 cast ID `0x33b0..0x33bf` **不在**其中(也不在任何其他分支中)。因此,未被展开的 cast `INTRINSIC_WO_CHAIN` 节点若抵达 matcher,就会 `CannotYetSelect`。
> **QUIRK —** matcher 中的 opcode 48 *确实*是 `ISD::INTRINSIC_WO_CHAIN`,并且*确实*存在(一个多 KB 的分支,匹配约 150 个 intrinsic);只是没有列出 cast ID。缺失是 cast ID 特有的,不是 opcode 缺失。期望这些 cast intrinsic 像 load 一样匹配、因而添加 TableGen pattern 的重新实现者会发现没有保留槽位;这是有意为之,因为 cast 应该在抵达 matcher 前被消费。
### Cast 如何在 matcher 前被清除
十六个 cast 都保持值不变:结果指针等于操作数指针,只是 `addrspace` tag 不同。预期的清除方式是,**消费它的** SparseCore load/store ISel pattern 会读*穿过*该 cast 的指针操作数(并且对 TEC/TAC 作用域的双操作数 cast,读取独立的 `i32 tileid` 操作数),因此 cast 节点变成 dead 并在 `Select` 看到它之前被 DCE;或者它会由通用 DAGCombiner 的 `visitINTRINSIC_WO_CHAIN` 指针透传折叠。无论哪种方式,cast 都不需要 matcher pattern。准确的清除阶段(consumer-pattern fold 还是通用 combiner DCE)未在此按字节追踪;负面结论则是牢固的:没有 `0xf4` 转换、没有 matcher 分支、没有特殊 `Select` handler。
### 函数映射
| 函数 | 地址 | 角色 |
|---|---|---|
| `TPUDAGToDAGISel::Select` | `0x13B69640` | 逐节点 ISel;opcode-48 intrinsic 分派 |
| `SelectionDAGISel::SelectCodeCommon` | (tail-called) | MatcherTable 解释器;`0x37CAC` 字节表 |
| MatcherTable opcode-48 分支 | (在表中) | `ISD::INTRINSIC_WO_CHAIN`;约 150 个 ID,**无** cast ID |
| `selectCrossLaneIntrinsic` / `selectCMask` / `selectErfIntrinsic` | `0x13B6B940` / `0x13B6C6E0` / `0x13B6B480` | 特殊处理器 intrinsic(不是 cast) |
---
## 16 个 Intrinsic 的 from→to AS 映射
### 目的
每个 cast intrinsic 的行为完全由其名称后缀决定,后缀命名目标 engine-scope,并且(对带 `.tile.` 中缀的形式)命名 per-tile pool 重新标记。本单元给出全部十六个的完整 from→to 地址空间映射,并锚定到 SparseCore 地址空间 ID 表。
### 命名语法
`llvm.tpu.addrspacecast` 之后的后缀是**目标 tag**。确认自二进制中的十六个名称字符串,共出现三种形状:
```text
llvm.tpu.addrspacecast no suffix → generic/default re-tag
llvm.tpu.addrspacecast.<engine|pool> 1 segment → re-tag to that engine-scope / pool
llvm.tpu.addrspacecast.<srcpool>.tile.<eng> 3 segment → per-tile-pool re-tag, named src + dst scopeengine tag 是 scs / tac / tec(指针被查看时所在作用域的 SparseCore 子引擎)和 tc(TensorCore)。pool tag 是 smem / spmem / sflag。.tile. 中缀标记一个 per-tile-pool 重新标记(指针会被重新标记进 tile-private pool,并在命名的目标 engine 作用域中查看)。.tile. token 本身不意味着第二个操作数;操作数个数由目标 sequencer scope 控制:可访问 tile 的引擎 TEC 和 TAC 接收 tileid,SCS 和 TC 不接收(见下一个标注)。
QUIRK — 操作数个数跟随目标 sequencer,而不是名称的
.tile.中缀。 cast 仅当其目标作用域是可访问 tile 的 SC engine,即 TEC 或 TAC 时,才接收第二个i32 tileid操作数(九个带NOperands<2u>trait 的 cast,已从二进制中每个 op class 的 trait list 按字节确认)。SCS/TC 作用域的 cast 带OneOperand,即使带.tile.也是单操作数,例如.sflag.tile.scs(0x33b3)和.smem.tile.scs(0x33b9)是单操作数,而.smem(0x33b8)、.spmem(0x33bb)、.tec(0x33be)以及两个 TAC cast.tac(0x33bc)/.sflag.tile.tac(0x33b6)是双操作数。双操作数 cast 的 lowering 主体由 Tile-ID Cast 负责。QUIRK — 后缀命名的是目标作用域,不是源。cast 保持值不变,所以源地址空间就是操作数指针携带的任何地址空间;intrinsic 只断言新 tag。ID 纯粹按字母顺序分配(它是 Intrinsic 名称表位置),所以相邻 ID 在语义上不相邻:
0x33b6(.sflag.tile.tac)和0x33b7(.sflag.tile.tec)只差目标 engine,而0x33bb(.spmem)和0x33bc(.tac)互不相关。
完整映射
目标地址空间读自 SparseCore 地址空间 ID 表(该表由地址空间目录负责;这里只复现这些 cast 目标涉及的 ID)。“AS” 列是 LLVM 地址空间编号;engine-scope tag 会重新标记到某个 pool 的逐作用域别名,而不是单一固定编号,所以这些行给出作用域而不是一个 ID。
Operands 列从每个 op class 的 operand trait 按字节确认(NOperands<2u> ⇒ 双操作数,OneOperand ⇒ 单操作数)。九个双操作数 cast 正好是 TEC 和 TAC 作用域集合。
| ID | hex | Intrinsic 名称 | 操作数 | from(src AS) | to(dst scope / AS) |
|---|---|---|---|---|---|
| 13232 | 0x33b0 | llvm.tpu.addrspacecast | ptr | any | generic / default pointer re-tag |
| 13233 | 0x33b1 | ….scs | ptr | any | SCS engine scope(scalar-sequencer view) |
| 13234 | 0x33b2 | ….scs.sflag.scs | ptr | sflag | SFLAG, SCS scope(sflag_scs, AS 223) |
| 13235 | 0x33b3 | ….sflag.tile.scs | ptr | sflag-tile | SFLAG tile pool, SCS view(sflag_tile AS 217) |
| 13236 | 0x33b4 | ….sflag.tile.sflag.scs | ptr | sflag-tile | SFLAG tile → sflag_scs(AS 223), SCS scope |
| 13237 | 0x33b5 | ….sflag.tile.sflag.tec | ptr, i32 tid | sflag-tile | SFLAG tile → sflag, TEC scope |
| 13238 | 0x33b6 | ….sflag.tile.tac | ptr, i32 tid | sflag-tile | SFLAG tile pool, TAC view(sflag_tile AS 217) |
| 13239 | 0x33b7 | ….sflag.tile.tec | ptr, i32 tid | sflag-tile | SFLAG tile pool, TEC view(sflag_tile AS 217) |
| 13240 | 0x33b8 | ….smem | ptr, i32 tid | any | SMEM(AS 0 / smem), TEC scope |
| 13241 | 0x33b9 | ….smem.tile.scs | ptr | smem-tile | per-tile SMEM(smem_tile/TileSmem AS 219), SCS scope |
| 13242 | 0x33ba | ….smem.tile.tec | ptr, i32 tid | smem-tile | per-tile SMEM(smem_tile/TileSmem AS 219), TEC scope |
| 13243 | 0x33bb | ….spmem | ptr, i32 tid | any | SPMEM(chip-shared SC SRAM, AS 202), TEC scope |
| 13244 | 0x33bc | ….tac | ptr, i32 tid | any | TAC engine scope(tile-access-core view) |
| 13245 | 0x33bd | ….tc | ptr | any | TensorCore scope(cross-engine handoff) |
| 13246 | 0x33be | ….tec | ptr, i32 tid | any | TEC engine scope(tile-execute view) |
| 13247 | 0x33bf | ….tec.sflag.tec | ptr, i32 tid | sflag | SFLAG, TEC scope(.tec engine + sflag pool) |
NOTE — intrinsic 名称及其连续 ID 分配(
0x33b0..0x33bf,夹在0x33af llvm.tpu.addcarry与0x33c0 llvm.tpu.alloca.dreg之间)逐字节读自二进制的 Intrinsic 名称表。操作数个数读自每个 op class 的 operand trait(九个NOperands<2u>双操作数 cast 正是 TEC 和 TAC 作用域集合;七个OneOperandcast 是 SCS/TC 作用域)。pool 地址空间编号217(sflag_tile)、219(smem_tile/TileSmem)、223(sflag_scs)、202(spmem)、0(smem)来自 cast-lowering 驱动(CastSflagPointerToSflagAny0x135b8a00,CastTileSmemPointerToSmem0x135b86e0)。对于裸 engine tag(.scs/.tac/.tc),给定 engine-scope tag 解析到的确切 LLVM 地址空间编号是由 Fat Pointers (AS7/8/9) 和地址空间目录负责的逐作用域别名选择;本页解析的是 pool,不是逐作用域数字别名。
双操作数(TEC/TAC 作用域)子集
十六个中有九个接收第二个 i32 tileid 操作数,也就是目标为可访问 tile 的 SC engine 的 cast。七个是 TEC 作用域:0x33b5(.sflag.tile.sflag.tec)、0x33b7(.sflag.tile.tec)、0x33b8(.smem)、0x33ba(.smem.tile.tec)、0x33bb(.spmem)、0x33be(.tec)、0x33bf(.tec.sflag.tec);两个是 TAC 作用域:0x33b6(.sflag.tile.tac)、0x33bc(.tac)。全部九个都带有 NOperands<2u> op trait;另外七个带有 OneOperand。剩余七个是单操作数重新标记,包括三个 SCS 作用域且带 .tile. 中缀的形式(.sflag.tile.scs、.sflag.tile.sflag.scs、.smem.tile.scs)以及裸 .scs / .scs.sflag.scs / .tc cast。双操作数 cast 主体,即 (base, tileid) 如何解析成 tile-local 物理地址,完整记录在 Tile-ID Cast;本页只记录 TEC 或 TAC 目标作用域是操作数个数判别条件,并且第二个操作数是 tile selector。
GOTCHA — 不要从
.tile.token 推断操作数数量。….sflag.tile.scs(0x33b3,1 个操作数)和….sflag.tile.tec(0x33b7,2 个操作数)都带.tile.,也都指向 SFLAG-tile pool,但只有 TEC 作用域的那个接收tileid。.sflag.tile.tac(0x33b6)和裸.tac(0x33bc)cast 也是 2 操作数,尽管 TAC 的裸形式没有.tile.。反过来,….smem(0x33b8)和….spmem(0x33bb)完全没有.tile.,却是 2 操作数。操作数个数判别条件是目标 sequencer scope 是否为可访问 tile 的 engine,即 TEC 或 TAC ⇒NOperands<2u>⇒(base, i32 tileid);SCS/TC ⇒OneOperand,而不是字面.tile.token。
注意事项
构建 SparseCore addrspacecast ISel 的重新实现者必须按顺序做这些事,并避开名称中内置的陷阱:
- 声明十六个 intrinsic,而不是一个
addrspacecast指令。 在字母顺序位置保留连续 Intrinsic ID(在本构建中位于addcarry与alloca.dreg之间),并从方言翻译中把它们作为普通 intrinsic 调用发出,绝不要作为 IRaddrspacecast指令。把它们标记为保持值不变(IntrNoMem,cast 结果等于其指针操作数),使 combiner 能折叠它们。 - 不要把它们接到
LowerADDRSPACECAST。 该分支只用于通用 IRaddrspacecast指令(机制 B)。把 cast intrinsic 路由到那里是最可能犯的错误,并会产生一个永远无法选择这些 cast 的后端。 - 不要给它们 matcher pattern;依赖消费者吸收。 cast 应被折叠进消费它的 load/store(后者读穿过指针和独立
tileid)。如果某个 cast 以某种方式存活到 matcher,正确行为是CannotYetSelect,这表示缺少消费者折叠,而不是缺少 cast pattern。 - 用名称后缀驱动 from→to 映射,用目标作用域驱动操作数个数。
.scs/.tac/.tec/.tcengine tag 选择目标 pool 的逐作用域别名;pool tag(smem/spmem/sflag)命名物理层级。第二个i32 tileid操作数仅当目标作用域是可访问 tile 的 engine,即 TEC 或 TAC(九个NOperands<2u>cast)时存在,并通过 tile 地址解析器路由;它不是由.tile.token 决定的,因为该 token 也出现在单操作数 SCS 作用域 cast 上。
存活 cast 的清除阶段(consumer-pattern fold 还是通用 visitINTRINSIC_WO_CHAIN DCE)未在此按字节追踪,是唯一的未闭合链接;它不影响上面的 ISel 契约,因为两种情况下 cast 都不需要 matcher pattern,也永远不会变成 0xf4 节点。
相关组件
| 名称 | 关系 |
|---|---|
MemorySpaceCastOpLowering::matchAndRewrite(0x135A5C20) | MLIR cast op:消去或 fall through 到通用 llvm.addrspacecast(机制 B 的入口) |
createIntrinsicCall(0x1683F440) | 把十六个 cast 作为存活的 intrinsic 调用发出(机制 A 的发出点) |
SelectionDAG::getAddrSpaceCast(0x192E2360) | 唯一的 ISD::ADDRSPACECAST(0xf4) 生产者,SC cast 永远不会抵达 |
TPUDAGToDAGISel::Select(0x13B69640) | 把 cast INTRINSIC_WO_CHAIN 节点路由到 SelectCodeCommon 默认路径 |
TPUTargetLowering::LowerADDRSPACECAST(0x13B70480) | 通用 IR addrspacecast 的 0xf4 → 0xf3 lowering(机制 B 的主体) |
交叉引用
- Fat Pointers (AS7/8/9) — 这些 cast 重新标记的 160/128/192-bit 结构化指针,以及逐作用域地址空间别名编号。
- Tile-ID Cast — 双操作数
(base, i32 tileid)cast 主体(这十六个中的九个 TEC 和 TAC 作用域 cast)。 - SparseCore Hardware Architecture — cast tag 命名的四层内存模型和 SCS/TAC/TEC engine scope。
- SparseCore Overview — Part IX 的导航入口;engine 名称、逐代存在性、数据路径。
- Stream Gather/Scatter — 读取穿过重新标记 SparseCore 指针的 indirect-DMA 消费者。
- Binary:
extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d) - 索引项: Part IX — SparseCore & BarnaCore / SparseCore pointers & DMA — 返回索引