If the lifetime-extended temporary object is not trivially destructible, there will be a ExprWithCleanups generated for the VarDecl, so calling CodeGenFunction::pushCleanupAfterFullExpr to push a llvm.lifetime.end() call as a cleanup (as clang currently does in non-temporary cases) just works.
If there is a ExprWithCleanups generated even for a trivially destructible temporary, generating llvm.lifetime.end() is easy by calling CodeGenFunction::pushCleanupAfterFullExpr.
This requires moving L5593 to L5550 in SemaExprCXX.cpp. The two-lines change breaks 36 tests, most of which are checking for warnings, so I suppose I also need to fix some warning detections.
Is this - by adding more ExprWithCleanups and fix tests - the correct way to fix the temporary lifetime bug? I feel weird about the existence of ExprWithCleanups in the first place, but that’s off the topic.
Just for illustration, the attached patch fixes the bug, but doesn’t fix the tests though.
As discussed off the list, ExprWithCleanups should be used for dtors only. So another solution can be creating a new LifetimeEndStack in CodeGenFunction, which is handled separately.
The problem of this solution is that, since LifetimeEnd marks and dtor calls are not in the same stack, they are not emitted interleaved. The result will be like:
call void dtor2();
call void dtor1();
llvm.lifetime.end(t2);
llvm.lifetime.end(t1);
instead of, ideally:
call void dtor2();
llvm.lifetime.end(t2);
call void dtor1();
llvm.lifetime.end(t1);
This approach is good enough to reduce the stack frame size, though.
"The lifetime of an object of type T ends when:
— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
— the storage which the object occupies is reused or released."
That is, for temporaries that have a trivial destructor or temporaries of a non-class type, the lifetime extends until the storage goes away (after all relevant destructors have run).
So, given:
struct A { ~A(); };
struct B { ~B(); };
void f(int&&);
( A(), f(0), B() );
… should clean up as follows:
call ~B()
end lifetime of B temporary
call ~A()
end lifetime of A temporary
end lifetime of int temporary
That’s obviously stupid, but that’s what the language rules require today. (I’m trying to get this fixed but have not succeeded yet.) It’s valid for us to do this:
It looks like the problem there is that we start the lifetime too late (in
C), not that we end it too early. But I suppose with our stack-based
approach to cleanups, these are closely-related issues