Sometimes throwing constructors / destructors

I am a bit puzzled when looking at the IR generated for the following testcase.

Why don’t we get an invoke for the first temporary constructor or destructor call ?

At the same time, at other places in the exception path, the destructor for the first temporary is assumed to be throwing as it is invoked (and not just called).

$ cat testcase.cpp

struct X {

X();

~X();

};

extern void use(const X &, const X &);

void test() {

use(X(), X());

}

$ clang++ -O1 –S –emit-llvm –o – testcase.cpp

; Function Attrs: uwtable

define void @_Z4testv() #0 {

%1 = alloca %struct.X, align 1

%2 = alloca %struct.X, align 1

call void @_ZN1XC1Ev(%struct.X* %1) ; ç non throwing constructor ?

invoke void @_ZN1XC1Ev(%struct.X* %2)

to label %3 unwind label %6

; :3 ; preds = %0

invoke void @Z3useRK1XS1(%struct.X* dereferenceable(1) %1, %struct.X* dereferenceable(1) %2)

to label %4 unwind label %10

; :4 ; preds = %3

invoke void @_ZN1XD1Ev(%struct.X* %2)

to label %5 unwind label %6

; :5 ; preds = %4

call void @_ZN1XD1Ev(%struct.X* %1) ; ç non throwing destructor ?

ret void

; :6 ; preds = %4, %0

%7 = landingpad { i8*, i32 } personality i8* bitcast (i32 (…)* @__gxx_personality_v0 to i8*)

cleanup

%8 = extractvalue { i8*, i32 } %7, 0

%9 = extractvalue { i8*, i32 } %7, 1

br label %14

; :10 ; preds = %3

%11 = landingpad { i8*, i32 } personality i8* bitcast (i32 (…)* @__gxx_personality_v0 to i8*)

cleanup

%12 = extractvalue { i8*, i32 } %11, 0

%13 = extractvalue { i8*, i32 } %11, 1

invoke void @_ZN1XD1Ev(%struct.X* %2)

to label %14 unwind label %18

; :14 ; preds = %10, %6

%.01 = phi i8* [ %8, %6 ], [ %12, %10 ]

%.0 = phi i32 [ %9, %6 ], [ %13, %10 ]

invoke void @_ZN1XD1Ev(%struct.X* %1)

to label %15 unwind label %18 ; ç throwing destructor ?

; :15 ; preds = %14

%16 = insertvalue { i8*, i32 } undef, i8* %.01, 0

%17 = insertvalue { i8*, i32 } %16, i32 %.0, 1

resume { i8*, i32 } %17

; :18 ; preds = %14, %10

%19 = landingpad { i8*, i32 } personality i8* bitcast (i32 (…)* @__gxx_personality_v0 to i8*)

catch i8* null

%20 = extractvalue { i8*, i32 } %19, 0

call void @__clang_call_terminate(i8* %20) #3

unreachable

}

Hi Arnaud,

Why don’t we get an invoke for the first temporary constructor or destructor
call ?

Isn't it because there's nothing to clean up if the very first
constructor called fails, so unwinding can just pass through this
frame without action? However, if the second one fails you have to
destroy the first object properly.

Similarly there's nothing that needs to be done if the very last
destructor throws.

Cheers.

Tim.

Hi Tim,

I understand your point but what bothers me is that if the first constructor call fails, I agree that there is no destructions to do, but the fact that it failed should still be signalled (with a resume, not a ret) so that I can be handled properly.

Cheers,
Arnaud

I think you misunderstand how exceptions work in LLVM IR. If a function that is called (not invoked) throws an exception, then the unwinder will not find a landing pad in the function and so will keep going. No code after the call will be reached in the case that an exception is thrown. The following are equivalent:

  call @foo()
  br label %1

and

  invoke @foo() to label %1 unwind to label %2

: <label>%2
  resume

The only difference is that, if an exception *is* thrown (and no optimisers turned the second form into the first) then the first form will be faster at run time because the personality function will not find a cleanup, call back into the function, then be called back. The unwind tables and generated code will also be smaller.

David

Yes that's it. Thanks for the explanations !

Cheers,
Arnaud