Date: Sun, 13 Aug 2023 20:10:18 +0100
On Sun, Aug 13, 2023 at 6:11 PM Arthur O'Dwyer wrote:
>
> Widget w for return = 42;
> Widget w for return = {1, 2, 3};
> Widget w for return (42); // forbid this, I hope
I think the following would be better:
for return Widget w = 42;
for return Widget w = {1, 2, 3};
for return Widget w(42);
> What would you then do with
> Widget y for return;
> if (cond1) throw y;
> else return y;
I hadn't even given consideration to throwing an
unmovable-and-uncopiable object. Not even sure how that would work...
I remember I tried to learn about how exceptions work under the hood
on Linux x86_64 but I just couldn't follow the whole 'personality
function' thing. I still don't really know how they work. So for now
I'll just talk about returning an unmovable-and-uncopiable object from
a function. So if we have a function with the following signature:
mutex Func(void);
then I propose that there are two acceptable forms of return statement
from this function. The first one is where 'return' is followed by a
prvalue, as follows:
mutex Func(void)
{
return mutex();
}
And then the second form is where 'return' isn't followed by anything
at all. In this circumstance, there must have been a prior local
variable defined as "for return", as follows:
mutex Func(void)
{
for return mutex mtx;
mtx.lock();
return; // This is not a typo
}
> But it wouldn't make that code portable to all possible platforms. (E.g. what about a
> platform where `std::mutex` is returned in a register? Could such a platform hypothetically exist?)
When I was writing my paper on 'nrvo', I had to get a rudimentary
understanding of a dozen or so instruction sets and calling
conventions (which was really fun when I discovered that the stack
grew upwards on SuperH -- then I was playing around with a
cross-compiler and Qemu). Some of the calling conventions will always
try to return a struct in registers if it can fit -- but when dealing
with an unmovable-and-uncopiable class, all calling conventions always
use memory instead of registers. This is because you are allowed to
take the address of a mutex (so it can't be in registers). Hmmmm.....
I wonder if we were to delete the unary ampersand operator (i.e. the
'addressof' operator) for an unmovable-and-uncopiable class, then
perhaps could a mutex reside in registers . ? . ? . ?
Just as an example, I think on the Microsoft x64 calling convention,
if you return a "pair<int,int>" from a function, or a similarly-sized
struct, it will be returned in registers. But if you make the struct
unmovable and uncopiable, it will come back in RAM.
> You couldn't ask the compiler to guarantee that copy elision happened, because that's dependent on ABI. E.g.:
> template<class T>
> T f() {
> T t;
> escape(&t);
> [[nrvo]] return t;
> }
So this could become:
template<class T>
T f(void)
{
for return T t;
escape(&t);
return; // This is not a typo
}
Furthermore I think we should be able to have a function such as the following:
recursive_mutex Func(void)
{
if ( some_global_boolean )
{
for return recursive_mutex m;
m.lock(); m.lock(); m.lock(); m.lock();
return;
}
else if ( SomeFunction() )
{
for return recursive_mutex m;
m.lock();
return;
}
return recursive_mutex();
}
>
> Widget w for return = 42;
> Widget w for return = {1, 2, 3};
> Widget w for return (42); // forbid this, I hope
I think the following would be better:
for return Widget w = 42;
for return Widget w = {1, 2, 3};
for return Widget w(42);
> What would you then do with
> Widget y for return;
> if (cond1) throw y;
> else return y;
I hadn't even given consideration to throwing an
unmovable-and-uncopiable object. Not even sure how that would work...
I remember I tried to learn about how exceptions work under the hood
on Linux x86_64 but I just couldn't follow the whole 'personality
function' thing. I still don't really know how they work. So for now
I'll just talk about returning an unmovable-and-uncopiable object from
a function. So if we have a function with the following signature:
mutex Func(void);
then I propose that there are two acceptable forms of return statement
from this function. The first one is where 'return' is followed by a
prvalue, as follows:
mutex Func(void)
{
return mutex();
}
And then the second form is where 'return' isn't followed by anything
at all. In this circumstance, there must have been a prior local
variable defined as "for return", as follows:
mutex Func(void)
{
for return mutex mtx;
mtx.lock();
return; // This is not a typo
}
> But it wouldn't make that code portable to all possible platforms. (E.g. what about a
> platform where `std::mutex` is returned in a register? Could such a platform hypothetically exist?)
When I was writing my paper on 'nrvo', I had to get a rudimentary
understanding of a dozen or so instruction sets and calling
conventions (which was really fun when I discovered that the stack
grew upwards on SuperH -- then I was playing around with a
cross-compiler and Qemu). Some of the calling conventions will always
try to return a struct in registers if it can fit -- but when dealing
with an unmovable-and-uncopiable class, all calling conventions always
use memory instead of registers. This is because you are allowed to
take the address of a mutex (so it can't be in registers). Hmmmm.....
I wonder if we were to delete the unary ampersand operator (i.e. the
'addressof' operator) for an unmovable-and-uncopiable class, then
perhaps could a mutex reside in registers . ? . ? . ?
Just as an example, I think on the Microsoft x64 calling convention,
if you return a "pair<int,int>" from a function, or a similarly-sized
struct, it will be returned in registers. But if you make the struct
unmovable and uncopiable, it will come back in RAM.
> You couldn't ask the compiler to guarantee that copy elision happened, because that's dependent on ABI. E.g.:
> template<class T>
> T f() {
> T t;
> escape(&t);
> [[nrvo]] return t;
> }
So this could become:
template<class T>
T f(void)
{
for return T t;
escape(&t);
return; // This is not a typo
}
Furthermore I think we should be able to have a function such as the following:
recursive_mutex Func(void)
{
if ( some_global_boolean )
{
for return recursive_mutex m;
m.lock(); m.lock(); m.lock(); m.lock();
return;
}
else if ( SomeFunction() )
{
for return recursive_mutex m;
m.lock();
return;
}
return recursive_mutex();
}
Received on 2023-08-13 19:10:32