获取LLVM Value的原始变量名

23
llvm::User(比如指令)的操作数是llvm::Value

mem2reg pass之后,变量以SSA form形式存在,并且它们的名称与原始源代码相对应的名称已经丢失。对于大多数变量(即中介变量),Value :: getName()未设置。

instnamer pass可以被运行以给所有变量命名,例如tmp1tmp2,但这并不反映它们最初来自哪里。下面是一些LLVM IR和原始C代码:

enter image description here

我正在构建一个简单的HTML页面来可视化和调试我正在进行的一些优化工作,并且我希望将SSA变量显示为namever符号,而不仅仅是临时的instnamer名称,以提高可读性。

我通过类似以下命令行的clang获取我的LLVM IR:

 clang -g3 -O1 -emit-llvm -o test.bc -c test.c

在IR中存在对llvm.dbg.declarellvm.dbg.value的调用;如何将它们转换为原始源代码名称和SSA版本号?

那么,我如何从llvm::Value中确定原始变量(或命名常量名称)? 调试器必须能够做到这一点,那么我该怎么办?


3
你用了哪个程序来创建这么好的汇编代码对比和源代码对比? - Jack L.
6
@JackL.,我很快地写了这个东西。它只是一个 JavaScript 画布。当有人通过为值赋予可读的名称<sub>ver</sub> 并获得 500 分时,我可能会发布它,你懂得 ;) - Will
@Will 你最终发布了你的比较工具吗?我猜它对很多人来说都非常有用。 - ransford
@ransford 恐怕不行。而且,可悲的是,我从中间名称到SSA<sub>num</sub>也没有更接近。我的项目遇到了其他困难,比如LLVM不保留指针,我理解这也咬了很多想要精确GC和移植到VLIWs等人的问题 :( - Will
4个回答

13

这是附加到LLVM IR中的元数据形式的调试信息的一部分。文档在这里。还有一个旧的博客文章提供了一些背景可用


$ cat  > z.c
long fact(long arg, long farg, long bart)
{
    long foo = farg + bart;
    return foo * arg;
}

$ clang -emit-llvm -O3 -g -c z.c
$ llvm-dis z.bc -o -
产生这个:
define i64 @fact(i64 %arg, i64 %farg, i64 %bart) #0 {
entry:
  tail call void @llvm.dbg.value(metadata !{i64 %arg}, i64 0, metadata !10), !dbg !17
  tail call void @llvm.dbg.value(metadata !{i64 %farg}, i64 0, metadata !11), !dbg !17
  tail call void @llvm.dbg.value(metadata !{i64 %bart}, i64 0, metadata !12), !dbg !17
  %add = add nsw i64 %bart, %farg, !dbg !18
  tail call void @llvm.dbg.value(metadata !{i64 %add}, i64 0, metadata !13), !dbg !18
  %mul = mul nsw i64 %add, %arg, !dbg !19
  ret i64 %mul, !dbg !19
}

使用-O0代替-O3, 您将看不到llvm.dbg.value,但您将能看到llvm.dbg.declare


我认为理论和实践有所分歧 :( 我从未看到过clang发出任何llvm.dbg.value调用。在我向SO提问之前,我已经阅读了相关文档。 - Will
2
@Will:clang不会发出llvm.dbg.value。当优化将值放入寄存器(而不是更容易访问的堆栈插槽)时,它们会发出这些值。 - Eli Bendersky
好的,那么你如何让mem2reg或其他工具发出它们呢?如果是这样,它们会成为让我将Values转换为源代码名称的秘密武器吗?如果是这样,又该怎么做呢? - Will
clang -O0 生成未经优化的代码,其中不会有 llvm.dbg.value,因为所有本地变量都在堆栈上。您需要生成优化的代码才能看到 llvm.dbg.value - Eli Bendersky
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/46371/discussion-between-will-and-eli-bendersky - Will
显示剩余2条评论

11

给定一个 Value,从中获取变量名称可以通过遍历封闭函数中的所有 llvm.dbg.declarellvm.dbg.value 调用来完成,检查是否有任何引用该值,如果有,则返回与该内部调用关联的 DIVariable

因此,代码应该大致如下(仅供参考,未经测试甚至编译):

const Function* findEnclosingFunc(const Value* V) {
  if (const Argument* Arg = dyn_cast<Argument>(V)) {
    return Arg->getParent();
  }
  if (const Instruction* I = dyn_cast<Instruction>(V)) {
    return I->getParent()->getParent();
  }
  return NULL;
}

const MDNode* findVar(const Value* V, const Function* F) {
  for (const_inst_iterator Iter = inst_begin(F), End = inst_end(F); Iter != End; ++Iter) {
    const Instruction* I = &*Iter;
    if (const DbgDeclareInst* DbgDeclare = dyn_cast<DbgDeclareInst>(I)) {
      if (DbgDeclare->getAddress() == V) return DbgDeclare->getVariable();
    } else if (const DbgValueInst* DbgValue = dyn_cast<DbgValueInst>(I)) {
      if (DbgValue->getValue() == V) return DbgValue->getVariable();
    }
  }
  return NULL;
}

StringRef getOriginalName(const Value* V) {
  // TODO handle globals as well

  const Function* F = findEnclosingFunc(V);
  if (!F) return V->getName();

  const MDNode* Var = findVar(V, F);
  if (!Var) return "tmp";

  return DIVariable(Var).getName();
}

您可以看到,我懒得处理全局变量,但事实上这并不是很重要 - 这需要迭代当前编译单元调试信息下列出的所有全局变量(使用M.getNamedMetadata("llvm.dbg.cu")获取当前模块中所有编译单元的列表),然后检查哪个与您的变量匹配(通过getGlobal方法),并返回它的名称。

然而,请注意,以上仅适用于与原始变量直接关联的值。任何计算结果产生的值都无法以这种方式正确命名;特别是,表示字段访问的值将不会用字段名称命名。这是可行的,但需要更复杂的处理 - 您将不得不从GEP中识别字段编号,然后深入结构体类型调试信息以获取字段名称。调试器确实这样做,但没有一个调试器在LLVM IR领域工作 - 就我所知,即使是LLVM自己的LLDB也是通过将对象文件中的DWARF解析为Clang类型来运行的。


1
非常好,而且与我找到正确名称的方式非常相似(尽管我一直在制作函数符号表的反向映射以加快速度)。然而,问题出在“但是”部分上;几乎所有IR中的变量都是临时变量,虽然作为人类,您可以追踪它们并查看它们来自哪里。 - Will
看起来它不再起作用了(在此答案之后,调试值进行了重大改进,现在它们不再返回Value *等其他内容),我不确定现在正确的做法是什么。 - Dan M.

1
如果您正在使用最新版本的Clang,其他一些方法可能无法正常工作。 相反,请使用clang的-fno-discard-value-names标志。这将使llvm::Values保留其原始名称。

0

2
嗨,这些链接很有帮助,但我仍然无法实现这个。你能详细说明一下你是如何获取原始变量名称的吗?谢谢。 - nicolas-mosch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接