Why this dead store of unique_ptr cannot be eliminated?










3















#include <memory>
#include <vector>
using namespace std;

vector<unique_ptr<int>> e;

void f(unique_ptr<int> u)
e.emplace_back(move(u));



For both Clang and GCC, the above code snippet generates something like:



f(std::unique_ptr<int, std::default_delete<int> >):
mov rsi, QWORD PTR e[rip+8] # rsi: vector.end_ptr
cmp rsi, QWORD PTR e[rip+16] # [e + rip + 16]: vector.storage_end_ptr
je .L52 # Slow path, need to reallocate
mov rax, QWORD PTR [rdi] # rax: unique_ptr<int> u
add rsi, 8 # end_ptr += 8
mov QWORD PTR [rdi], 0 # <==== Do we need to set the argument u to null here?
mov QWORD PTR [rsi-8], rax # *(end_ptr - 8) = u
mov QWORD PTR e[rip+8], rsi # update end_ptr
ret
.L52: # omitted


I was wondering why does the compiler generate mov QWORD PTR[rdi], 0 in this function? Is there any convention that requires compiler to do so?



Moreover, for simpler case like this:



void f(unique_ptr<int> u);

void h(int x)
auto p = make_unique<int>(x);
f(move(p));



Why does the compiler generate:



call operator delete(void*, unsigned long)


at the end of h(), given that p is always nullptr after the invocation of f?










share|improve this question






















  • A preliminary guess: Itanium ABI requires the caller to handle the construction/destruction of argument. The operator delete is actually called for the copied argument, u. Therefore, the callee needs to set the unique_ptr to nullptr to ask for the destruction from caller.

    – lz96
    Nov 16 '18 at 2:41
















3















#include <memory>
#include <vector>
using namespace std;

vector<unique_ptr<int>> e;

void f(unique_ptr<int> u)
e.emplace_back(move(u));



For both Clang and GCC, the above code snippet generates something like:



f(std::unique_ptr<int, std::default_delete<int> >):
mov rsi, QWORD PTR e[rip+8] # rsi: vector.end_ptr
cmp rsi, QWORD PTR e[rip+16] # [e + rip + 16]: vector.storage_end_ptr
je .L52 # Slow path, need to reallocate
mov rax, QWORD PTR [rdi] # rax: unique_ptr<int> u
add rsi, 8 # end_ptr += 8
mov QWORD PTR [rdi], 0 # <==== Do we need to set the argument u to null here?
mov QWORD PTR [rsi-8], rax # *(end_ptr - 8) = u
mov QWORD PTR e[rip+8], rsi # update end_ptr
ret
.L52: # omitted


I was wondering why does the compiler generate mov QWORD PTR[rdi], 0 in this function? Is there any convention that requires compiler to do so?



Moreover, for simpler case like this:



void f(unique_ptr<int> u);

void h(int x)
auto p = make_unique<int>(x);
f(move(p));



Why does the compiler generate:



call operator delete(void*, unsigned long)


at the end of h(), given that p is always nullptr after the invocation of f?










share|improve this question






















  • A preliminary guess: Itanium ABI requires the caller to handle the construction/destruction of argument. The operator delete is actually called for the copied argument, u. Therefore, the callee needs to set the unique_ptr to nullptr to ask for the destruction from caller.

    – lz96
    Nov 16 '18 at 2:41














3












3








3


2






#include <memory>
#include <vector>
using namespace std;

vector<unique_ptr<int>> e;

void f(unique_ptr<int> u)
e.emplace_back(move(u));



For both Clang and GCC, the above code snippet generates something like:



f(std::unique_ptr<int, std::default_delete<int> >):
mov rsi, QWORD PTR e[rip+8] # rsi: vector.end_ptr
cmp rsi, QWORD PTR e[rip+16] # [e + rip + 16]: vector.storage_end_ptr
je .L52 # Slow path, need to reallocate
mov rax, QWORD PTR [rdi] # rax: unique_ptr<int> u
add rsi, 8 # end_ptr += 8
mov QWORD PTR [rdi], 0 # <==== Do we need to set the argument u to null here?
mov QWORD PTR [rsi-8], rax # *(end_ptr - 8) = u
mov QWORD PTR e[rip+8], rsi # update end_ptr
ret
.L52: # omitted


I was wondering why does the compiler generate mov QWORD PTR[rdi], 0 in this function? Is there any convention that requires compiler to do so?



Moreover, for simpler case like this:



void f(unique_ptr<int> u);

void h(int x)
auto p = make_unique<int>(x);
f(move(p));



Why does the compiler generate:



call operator delete(void*, unsigned long)


at the end of h(), given that p is always nullptr after the invocation of f?










share|improve this question














#include <memory>
#include <vector>
using namespace std;

vector<unique_ptr<int>> e;

void f(unique_ptr<int> u)
e.emplace_back(move(u));



For both Clang and GCC, the above code snippet generates something like:



f(std::unique_ptr<int, std::default_delete<int> >):
mov rsi, QWORD PTR e[rip+8] # rsi: vector.end_ptr
cmp rsi, QWORD PTR e[rip+16] # [e + rip + 16]: vector.storage_end_ptr
je .L52 # Slow path, need to reallocate
mov rax, QWORD PTR [rdi] # rax: unique_ptr<int> u
add rsi, 8 # end_ptr += 8
mov QWORD PTR [rdi], 0 # <==== Do we need to set the argument u to null here?
mov QWORD PTR [rsi-8], rax # *(end_ptr - 8) = u
mov QWORD PTR e[rip+8], rsi # update end_ptr
ret
.L52: # omitted


I was wondering why does the compiler generate mov QWORD PTR[rdi], 0 in this function? Is there any convention that requires compiler to do so?



Moreover, for simpler case like this:



void f(unique_ptr<int> u);

void h(int x)
auto p = make_unique<int>(x);
f(move(p));



Why does the compiler generate:



call operator delete(void*, unsigned long)


at the end of h(), given that p is always nullptr after the invocation of f?







c++ gcc compiler-optimization abi






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Nov 16 '18 at 2:02









lz96lz96

9311330




9311330












  • A preliminary guess: Itanium ABI requires the caller to handle the construction/destruction of argument. The operator delete is actually called for the copied argument, u. Therefore, the callee needs to set the unique_ptr to nullptr to ask for the destruction from caller.

    – lz96
    Nov 16 '18 at 2:41


















  • A preliminary guess: Itanium ABI requires the caller to handle the construction/destruction of argument. The operator delete is actually called for the copied argument, u. Therefore, the callee needs to set the unique_ptr to nullptr to ask for the destruction from caller.

    – lz96
    Nov 16 '18 at 2:41

















A preliminary guess: Itanium ABI requires the caller to handle the construction/destruction of argument. The operator delete is actually called for the copied argument, u. Therefore, the callee needs to set the unique_ptr to nullptr to ask for the destruction from caller.

– lz96
Nov 16 '18 at 2:41






A preliminary guess: Itanium ABI requires the caller to handle the construction/destruction of argument. The operator delete is actually called for the copied argument, u. Therefore, the callee needs to set the unique_ptr to nullptr to ask for the destruction from caller.

– lz96
Nov 16 '18 at 2:41













1 Answer
1






active

oldest

votes


















2














In both of these cases, the answer is: because the object you moved from will still be destroyed.



If you look at the code generated for a call to



void f(unique_ptr<int> u);


you will notice that the caller creates the object for parameter u and calls its destructor afterwards as mandated by the calling convention. In case the call to f() is inlined, the compiler will most likely be able to optimize this away. But the code generated for f() has no control over the destructor of u and, thus, has to set the internal pointer of u to zero assuming that the destructor of u will run after the function returns.



In your second example, we have sort of the inverse situation:



void h(int x) 
auto p = make_unique<int>(x);
f(move(p));



Contrary to what the name may suggest, std::move() does not actually move an object. All it does is cast to an rvalue reference which allows the recipient of that reference to move from the object referred to—if he so choses. The actual move only happens, e.g., when another object is constructed from the given argument via a move constructor. Since the compiler does not know anything about what happens inside f() at the point of definition of h(), it can't assume that f() will always move from the given object. For example, f() could simply return or move only in some cases and not in others. Therefore, the compiler has to assume that the function might return without moving from the object and has to emit the delete for the destructor. The function could also perform a move assignment instead of a move construction, in which case the outer destructor would still be needed to release ownership of the object previously held by whatever was assigned ownership of the new object…






share|improve this answer

























  • Thanks. I just find this in Itanium ABI specification. It seems that unique_ptr is not zero cost when it crosses ABI boundary. At least, we need another layer of indirection due to call convention and cases like this prohibit optimization for non-used dtors.

    – lz96
    Nov 16 '18 at 2:47












  • The question is: not zero cost in comparison to what alternative? Passing a unique_ptr is semantically different from passing a raw pointer. If you want single ownership semantics then you would have to somehow signal back to the caller whether there was a transfer of ownership or not, even if you were just passing a raw pointer instead of a unique_ptr

    – Michael Kenzel
    Nov 16 '18 at 10:56












  • Compared to using OwnerPtr<T> = T* and let the parameter of f be OwnerPtr<int> and let f determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forces unique_ptr to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since only f knows if it should be destroyed, but the caller doesn't know).

    – lz96
    Nov 16 '18 at 20:12










Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53330428%2fwhy-this-dead-store-of-unique-ptr-cannot-be-eliminated%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









2














In both of these cases, the answer is: because the object you moved from will still be destroyed.



If you look at the code generated for a call to



void f(unique_ptr<int> u);


you will notice that the caller creates the object for parameter u and calls its destructor afterwards as mandated by the calling convention. In case the call to f() is inlined, the compiler will most likely be able to optimize this away. But the code generated for f() has no control over the destructor of u and, thus, has to set the internal pointer of u to zero assuming that the destructor of u will run after the function returns.



In your second example, we have sort of the inverse situation:



void h(int x) 
auto p = make_unique<int>(x);
f(move(p));



Contrary to what the name may suggest, std::move() does not actually move an object. All it does is cast to an rvalue reference which allows the recipient of that reference to move from the object referred to—if he so choses. The actual move only happens, e.g., when another object is constructed from the given argument via a move constructor. Since the compiler does not know anything about what happens inside f() at the point of definition of h(), it can't assume that f() will always move from the given object. For example, f() could simply return or move only in some cases and not in others. Therefore, the compiler has to assume that the function might return without moving from the object and has to emit the delete for the destructor. The function could also perform a move assignment instead of a move construction, in which case the outer destructor would still be needed to release ownership of the object previously held by whatever was assigned ownership of the new object…






share|improve this answer

























  • Thanks. I just find this in Itanium ABI specification. It seems that unique_ptr is not zero cost when it crosses ABI boundary. At least, we need another layer of indirection due to call convention and cases like this prohibit optimization for non-used dtors.

    – lz96
    Nov 16 '18 at 2:47












  • The question is: not zero cost in comparison to what alternative? Passing a unique_ptr is semantically different from passing a raw pointer. If you want single ownership semantics then you would have to somehow signal back to the caller whether there was a transfer of ownership or not, even if you were just passing a raw pointer instead of a unique_ptr

    – Michael Kenzel
    Nov 16 '18 at 10:56












  • Compared to using OwnerPtr<T> = T* and let the parameter of f be OwnerPtr<int> and let f determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forces unique_ptr to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since only f knows if it should be destroyed, but the caller doesn't know).

    – lz96
    Nov 16 '18 at 20:12















2














In both of these cases, the answer is: because the object you moved from will still be destroyed.



If you look at the code generated for a call to



void f(unique_ptr<int> u);


you will notice that the caller creates the object for parameter u and calls its destructor afterwards as mandated by the calling convention. In case the call to f() is inlined, the compiler will most likely be able to optimize this away. But the code generated for f() has no control over the destructor of u and, thus, has to set the internal pointer of u to zero assuming that the destructor of u will run after the function returns.



In your second example, we have sort of the inverse situation:



void h(int x) 
auto p = make_unique<int>(x);
f(move(p));



Contrary to what the name may suggest, std::move() does not actually move an object. All it does is cast to an rvalue reference which allows the recipient of that reference to move from the object referred to—if he so choses. The actual move only happens, e.g., when another object is constructed from the given argument via a move constructor. Since the compiler does not know anything about what happens inside f() at the point of definition of h(), it can't assume that f() will always move from the given object. For example, f() could simply return or move only in some cases and not in others. Therefore, the compiler has to assume that the function might return without moving from the object and has to emit the delete for the destructor. The function could also perform a move assignment instead of a move construction, in which case the outer destructor would still be needed to release ownership of the object previously held by whatever was assigned ownership of the new object…






share|improve this answer

























  • Thanks. I just find this in Itanium ABI specification. It seems that unique_ptr is not zero cost when it crosses ABI boundary. At least, we need another layer of indirection due to call convention and cases like this prohibit optimization for non-used dtors.

    – lz96
    Nov 16 '18 at 2:47












  • The question is: not zero cost in comparison to what alternative? Passing a unique_ptr is semantically different from passing a raw pointer. If you want single ownership semantics then you would have to somehow signal back to the caller whether there was a transfer of ownership or not, even if you were just passing a raw pointer instead of a unique_ptr

    – Michael Kenzel
    Nov 16 '18 at 10:56












  • Compared to using OwnerPtr<T> = T* and let the parameter of f be OwnerPtr<int> and let f determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forces unique_ptr to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since only f knows if it should be destroyed, but the caller doesn't know).

    – lz96
    Nov 16 '18 at 20:12













2












2








2







In both of these cases, the answer is: because the object you moved from will still be destroyed.



If you look at the code generated for a call to



void f(unique_ptr<int> u);


you will notice that the caller creates the object for parameter u and calls its destructor afterwards as mandated by the calling convention. In case the call to f() is inlined, the compiler will most likely be able to optimize this away. But the code generated for f() has no control over the destructor of u and, thus, has to set the internal pointer of u to zero assuming that the destructor of u will run after the function returns.



In your second example, we have sort of the inverse situation:



void h(int x) 
auto p = make_unique<int>(x);
f(move(p));



Contrary to what the name may suggest, std::move() does not actually move an object. All it does is cast to an rvalue reference which allows the recipient of that reference to move from the object referred to—if he so choses. The actual move only happens, e.g., when another object is constructed from the given argument via a move constructor. Since the compiler does not know anything about what happens inside f() at the point of definition of h(), it can't assume that f() will always move from the given object. For example, f() could simply return or move only in some cases and not in others. Therefore, the compiler has to assume that the function might return without moving from the object and has to emit the delete for the destructor. The function could also perform a move assignment instead of a move construction, in which case the outer destructor would still be needed to release ownership of the object previously held by whatever was assigned ownership of the new object…






share|improve this answer















In both of these cases, the answer is: because the object you moved from will still be destroyed.



If you look at the code generated for a call to



void f(unique_ptr<int> u);


you will notice that the caller creates the object for parameter u and calls its destructor afterwards as mandated by the calling convention. In case the call to f() is inlined, the compiler will most likely be able to optimize this away. But the code generated for f() has no control over the destructor of u and, thus, has to set the internal pointer of u to zero assuming that the destructor of u will run after the function returns.



In your second example, we have sort of the inverse situation:



void h(int x) 
auto p = make_unique<int>(x);
f(move(p));



Contrary to what the name may suggest, std::move() does not actually move an object. All it does is cast to an rvalue reference which allows the recipient of that reference to move from the object referred to—if he so choses. The actual move only happens, e.g., when another object is constructed from the given argument via a move constructor. Since the compiler does not know anything about what happens inside f() at the point of definition of h(), it can't assume that f() will always move from the given object. For example, f() could simply return or move only in some cases and not in others. Therefore, the compiler has to assume that the function might return without moving from the object and has to emit the delete for the destructor. The function could also perform a move assignment instead of a move construction, in which case the outer destructor would still be needed to release ownership of the object previously held by whatever was assigned ownership of the new object…







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 16 '18 at 11:00

























answered Nov 16 '18 at 2:41









Michael KenzelMichael Kenzel

4,56811020




4,56811020












  • Thanks. I just find this in Itanium ABI specification. It seems that unique_ptr is not zero cost when it crosses ABI boundary. At least, we need another layer of indirection due to call convention and cases like this prohibit optimization for non-used dtors.

    – lz96
    Nov 16 '18 at 2:47












  • The question is: not zero cost in comparison to what alternative? Passing a unique_ptr is semantically different from passing a raw pointer. If you want single ownership semantics then you would have to somehow signal back to the caller whether there was a transfer of ownership or not, even if you were just passing a raw pointer instead of a unique_ptr

    – Michael Kenzel
    Nov 16 '18 at 10:56












  • Compared to using OwnerPtr<T> = T* and let the parameter of f be OwnerPtr<int> and let f determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forces unique_ptr to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since only f knows if it should be destroyed, but the caller doesn't know).

    – lz96
    Nov 16 '18 at 20:12

















  • Thanks. I just find this in Itanium ABI specification. It seems that unique_ptr is not zero cost when it crosses ABI boundary. At least, we need another layer of indirection due to call convention and cases like this prohibit optimization for non-used dtors.

    – lz96
    Nov 16 '18 at 2:47












  • The question is: not zero cost in comparison to what alternative? Passing a unique_ptr is semantically different from passing a raw pointer. If you want single ownership semantics then you would have to somehow signal back to the caller whether there was a transfer of ownership or not, even if you were just passing a raw pointer instead of a unique_ptr

    – Michael Kenzel
    Nov 16 '18 at 10:56












  • Compared to using OwnerPtr<T> = T* and let the parameter of f be OwnerPtr<int> and let f determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forces unique_ptr to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since only f knows if it should be destroyed, but the caller doesn't know).

    – lz96
    Nov 16 '18 at 20:12
















Thanks. I just find this in Itanium ABI specification. It seems that unique_ptr is not zero cost when it crosses ABI boundary. At least, we need another layer of indirection due to call convention and cases like this prohibit optimization for non-used dtors.

– lz96
Nov 16 '18 at 2:47






Thanks. I just find this in Itanium ABI specification. It seems that unique_ptr is not zero cost when it crosses ABI boundary. At least, we need another layer of indirection due to call convention and cases like this prohibit optimization for non-used dtors.

– lz96
Nov 16 '18 at 2:47














The question is: not zero cost in comparison to what alternative? Passing a unique_ptr is semantically different from passing a raw pointer. If you want single ownership semantics then you would have to somehow signal back to the caller whether there was a transfer of ownership or not, even if you were just passing a raw pointer instead of a unique_ptr

– Michael Kenzel
Nov 16 '18 at 10:56






The question is: not zero cost in comparison to what alternative? Passing a unique_ptr is semantically different from passing a raw pointer. If you want single ownership semantics then you would have to somehow signal back to the caller whether there was a transfer of ownership or not, even if you were just passing a raw pointer instead of a unique_ptr

– Michael Kenzel
Nov 16 '18 at 10:56














Compared to using OwnerPtr<T> = T* and let the parameter of f be OwnerPtr<int> and let f determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forces unique_ptr to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since only f knows if it should be destroyed, but the caller doesn't know).

– lz96
Nov 16 '18 at 20:12





Compared to using OwnerPtr<T> = T* and let the parameter of f be OwnerPtr<int> and let f determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forces unique_ptr to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since only f knows if it should be destroyed, but the caller doesn't know).

– lz96
Nov 16 '18 at 20:12



















draft saved

draft discarded
















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53330428%2fwhy-this-dead-store-of-unique-ptr-cannot-be-eliminated%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Top Tejano songwriter Luis Silva dead of heart attack at 64

ReactJS Fetched API data displays live - need Data displayed static

政党