什么c++代码可以在LLVM中生成phi节点

What c++ code can generate phi node in LLVM

理论上,当无法静态确定变量赋值时,就会出现 phi 节点。但是下面的代码并没有像预期的那样生成 phi 节点:

clang++ -c -emit-llvm -fno-discard-value-names -S -o test.ll test.cpp
struct someClass
{
    int a;
    char* b;
    bool c;
    someClass *next;
};

someClass* m(bool r, bool y, someClass* c){
    bool l = y || r ;
    if(l)
        return c;
    else
        return nullptr;
}

someClass* phiFunc(int a, int b, someClass *c) {
  if (a > b) {
    c = m(false, true, c);
  } else {
    c = m(true, false, c);
  }
  someClass* ret = m(true, true, c); // <<--- phi node expected here.
  return ret;
}

c 更改为 int 或添加 -O0 也没有任何区别。 通过查看下面生成的 IR,我们可以看到 c.addr 分别存储在 if.thenif.else 分支中的寄存器 callcall1 中。 但是加上--print-memoryssa,我觉得问题出在为什么内存phi出现编译器不生成phi节点

opt-10 --print-memoryssa -S test.ll
; Function Attrs: noinline nounwind optnone uwtable
define dso_local %struct.someClass* @_Z7phiFunciiP9someClass(i32 %a, i32 %b, %struct.someClass* %c) #0 {
entry:
  %a.addr = alloca i32, align 4
  %b.addr = alloca i32, align 4
  %c.addr = alloca %struct.someClass*, align 8
  %ret = alloca %struct.someClass*, align 8
; 1 = MemoryDef(liveOnEntry)
  store i32 %a, i32* %a.addr, align 4
; 2 = MemoryDef(1)
  store i32 %b, i32* %b.addr, align 4
; 3 = MemoryDef(2)
  store %struct.someClass* %c, %struct.someClass** %c.addr, align 8
; MemoryUse(1) MustAlias
  %0 = load i32, i32* %a.addr, align 4
; MemoryUse(2) MustAlias
  %1 = load i32, i32* %b.addr, align 4
  %cmp = icmp sgt i32 %0, %1
  br i1 %cmp, label %if.then, label %if.else

if.then:                                          ; preds = %entry
; MemoryUse(3) MustAlias
  %2 = load %struct.someClass*, %struct.someClass** %c.addr, align 8
; 4 = MemoryDef(3)
  %call = call %struct.someClass* @_Z1mbbP9someClass(i1 zeroext false, i1 zeroext true, %struct.someClass* %2)
; 5 = MemoryDef(4)
  store %struct.someClass* %call, %struct.someClass** %c.addr, align 8
  br label %if.end

if.else:                                          ; preds = %entry
; MemoryUse(3) MustAlias
  %3 = load %struct.someClass*, %struct.someClass** %c.addr, align 8
; 6 = MemoryDef(3)
  %call1 = call %struct.someClass* @_Z1mbbP9someClass(i1 zeroext true, i1 zeroext false, %struct.someClass* %3)
; 7 = MemoryDef(6)
  store %struct.someClass* %call1, %struct.someClass** %c.addr, align 8
  br label %if.end

if.end:                                           ; preds = %if.else, %if.then
; 10 = MemoryPhi({if.then,5},{if.else,7})
; MemoryUse(10) MayAlias
  %4 = load %struct.someClass*, %struct.someClass** %c.addr, align 8
; 8 = MemoryDef(10)
  %call2 = call %struct.someClass* @_Z1mbbP9someClass(i1 zeroext true, i1 zeroext true, %struct.someClass* %4)
; 9 = MemoryDef(8)
  store %struct.someClass* %call2, %struct.someClass** %ret, align 8
; MemoryUse(9) MustAlias
  %5 = load %struct.someClass*, %struct.someClass** %ret, align 8
  ret %struct.someClass* %5
}

使用mem2reg优化也无济于事:

➜  ~ opt -mem2reg -S test.ll -o test-opt.ll
➜  ~ diff test.ll test-opt.ll
1c1
< ; ModuleID = 'test.cpp'
---
> ; ModuleID = 'test.ll'

如何让编译器按预期生成 phi 节点?

谢谢!

请参阅函数声明上方评论中的 optnone

; Function Attrs: noinline nounwind optnone uwtable

clang默认的优化级别是-O0,表示不优化,clang标记所有函数禁止进一步优化。将 -Xclang -disable-O0-optnone 传递给 clang 以允许 LLVM 的 mem2reg 工作。

$ clang++ -Xclang -disable-O0-optnone -c -emit-llvm -fno-discard-value-names -S -o - 72123225.cc | opt -mem2reg -S -o - | llvm-extract --func=_Z7phiFunciiP9someClass | llvm-dis
; ModuleID = '<stdin>'
source_filename = "72123225.cc"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"

%struct.someClass = type { i32, i8*, i8, %struct.someClass* }

; Function Attrs: noinline nounwind uwtable
declare dso_local %struct.someClass* @_Z1mbbP9someClass(i1 zeroext, i1 zeroext, %struct.someClass*) #0

; Function Attrs: noinline nounwind uwtable
define dso_local %struct.someClass* @_Z7phiFunciiP9someClass(i32 %a, i32 %b, %struct.someClass* %c) #0 {
entry:
  %cmp = icmp sgt i32 %a, %b
  br i1 %cmp, label %if.then, label %if.else

if.then:                                          ; preds = %entry
  %call = call %struct.someClass* @_Z1mbbP9someClass(i1 zeroext false, i1 zeroext true, %struct.someClass* %c)
  br label %if.end

if.else:                                          ; preds = %entry
  %call1 = call %struct.someClass* @_Z1mbbP9someClass(i1 zeroext true, i1 zeroext false, %struct.someClass* %c)
  br label %if.end

if.end:                                           ; preds = %if.else, %if.then
  %c.addr.0 = phi %struct.someClass* [ %call, %if.then ], [ %call1, %if.else ]
  %call2 = call %struct.someClass* @_Z1mbbP9someClass(i1 zeroext true, i1 zeroext true, %struct.someClass* %c.addr.0)
  ret %struct.someClass* %call2
}

attributes #0 = { noinline nounwind uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="all" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.module.flags = !{!0}
!llvm.ident = !{!1}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{!"Debian clang version 11.1.0-6+b2"}