Skip to content

VGPR到SGPR拷贝的错误代码生成 #214

Description

@Humber-186

Ventus libclc linked bitcode 构建阶段大量打印 Impossible RISCV physreg copy

摘要

dev-thu-sfu-mma 分支上执行:

bash build-ventus.sh --build libclc

构建尾部会大量打印:

Impossible RISCV physreg copy: dst=X5 src=V0
Impossible RISCV physreg copy: dst=X5 src=V1
Impossible RISCV physreg copy: dst=X5 src=V3
...

当前顶层脚本仍通过 || true 吞掉 libclc/build_riscv32clc.sh 的状态,所以顶层命令返回 0。但这些诊断说明后端仍生成了非法的 VGPR 到 GPR 物理寄存器 copy,属于真实 codegen 问题,不应按普通 warning 处理。copyPhysReg() 禁止 VGPR -> GPR/GPRF32 是 Ventus SIMT 寄存器模型下的预期行为;问题在于更早阶段不应生成这种 copy。

问题根因在 main 分支存在,编译中途可见生成的错误代码,但恰好都被后续 Pass 常量优化掉了,因此未暴露问题。
dev-thu-sfu-mma 分支修改了 libclc 编译流程,导致 inline 机会增加,生成了一些不可被常量优化掉的 VGPR 到 SGPR 错误拷贝,导致问题暴露

相关 commit

当前出问题的分支:

518173e25cb69c6364ba57f93c9f6ad39849fada
[Ventus] Preserve SFU MMA features on dev-thu base

对照的 main

95fb0ba3a88151b6c90a1c25b7161246be6fd3b5
[VENTUS][FEAT] Enable promote small-vector alloca

dev-thu-sfu-mmamain 之后的相关提交包括:

518173e25cb6 [Ventus] Preserve SFU MMA features on dev-thu base
86558bf02778 [Ventus] Wire MMA pseudo lowering on dev-thu base
73157331f871 code cleanup
32b89a8c3eae [Ventus] Add MMA lowering on dev-thu base
712c32dcb712 [Ventus] Build libclc from linked bitcode
7d5ffac57176 [RISCV][Ventus] Specialize private-derived generic pointers
25321e824d52 [RISCV][Ventus] Temporarily disable SGPR SIMT checker
e9d0eaecfd84 [Ventus] Fix SIMT SGPR shadow checks

复现方式

为了避免海量日志刷屏,建议在仓库根目录重定向到文件:

bash build-ventus.sh --build libclc > ventus-libclc-build-check.log 2>&1
echo $?
wc -l ventus-libclc-build-check.log
rg -c "Impossible RISCV physreg copy" ventus-libclc-build-check.log
rg -n "Building riscv32|Impossible RISCV physreg copy|error:|FAILED|clang|builtins.link|riscv32clc" \
  ventus-libclc-build-check.log | sed -n '1,120p'
tail -n 80 ventus-libclc-build-check.log

本地实测:

exit=0
3128 ventus-libclc-build-check.log
1977 lines match "Impossible RISCV physreg copy"

第一处诊断出现在:

************* Building riscv32 libclc object file ************
Impossible RISCV physreg copy: dst=X5 src=V0
Impossible RISCV physreg copy: dst=X5 src=V1
...

对应的脚本调用位于 build-ventus.sh

bash ${DIR}/libclc/build_riscv32clc.sh ${DIR}/libclc ${LIBCLC_BUILD_DIR} ${VENTUS_INSTALL_PREFIX} || true

因此当前顶层命令返回 0,不是因为没有问题,而是因为这一阶段仍被 || true 吞掉状态。

实际触发的子命令

libclc/build_riscv32clc.sh 当前逻辑为:

BUILTINS_BC=${LIBCLC_BUILD_DIR}/builtins.link.riscv32--.bc
OUTPUT_OBJ=${LIBCLC_BUILD_DIR}/riscv32clc.o

${BINARY_DIR}/bin/clang -target riscv32 -mcpu=ventus-gpgpu \
    -cl-std=CL2.0 \
    -Wno-override-module \
    -ffunction-sections -fdata-sections \
    -c "${BUILTINS_BC}" \
    -o "${OUTPUT_OBJ}"

直接运行 clang 子命令也能复现大量诊断:

rm -f riscv32clc-direct.o ventus-libclc-clang-direct.log
install/bin/clang -target riscv32 -mcpu=ventus-gpgpu \
  -cl-std=CL2.0 \
  -Wno-override-module \
  -ffunction-sections -fdata-sections \
  -c build-libclc/builtins.link.riscv32--.bc \
  -o riscv32clc-direct.o \
  > ventus-libclc-clang-direct.log 2>&1
echo $?
wc -l ventus-libclc-clang-direct.log
rg -c "Impossible RISCV physreg copy" ventus-libclc-clang-direct.log

本地实测:

exit=0
1977 ventus-libclc-clang-direct.log
1977

对象文件仍会生成:

riscv32clc-direct.o

这说明当前本地 install/bin/clang 表现为没有因该 llvm_unreachable 中止编译,但后端确实走到了 impossible copy 路径。不同 LLVM 构建配置下,llvm_unreachable 的运行时表现可能不同,因此不应依赖当前退出码为 0 来判断问题是否无害。

关键 MIR 线索

在仓库根目录用 __divdf3 可以复现早期寄存器类混用:

install/bin/llvm-extract --func=__divdf3 \
  build-libclc/builtins.link.riscv32--.bc \
  -o divdf3.check.bc

install/bin/llc \
  -mtriple=riscv32 -mcpu=ventus-gpgpu \
  -stop-after=amdgpu-isel \
  divdf3.check.bc \
  -o divdf3.after-isel.check.mir

rg -n "%[0-9]+:gpr = COPY %[0-9]+|%[0-9]+:vgpr = COPY \\$x0" \
  divdf3.after-isel.check.mir | sed -n '1,120p'

实测 MIR 中可见:

%351:vgpr = COPY $x0
%22:vgpr = COPY %351
%350:gpr = COPY %351
%349:gpr = COPY %351

这里 %351vgpr,但 %350/%349gpr,即虚拟寄存器阶段已经出现了 VGPR -> GPR 方向的 copy。

注意:这个单函数 MIR 片段是定位 register-class 传播问题的早期线索,不等价于证明 __divdf3 单独完整 codegen 一定会走到最终的 Impossible RISCV physreg copy。在 main 上用同一函数实测,-stop-after=amdgpu-isel 后也能看到相同形态,但 llc /tmp/main-divdf3.check.bc 可以完整生成汇编。最终构建阶段的大量 Impossible RISCV physreg copy 仍需以完整 linked bitcode codegen 的实测日志为准。

进一步追踪 main 上的 __divdf3,这类早期 copy 在后续阶段会被合法化。例如早期 MIR 中有:

%351:vgpr = COPY $x0
%350:gpr = COPY %351
%349:gpr = COPY %351

用默认 llc 路径追踪时,到 virtregrewriter 后还能看到短暂的物理寄存器形式:

$v3 = COPY $x0
$x5 = COPY $v3
$x6 = COPY $v3

而更接近旧 libclc clang -c 行为的 -regalloc=fast 路径中,到 postrapseudos 前该片段已经被传播成:

$v3 = COPY $x0
$x5 = COPY $x0
$x6 = COPY $x0

随后 postrapseudos 后变为合法的 scalar/vector 分别取零:

$v3 = VMV_V_X $x0
$x5 = ADDI $x0, 0
$x6 = ADDI $x0, 0
$v10 = VMV_V_X $x0
$v9 = VMV_V_X $x0

也就是说,main 上这个具体 __divdf3 片段没有把 VGPR -> GPR copy 保留到最终机器指令;它被后续 pass 识别为来自 $x0 的零值,并改写成合法的 GPR 赋零和 VGPR broadcast zero。当前 issue 的最终错误来自完整 linked bitcode codegen 中仍有其他非法 VGPR -> GPR copy 存活到 copyPhysReg()

相关源码点:

// llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
// vGPR -> sGPR move
if (RISCV::GPRRegClass.contains(DstReg) &&
    RISCV::VGPRRegClass.contains(SrcReg)) {
  llvm_unreachable("Illegal copy from VGPR to SGPR");
}

VGPR -> GPR 在 Ventus SIMT 模型下是非法方向。这里的非法不是 copyPhysReg() 缺功能;如果这种方向的 copy 存活到物理寄存器 copy 阶段,copyPhysReg() 拒绝它是正确的目标约束。修复必须保持该方向 copy 被禁止,不能通过实现 VGPR -> GPR/GPRF32 copy、恢复 VMV_X_S / VFMV_F_S,或其他方式把 per-lane 值静默拉回 scalar 域来绕过。

与 main 的对比

main 对应版本的 libclc/build_riscv32clc.sh 仍是旧流程:

# Collect all the object files in build directory
OBJECT_FILE_LIST=""
for item in $(find ${LIBCLC_BUILD_DIR} -name "*.bc.o")
do
    OBJECT_FILE_LIST="${OBJECT_FILE_LIST} ${item}"
done

# Compile other left IR files
for item in $(ls ${LIBCLC_DIR}/generic/lib | grep  ll)
do
    ${BINARY_DIR}/bin/clang ... -c ${LIBCLC_DIR}/generic/lib/${item} ...
    OBJECT_FILE_LIST="${OBJECT_FILE_LIST} ${LIBCLC_BUILD_DIR}/${item}.o"
done

${BINARY_DIR}/bin/ld.lld --relocatable ${OBJECT_FILE_LIST} \
    --allow-multiple-definition \
    -o ${LIBCLC_BUILD_DIR}/riscv32clc.o

也就是说,main 没有把完整 builtins.link.riscv32--.bc 直接送进一次后端 codegen 来生成最终 riscv32clc.o,因此不会以同样方式暴露该问题。

这不表示 main 中完全没有早期 SGPR/VGPR register-class 混用线索。实际检查 main__divdf3 可见同类早期 MIR copy,但该函数单独完整 codegen 时后续 pass 会把这类来自 $x0 的 copy 合法化,最终汇编没有保留对应的非法 VGPR -> GPR 机器指令。

额外验证:如果在 main 上手动直接编译 linked bitcode:

install/bin/clang -target riscv32 -mcpu=ventus-gpgpu \
  -cl-std=CL2.0 \
  -Wno-override-module \
  -ffunction-sections -fdata-sections \
  -c build-libclc/builtins.link.riscv32--.bc \
  -o main-riscv32clc-direct.o

它不会走到这串 Impossible RISCV physreg copy,而是先死在另一个旧问题:

fatal error: error in backend: Cannot select: i32 = RISCVISD::FCVT_X
In function: _Z16convert_char_rtef

dev-thu-sfu-mma 中的 712c32dcb712 同时增加了 riscv_fcvt_x/riscv_fcvt_xu 的 Zfinx pattern,使 linked bitcode 编译能越过这个更早的 FCVT 选择失败,继续暴露后面的 VGPR/GPR copy 问题。

commit 分层分析

1. 高置信根因方向:Ventus divergence/register-class 传播不完整

当前证据能严格确认:完整 linked bitcode codegen 会在物理寄存器 copy 阶段触发 VGPR -> GPR;虚拟寄存器阶段也能看到 VGPR -> GPR 方向的早期 copy 线索。更具体的触发点仍需继续缩小到 PHI、return copy、tuple copy、constant zero lowering、后续 copy 消除未覆盖的路径,或其他 lowering 路径。

根因方向不在 copyPhysReg() 缺少 VGPR -> GPR 实现。相反,copyPhysReg() 拒绝该方向是正确的目标约束。

问题本质是 Ventus 后端在 SelectionDAG/ISel 及后续 PHI/tuple/return copy 路径中,仍会让 divergent/VGPR 值流向 GPR use,产生非法跨域 COPY。

相关逻辑包括:

// llvm/lib/Target/RISCV/RISCVISelLowering.cpp
if (Subtarget.hasVInstructions()) {
  addRegisterClass(MVT::i32, &RISCV::VGPRRegClass);
  addRegisterClass(MVT::f32, &RISCV::VGPRRegClass);
}

以及:

const TargetRegisterClass *
RISCVTargetLowering::getRegClassFor(MVT VT, bool isDivergent) const {
  const TargetRegisterClass *RC = TargetLoweringBase::getRegClassFor(VT, false);
  const RISCVRegisterInfo *TRI = Subtarget.getRegisterInfo();
  if (!TRI->isSGPRClass(RC) && !isDivergent)
    return VT == MVT::i32 ? &RISCV::GPRRegClass : &RISCV::GPRF32RegClass;
  else if (TRI->isSGPRClass(RC) && isDivergent)
    return &RISCV::VGPRRegClass;

  return RC;
}

以及 non-kernel 参数取入 VGPR 的逻辑:

// Setting isDivergent = true is essential to use VGPR
const TargetRegisterClass *RC = TLI.getRegClassFor(LocVT.getSimpleVT(), true);
Register VReg = RegInfo.createVirtualRegister(RC);
RegInfo.addLiveIn(VA.getLocReg(), VReg);
SDValue Val = DAG.getCopyFromReg(Chain, DL, VReg, LocVT);

这些规则组合后,在 soft-float helper 这类既有 divergent 参数又有大量 scalar 控制流/常量/PHI 的函数中,容易让同一个值在不同 use 上被分配到不同 register class。

2. 构建暴露变更:712c32dcb712

712c32dcb712 [Ventus] Build libclc from linked bitcode

该提交将 riscv32clc.o 从 linked bitcode 直接 codegen:

- ${BINARY_DIR}/bin/ld.lld --relocatable ${OBJECT_FILE_LIST} ...
+ ${BINARY_DIR}/bin/clang ... -c "${BUILTINS_BC}" -o "${OUTPUT_OBJ}"

它的 commit message 已明确说明影响:

Building the final riscv32clc.o now runs code generation over the whole
linked libclc module, which may take longer and can expose real backend
issues that were previously hidden by per-file codegen.

这正是当前问题的主要暴露条件。

3. 使 linked bitcode 继续向后暴露的次级变更:同一提交中的 FCVT pattern

712c32dcb712 还增加了:

def : Pat<(i32 (riscv_fcvt_x GPRF32:$rs1, timm:$frm)),
          (FCVT_W_S $rs1, timm:$frm)>;
def : Pat<(i32 (riscv_fcvt_xu GPRF32:$rs1, timm:$frm)),
          (FCVT_WU_S $rs1, timm:$frm)>;

这让当前分支越过了 main 上 linked bitcode 编译会先遇到的 Cannot select RISCVISD::FCVT_X

4. 日志可见变更:518173e25cb6

518173e25cb6 [Ventus] Preserve SFU MMA features on dev-thu base

该提交修改了 RISCVInstrInfo::copyPhysReg(),新增了:

errs() << "Impossible RISCV physreg copy: dst=" << TRI.getName(DstReg)
       << " src=" << TRI.getName(SrcReg) << '\n';
llvm_unreachable("Impossible reg-to-reg copy");

因此当前分支会明确打印 dst/src,例如:

Impossible RISCV physreg copy: dst=X5 src=V0

main 同一位置没有这条 errs(),只有 llvm_unreachable("Impossible reg-to-reg copy")。因此可以确认 518173e25cb6 让这类 impossible copy 的 dst/src 日志变得可见。该提交本身还包含其他后端改动;若要严格证明它没有影响触发路径,需要进一步逐 commit/bisection 验证。

当前判断

分层结论:

已确认的问题:
  完整 linked bitcode codegen 会触发 VGPR -> GPR 物理寄存器 copy;
  物理寄存器分配前能看到 VGPR -> GPR 方向的早期 COPY 线索;
  copyPhysReg() 禁止该方向 copy 是预期行为,不应放宽。

高置信根因方向:
  Ventus SGPR/VGPR divergence/register-class 传播不完整,后端能生成非法 VGPR -> GPR COPY。

构建暴露变更:
  712c32dcb712 [Ventus] Build libclc from linked bitcode。
  它让最终 riscv32clc.o 从完整 linked bitcode 一次性 codegen。

次级暴露条件:
  712c32dcb712 同时修了 main 上 linked bitcode 会先遇到的 FCVT_X selection failure,
  因此当前分支可以继续走到 VGPR/GPR copy 问题。

日志可见变更:
  518173e25cb6 [Ventus] Preserve SFU MMA features on dev-thu base。
  它在 copyPhysReg impossible 分支打印 dst/src;该提交是否还影响触发路径需另行 bisection 才能严格确认。

不建议的修复方向

不建议:

1. 在 copyPhysReg() 中实现 VGPR -> GPR/GPRF32。禁止该方向 copy 是 Ventus SIMT 模型的预期约束,不是待补功能。
2. 恢复或依赖 VMV_X_S / VFMV_F_S,把 per-lane 值静默拉回 scalar 域。
3. 在构建脚本里过滤日志或继续扩大 silent fallback。
4. 关闭 cl_khr_fp64 或跳过 soft-float helper 来绕过。
5. 静默删除非法 COPY。

这些做法会隐藏真正的 SGPR/VGPR 域约束问题。

建议的修复方向

更合理的修复方向是在 ISel / machine pass 层保证 register class 一致性:

1. divergent 值一旦进入 VGPR 域,不应通过普通 COPY 被拉回 GPR。
2. 需要 GPR operand 的 pattern 必须只接受 uniform/SGPR 来源。
3. i32/f32 register class 不能只按类型决定,必须稳定结合 divergence。
4. PHI、return tuple、constant zero、COPY_TO_REGCLASS 等路径需要统一处理。
5. 对 Ventus 增加 verifier,在 finalize-isel 或 Ventus VV conversion 后尽早报出非法 VGPR -> GPR COPY。

已有 VentusFixMixedPHI 能处理一部分 mixed PHI,但当前问题说明覆盖面还不完整,特别是 linked libclc 输入下的 soft-float helper、tuple copy、return value copy 或 constant zero lowering 路径仍可能留下非法形态。

验证方式

修复后至少验证:

bash build-ventus.sh --build libclc > ventus-libclc-build-fixed.log 2>&1
rg -n "Impossible RISCV physreg copy|Illegal copy from VGPR|VGPR to SGPR" \
  ventus-libclc-build-fixed.log

并检查单函数 MIR:

install/bin/llvm-extract --func=__divdf3 \
  build-libclc/builtins.link.riscv32--.bc \
  -o divdf3.fixed.bc

install/bin/llc \
  -mtriple=riscv32 -mcpu=ventus-gpgpu \
  -stop-after=amdgpu-isel \
  divdf3.fixed.bc \
  -o divdf3.after-isel.fixed.mir

rg -n "%[0-9]+:gpr = COPY %[0-9]+|%[0-9]+:gpr = COPY %[0-9]+:vgpr" \
  divdf3.after-isel.fixed.mir

更稳妥的验证方式是新增 MachineFunction verifier,根据 MachineRegisterInfo 查询 src/dst register class,而不是依赖文本 grep。

附件:更多相关讨论总结
dialogue-summary.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions