You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LLVM IR到RISC-V汇编映射:如何替换ADD与SUB指令行为

实现RISC-V ADD与SUB指令的逻辑互换

为什么不能直接改.td文件?

你说的没错,RISCVInstrInfo.td里的定义只是描述指令的编码、名称和操作数格式,LLVM的指令选择逻辑是把IR层的add/sub映射到对应的目标指令节点,改.td只会换指令名或编码,不会改变逻辑映射关系。要实现C加法变二进制减法,得在IR到机器指令的转换流程里动手。

两种可行的实现方式

方式一:在SelectionDAG Lowering阶段替换操作

直接在RISCVISelLowering.cpp里修改IR节点到目标指令节点的映射:
找到LowerOperation函数,对ISD::ADD和ISD::SUB节点做反向映射:

SDValue RISCVTargetLowering::LowerOperation(SDValue Op, SelectionDAG &DAG) const {
  switch (Op.getOpcode()) {
  case ISD::ADD: {
    // 把IR的ADD转换成RISC-V的SUB节点
    SDValue lhs = Op.getOperand(0);
    SDValue rhs = Op.getOperand(1);
    return DAG.getNode(RISCVISD::SUB, SDLoc(Op), Op.getValueType(), lhs, rhs);
  }
  case ISD::SUB: {
    // 把IR的SUB转换成RISC-V的ADD节点
    SDValue lhs = Op.getOperand(0);
    SDValue rhs = Op.getOperand(1);
    return DAG.getNode(RISCVISD::ADD, SDLoc(Op), Op.getValueType(), lhs, rhs);
  }
  // 其他case保持原样,不要动
  default:
    return SDValue();
  }
}

这个方法是在指令选择的早期阶段修改,后续的优化和发射都会基于替换后的节点处理。

方式二:在MachineInstr阶段直接替换操作码

如果觉得SelectionDAG的逻辑太绕,可以等机器指令生成后,直接遍历替换操作码,更直观:

  1. 写一个自定义的MachineFunctionPass:
struct SwapAddSubPass : public MachineFunctionPass {
  static char ID;
  SwapAddSubPass() : MachineFunctionPass(ID) {}

  bool runOnMachineFunction(MachineFunction &MF) override {
    for (auto &MBB : MF) {
      for (auto &MI : MBB) {
        // 直接替换ADD和SUB的操作码
        if (MI.getOpcode() == RISCV::ADD) {
          MI.setOpcode(RISCV::SUB);
        } else if (MI.getOpcode() == RISCV::SUB) {
          MI.setOpcode(RISCV::ADD);
        }
      }
    }
    return true;
  }
};

char SwapAddSubPass::ID = 0;
static RegisterPass<SwapAddSubPass> X("swap-add-sub", "Swap ADD/SUB instructions for RISC-V");
  1. 在RISCVTargetMachine.cpp里把这个Pass加到后端的Pass序列中,确保在指令发射前执行(比如插在RISCVCodeGenPrepare之后)。

验证步骤

  1. 重新编译llvm-project,生成修改后的clang和llc;
  2. 写测试C代码:
int main() {
  int a = 10, b = 4;
  int c = a + b; // 实际会执行10-4=6
  return c;
}
  1. 用修改后的clang编译:clang -target riscv64-unknown-linux-gnu test.c -o test;
  2. 反编译看结果:objdump -d test,main函数里的加法指令应该变成subw(RV64)或sub(RV32)。

注意点

  • RISC-V的ADD和SUB都是R型指令,操作数顺序完全一致,所以直接替换操作码不会有问题;
  • 如果需要区分有符号/无符号加法,可以在Lowering阶段针对ISD::SADD/ISD::UADD分别处理;
  • 虽然CodeGen和SelectionDAG的文档确实难啃,但从这种小修改入手,跟着代码调用链走,慢慢就能摸清楚后端的流程。

内容的提问来源于stack exchange,提问作者Ilyass

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 18:34:57