Why this dead store of unique_ptr cannot be eliminated?
#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
add a comment |
#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
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
add a comment |
#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
#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
c++ gcc compiler-optimization abi
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
add a comment |
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
add a comment |
1 Answer
1
active
oldest
votes
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…
Thanks. I just find this in Itanium ABI specification. It seems thatunique_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 aunique_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 aunique_ptr
…
– Michael Kenzel
Nov 16 '18 at 10:56
Compared tousing OwnerPtr<T> = T*
and let the parameter off
beOwnerPtr<int>
and letf
determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forcesunique_ptr
to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since onlyf
knows if it should be destroyed, but the caller doesn't know).
– lz96
Nov 16 '18 at 20:12
add a comment |
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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…
Thanks. I just find this in Itanium ABI specification. It seems thatunique_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 aunique_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 aunique_ptr
…
– Michael Kenzel
Nov 16 '18 at 10:56
Compared tousing OwnerPtr<T> = T*
and let the parameter off
beOwnerPtr<int>
and letf
determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forcesunique_ptr
to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since onlyf
knows if it should be destroyed, but the caller doesn't know).
– lz96
Nov 16 '18 at 20:12
add a comment |
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…
Thanks. I just find this in Itanium ABI specification. It seems thatunique_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 aunique_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 aunique_ptr
…
– Michael Kenzel
Nov 16 '18 at 10:56
Compared tousing OwnerPtr<T> = T*
and let the parameter off
beOwnerPtr<int>
and letf
determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forcesunique_ptr
to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since onlyf
knows if it should be destroyed, but the caller doesn't know).
– lz96
Nov 16 '18 at 20:12
add a comment |
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…
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…
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 thatunique_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 aunique_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 aunique_ptr
…
– Michael Kenzel
Nov 16 '18 at 10:56
Compared tousing OwnerPtr<T> = T*
and let the parameter off
beOwnerPtr<int>
and letf
determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forcesunique_ptr
to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since onlyf
knows if it should be destroyed, but the caller doesn't know).
– lz96
Nov 16 '18 at 20:12
add a comment |
Thanks. I just find this in Itanium ABI specification. It seems thatunique_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 aunique_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 aunique_ptr
…
– Michael Kenzel
Nov 16 '18 at 10:56
Compared tousing OwnerPtr<T> = T*
and let the parameter off
beOwnerPtr<int>
and letf
determine whether the deleter will be called or not (actually, that's the implementation of Rust ownership), the Itanium ABI forcesunique_ptr
to be passed by reference and destroyed by the caller, which prohibits optimization of dtor (since onlyf
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
add a comment |
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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