请问如何通过写一个LLVM pass把原程序中浮点型变量的类型改成long double?

How to change the type of float-point variables in the original program to long double by writing a LLVM pass please?

我正在编写一个LLVM pass,将原程序中的浮点变量类型更改为long double。一个玩具测试程序是:

int main(){
    double i = 0.0000000000000001;
    if(i + 1 > 1)
        printf("correct answer");
    else
        printf("wrong answer");
    return 0;
}

我的通行证需要将 i 的类型更改为 "long double"。 原程序的IR和改造后的程序有五个不同的地方。

<   %i = alloca x86_fp80, align 16
---
>   %i = alloca double, align 8

<   store x86_fp80 0xK3FC9E69594BEC44DE000, x86_fp80* %i, align 16
<   %0 = load x86_fp80, x86_fp80* %i, align 16
<   %add = fadd x86_fp80 %0, 0xK3FFF8000000000000000
<   %cmp = fcmp ogt x86_fp80 %add, 0xK3FFF8000000000000000
---
>   store double 1.000000e-16, double* %i, align 8
>   %0 = load double, double* %i, align 8
>   %add = fadd double %0, 1.000000e+00
>   %cmp = fcmp ogt double %add, 1.000000e+00

我做上述变换的pass的大纲如下:

virtual bool runOnFunction(Function &F) {
    std::string svariable ("i");
    const ValueSymbolTable& symbolTable = F.getValueSymbolTable();
    Value* target = symbolTable.lookup(svariable);
    AllocaInst* old_target = dyn_cast<AllocaInst>(target);
    errs() <<"old_target: " << *target << "\n";
    errs() <<"num of old_target_uses:" << old_target->getNumUses() <<"\n";

    //get the type of long double and construct new AllocaInst
    LLVMContext &context = F.getContext();
    Type* type = Type::getX86_FP80Ty(context);
    unsigned alignment = 16;
    AllocaInst* new_target = new AllocaInst(type, 0, alignment, "new", old_target);
    new_target->takeName(old_target);

    // iterating through instructions using old AllocaInst
    Value::use_iterator it = old_target->use_begin();
    for(; it != old_target->use_end(); it++) {
          Value * temp = it->get();
          errs() <<"temp:" << *temp <<"\n";
          //transform() is under construction
          transform(it, new_target, type, alignment);

    }
    old_target->eraseFromParent();
    return false;
}

原程序IR中与double i相关的指令应该有4条:

>   store double 1.000000e-16, double* %i, align 8
>   %0 = load double, double* %i, align 8
>   %add = fadd double %0, 1.000000e+00
>   %cmp = fcmp ogt double %add, 1.000000e+00

但是pass的输出和上面预期的不一样:

old_target: %i = alloca double, align 8
num of old_target_uses:2
temp:  %0 = alloca double, align 8
temp:  %0 = alloca double, align 8

所以我的第一个问题是为什么 getNumUses() 和 use_iterator 没有 return 正确答案,我是不是在我的通行证大纲中以错误的方式使用了它们?

我的第二个问题是在我的 transform() 函数中,我需要枚举每一种指令,例如 LoadInst、StoreInst、BinaryOperation 并用新的类型重建它们,对吗?

非常感谢:)

关于你的第一个问题,每个 Use 对象基本上是数据流图中的一条边,将 Value (主要是指令或常量)链接到它的 Users (指令或常量)。这两个值都可以分别通过 Use::getUse::getUser 访问。

Value::use_iterator it = old_target->use_begin();
for(; it != old_target->use_end(); it++) {
      Value * temp = it->get();
}

这里,当你迭代old_target的uses并且将temp赋值给每个use的used值时,赋值的实际上是old_target本身。我相信您想要的是 it->getUser,这将是一个 用户,每次都不一样。

请注意 getNumUses() 实际上是正确的,因为在您的示例中 %i 使用了两次,第一次在 store 中,然后在 load.[=49= 中]

My second question is in my transform() function, I need to enumerate every kind of instruction such as LoadInst, StoreInst, BinaryOperation and reconstruct them with the new type, right?

至于实际替换类型,我想这就是这里所需要的。请注意,通常以这种方式替换类型可能会导致不正确的结果,因此您可能需要先检查没有 bitcastptrtoint 等指令对这些变量进行操作。我建议您从一开始就只支持 allocas 源和对这些源进行操作的有限指令子集,并从这里扩展子集。

然后,您将以某种方式转换每个用户,以适应其操作数之一从 double/double*x86_fp80/x86_fp80* 的更改类型。如果其结果类型发生变化,您还需要传播它。为此,您可能会发现工作列表模式很有帮助——这就是 LLVM 本身组织的遍数 (example)。

更新 IR 的常用方法是 Value::replaceAllUsesWith。但是,在您的情况下,类型很可能也会更改,这会导致此函数失败并显示错误消息。因此您需要手动更改 IR 和类型,可能使用 User::setOperand, Value::mutateType 的某种组合,并在需要时创建新指令。

例如:

  • 对于loadfaddfcmp和类似指令,替换操作数并将结果添加到工作列表;
  • 对于store指令:
    • 如果第一个操作数的类型已更改,则中止处理,除非第二个操作数的类型也已更改;
    • 如果第二个操作数的类型改变了,但第一个操作数的类型没有改变,将第一个操作数转换为x86_fp80(with fpext);
    • 无论如何,将结果添加到工作列表中;
  • 对于 callinvoke 指令,使用 fptruncx86_fp80 操作数转换为 double (或者中止,如果你想禁止这样的向下转换) ;
  • 对于 bitcastptrtoint 指令,中止处理;