Why does Clang generate duplicated bitcode for the constructor of a virtual inheritance class?

When working with virtual inheritance in C++, I notice that Clang seems to generate duplicated LLVM IR for a virtual inheritance class. Here is my test code:

// test.c
#include <stdio.h>

class A {
  public:
    virtual int f() { return 1; }
};

class B: public A {
};

class C: virtual public A {
};

int main(int argc, char **argv) {
  B *pb = new B;
  C *pc = new C;                 // Clang generates duplicate IR for the constructor of C
  return 0;
}

Here is my compilation command:

clang virtual-inheritance-1-test.cpp -O0 -c -emit-llvm -fno-rtti

And here is part of the generated bitcode:

%class.B = type { %class.A }
%class.A = type { i32 (...)** }
%class.C = type { %class.A }

@_ZTV1B = linkonce_odr dso_local unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%class.A*)* @_ZN1A1fEv to i8*)] }, comdat, align 8
@_ZTV1A = linkonce_odr dso_local unnamed_addr constant { [3 x i8*] } { [3 x i8*] [i8* null, i8* null, i8* bitcast (i32 (%class.A*)* @_ZN1A1fEv to i8*)] }, comdat, align 8
@_ZTV1C = linkonce_odr dso_local unnamed_addr constant { [5 x i8*] } { [5 x i8*] [i8* null, i8* null, i8* null, i8* null, i8* bitcast (i32 (%class.A*)* @_ZN1A1fEv to i8*)] }, comdat, align 8
@_ZTT1C = linkonce_odr dso_local unnamed_addr constant [2 x i8*] [i8* bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1C, i32 0, inrange i32 0, i32 4) to i8*), i8* bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1C, i32 0, inrange i32 0, i32 4) to i8*)], comdat, align 8

; Function Attrs: noinline norecurse optnone uwtable
define dso_local i32 @main(i32 %0, i8** %1) #0 {
  %3 = alloca i32, align 4
  %4 = alloca i32, align 4
  %5 = alloca i8**, align 8
  %6 = alloca %class.B*, align 8
  %7 = alloca %class.C*, align 8
  store i32 0, i32* %3, align 4
  store i32 %0, i32* %4, align 4
  store i8** %1, i8*** %5, align 8
  %8 = call i8* @_Znwm(i64 8) #3
  %9 = bitcast i8* %8 to %class.B*
  call void @_ZN1BC2Ev(%class.B* %9) #4
  store %class.B* %9, %class.B** %6, align 8
  %10 = call i8* @_Znwm(i64 8) #3
  %11 = bitcast i8* %10 to %class.C*
  call void @_ZN1CC1Ev(%class.C* %11) #4
  store %class.C* %11, %class.C** %7, align 8
  ret i32 0
}

; Function Attrs: nobuiltin
declare dso_local noalias i8* @_Znwm(i64) #1

; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local void @_ZN1BC2Ev(%class.B* %0) unnamed_addr #2 comdat align 2 {
  %2 = alloca %class.B*, align 8
  store %class.B* %0, %class.B** %2, align 8
  %3 = load %class.B*, %class.B** %2, align 8
  %4 = bitcast %class.B* %3 to %class.A*
  call void @_ZN1AC2Ev(%class.A* %4) #4
  %5 = bitcast %class.B* %3 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [3 x i8*] }, { [3 x i8*] }* @_ZTV1B, i32 0, inrange i32 0, i32 2) to i32 (...)**), i32 (...)*** %5, align 8
  ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local void @_ZN1CC1Ev(%class.C* %0) unnamed_addr #2 comdat align 2 {
  %2 = alloca %class.C*, align 8
  store %class.C* %0, %class.C** %2, align 8
  %3 = load %class.C*, %class.C** %2, align 8
  %4 = bitcast %class.C* %3 to %class.A*
  call void @_ZN1AC2Ev(%class.A* %4) #4
  %5 = bitcast %class.C* %3 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1C, i32 0, inrange i32 0, i32 4) to i32 (...)**), i32 (...)*** %5, align 8
  %6 = bitcast %class.C* %3 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1C, i32 0, inrange i32 0, i32 4) to i32 (...)**), i32 (...)*** %6, align 8
  ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local void @_ZN1AC2Ev(%class.A* %0) unnamed_addr #2 comdat align 2 {
  %2 = alloca %class.A*, align 8
  store %class.A* %0, %class.A** %2, align 8
  %3 = load %class.A*, %class.A** %2, align 8
  %4 = bitcast %class.A* %3 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [3 x i8*] }, { [3 x i8*] }* @_ZTV1A, i32 0, inrange i32 0, i32 2) to i32 (...)**), i32 (...)*** %4, align 8
  ret void
}

; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local i32 @_ZN1A1fEv(%class.A* %0) unnamed_addr #2 comdat align 2 {
  %2 = alloca %class.A*, align 8
  store %class.A* %0, %class.A** %2, align 8
  %3 = load %class.A*, %class.A** %2, align 8
  ret i32 1
}

I notice in the constructor of the class C , there is a duplication of IR generated for %5 and %6 .

; Function Attrs: noinline nounwind optnone uwtable
define linkonce_odr dso_local void @_ZN1CC1Ev(%class.C* %0) unnamed_addr #2 comdat align 2 {
  %2 = alloca %class.C*, align 8
  store %class.C* %0, %class.C** %2, align 8
  %3 = load %class.C*, %class.C** %2, align 8
  %4 = bitcast %class.C* %3 to %class.A*
  call void @_ZN1AC2Ev(%class.A* %4) #4
  %5 = bitcast %class.C* %3 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1C, i32 0, inrange i32 0, i32 4) to i32 (...)**), i32 (...)*** %5, align 8
  %6 = bitcast %class.C* %3 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1C, i32 0, inrange i32 0, i32 4) to i32 (...)**), i32 (...)*** %6, align 8
  ret void
}

In particular, the two pairs of following instructions of _ZN1CC1Ev seem to perform exactly the same action.

  %5 = bitcast %class.C* %3 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1C, i32 0, inrange i32 0, i32 4) to i32 (...)**), i32 (...)*** %5, align 8

and

  %6 = bitcast %class.C* %3 to i32 (...)***
  store i32 (...)** bitcast (i8** getelementptr inbounds ({ [5 x i8*] }, { [5 x i8*] }* @_ZTV1C, i32 0, inrange i32 0, i32 4) to i32 (...)**), i32 (...)*** %6, align 8

Does anybody know why such duplicated code are generated?

Thank you for spending your time on my question!

Hi did you dig further on this. I too had the same question.

No, I haven’t been able to figure it out