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

LLVM Pass开发:如何定位全局变量首次使用位置并应用转换

定位全局变量首次使用并执行一次性转换的LLVM方案

针对你的需求——在全局变量首次执行使用前仅执行一次转换,LLVM没有直接提供全局范围的DominatorTree,但可以通过调用图分析+函数内支配树的组合方案实现,完全不需要创建预处理存根函数。

核心思路

  1. 先确定程序的可达函数(从入口点如main出发,通过调用图遍历所有会被执行到的函数),过滤掉未被调用的死代码中的全局变量使用。
  2. 对每个可达函数,收集其中目标全局变量的所有使用指令。
  3. 结合函数内的DominatorTree,找到该函数内所有全局变量使用的公共支配块——也就是所有执行路径中一定会最先到达的代码位置,在该位置前插入转换逻辑。
  4. 一旦找到第一个符合条件的插入点并完成转换,立即标记该全局变量为已处理,后续所有使用都跳过操作。

具体实现步骤

1. 构建调用图,遍历可达函数

通过LLVM的CallGraphWrapperPass获取模块的调用图,从入口函数(通常是main)出发,广度优先或深度优先遍历所有可达函数,避免处理死代码。

2. 收集函数内的全局变量使用指令

对每个可达函数,遍历其所有基本块和指令,筛选出加载/存储目标全局变量的指令(比如LoadInst或StoreInst)。

3. 利用支配树定位最早插入点

对包含全局变量使用的函数,构建DominatorTree,找到所有使用指令所在基本块的最近公共支配块——这个块是所有执行路径中必然会先于任何全局变量使用到达的位置,在该块的首个非PHI指令前插入转换代码即可保证在首次使用前执行。

代码示例(LLVM Pass)

#include "llvm/Analysis/CallGraph.h"
#include "llvm/Analysis/DominatorTree.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Pass.h"
#include "llvm/Support/raw_ostream.h"
#include <queue>
#include <set>
#include <vector>

using namespace llvm;

namespace {
struct GlobalTransformPass : public ModulePass {
  static char ID;
  GlobalTransformPass() : ModulePass(ID) {}

  bool runOnModule(Module &M) override {
    // 目标全局变量
    GlobalVariable *TargetGV = M.getNamedGlobal("MyGlobal");
    if (!TargetGV || TargetGV->isDeclaration()) return false;

    // 获取程序入口函数(这里假设是main,可根据场景调整为_start等)
    Function *EntryFunc = M.getFunction("main");
    if (!EntryFunc) return false;

    // 获取调用图
    CallGraph &CG = getAnalysis<CallGraphWrapperPass>().getCallGraph();

    bool IsGVProcessed = false;
    std::set<Function*> VisitedFuncs;
    std::queue<Function*> Worklist;
    Worklist.push(EntryFunc);
    VisitedFuncs.insert(EntryFunc);

    while (!Worklist.empty() && !IsGVProcessed) {
      Function *CurrentFunc = Worklist.front();
      Worklist.pop();

      if (CurrentFunc->isDeclaration()) continue;

      // 收集当前函数中所有使用目标全局变量的指令
      std::vector<Instruction*> GVUses;
      for (BasicBlock &BB : *CurrentFunc) {
        for (Instruction &I : BB) {
          // 匹配加载全局变量的指令,可扩展到存储或其他使用场景
          if (LoadInst *LI = dyn_cast<LoadInst>(&I)) {
            if (LI->getPointerOperand() == TargetGV) {
              GVUses.push_back(LI);
            }
          }
        }
      }

      if (GVUses.empty()) {
        // 当前函数不使用目标GV,继续遍历它调用的函数
        for (auto &Edge : CG[CurrentFunc]) {
          Function *Callee = Edge.second->getFunction();
          if (Callee && !VisitedFuncs.count(Callee)) {
            VisitedFuncs.insert(Callee);
            Worklist.push(Callee);
          }
        }
        continue;
      }

      // 构建当前函数的支配树,找到所有使用点的公共支配块
      DominatorTree &DT = getAnalysis<DominatorTreeWrapperPass>(*CurrentFunc).getDomTree();
      BasicBlock *CommonDomBB = GVUses[0]->getParent();
      for (Instruction *Use : GVUses) {
        CommonDomBB = DT.findNearestCommonDominator(CommonDomBB, Use->getParent());
      }

      // 确定插入位置:跳过PHI节点,选块内第一个可插入指令的位置
      Instruction *InsertPt = &CommonDomBB->front();
      while (isa<PHINode>(InsertPt)) {
        InsertPt = InsertPt->getNextNode();
      }

      // 在这里插入你的转换逻辑
      // 示例:插入一个自定义转换函数的调用
      // Function *TransformFunc = M.getFunction("TransformMyGlobal");
      // if (TransformFunc) {
      //   CallInst::Create(TransformFunc, {}, "", InsertPt);
      // }

      errs() << "已在函数" << CurrentFunc->getName() << "的基本块" 
             << CommonDomBB->getName() << "中插入全局变量转换逻辑\n";

      // 标记全局变量已处理,终止遍历
      IsGVProcessed = true;
      break;
    }

    return IsGVProcessed;
  }

  void getAnalysisUsage(AnalysisUsage &AU) const override {
    AU.addRequired<CallGraphWrapperPass>();
    AU.addRequired<DominatorTreeWrapperPass>();
    AU.setPreservesCFG();
  }
};
} // namespace

char GlobalTransformPass::ID = 0;
static RegisterPass<GlobalTransformPass> X("global-transform", "全局变量首次使用前转换Pass");

关键注意事项

  • 间接调用处理:如果程序存在函数指针等间接调用,CallGraph可能无法完全捕获,此时可选择保守处理(假设所有函数都是可达的),或结合指针分析增强调用图准确性。
  • 多线程场景:如果程序是多线程的,首次使用可能在多个线程中触发,需要额外添加线程同步逻辑(如原子操作、锁)保证转换仅执行一次。
  • 入口点适配:非标准程序(如嵌入式、内核)需将入口函数从main改为对应启动函数(如_start)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 01:40:23