Date: Mon, 28 Apr 2025 19:39:08 +0100
>[[no_unique_address]] is a hint to the compiler, but just like the compiler folds function pointer together, it could do so for other things.
>At least the msvc documentation mentions it explicitly:
>
>https://learn.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=msvc-170
>
>----
>Because /OPT:ICF can cause the same address to be assigned to different functions or read-only data members (that is, const variables when compiled by using /Gy), it can break a program that depends on unique addresses for functions or read-only data members. For more information, see /Gy (Enable Function-Level Linking).
>----
You are misreading either the documentation or the example I provided.
I'll highlight a specific part of the documentation:
>(that is, const variables when compiled by using /Gy)
And then contrast with the example I provided:
>```
>template<typename T>
>struct unique_object { static char obj; };
>```
Not that `obj` is static, but not const. Hence the linker optimisation won't merge it. Merging writable variables would be a disaster because it would mean the following could return `2`:
```
static int a;
static int b;
int foo()
{
a = 1;
b = 2;
return a;
}
```
Which would be obviously wrong.
I do also think that the quoted optimisation is a case of MSC violating the standard by default (since the standard specifically makes an exception that you can do for string literals what they're doing for all const data, and it would seem odd that the standard would mention that you're explicitly allowed to do something for one specific data type you'd already implicitly be allowed to do for any data type) but such is the world we live in that we must work around Microsoft's defaults.
On 28 April 2025 14:55:34 BST, Federico Kircheis <federico_at_[hidden]> wrote:
>On 28/04/2025 2:51 pm, Jennifier Burnett wrote:
>>> If I use in my code get3 instead of get1 the lifetime of T does not begin.
>>
>> Yes, if you did a ctrl-f replace of "get1" with "get3" that would be incorrect. That's not what we're talking about though.
>>
>> What we are talking about is if the compiler can, after having generated the ASSEMBLY for get1 and get3, eliminate the definition of one of them and redirect the label to the other definition.
>>
>> At this point we are well beyond the boundaries of C++ and into the machine, which doesn't have any concepts of "lifetime" or "type", everything is just bytes.
>
>Mhm, OK, I did not think it this way...
>
>> If you were able to tell the difference between the compiler running one function and another then merging the two definitions would be immediately incorrect, no further discussion needed. The compiler might merge the two functions is specifically because there is no difference between the two of them when fed into the CPU.
>>
>> For example, written in aarch64 assembly the three functions would look like:
>>
>> ```
>> get1:
>> ret
>> get2:
>> ret
>> get3:
>> ret
>> ```
>>
>> What you are suggesting is that jumping to each of those 3 labels will produce different behaviour, which you can tell visually is incorrect.
>>
>>> From the documentation I've read, what merges functions/function pointers together might also merge constants
>>
>> No. This is why [[no_unique_address]] exists. All objects (except empty base classes) will have addresses which are unique amongst all other objects that are not marked [[no_unique_address]].
>
>[[no_unique_address]] is a hint to the compiler, but just like the compiler folds function pointer together, it could do so for other things.
>At least the msvc documentation mentions it explicitly:
>
>https://learn.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=msvc-170
>
>----
>Because /OPT:ICF can cause the same address to be assigned to different functions or read-only data members (that is, const variables when compiled by using /Gy), it can break a program that depends on unique addresses for functions or read-only data members. For more information, see /Gy (Enable Function-Level Linking).
>----
>
>I thought I had another resource for another compiler/linker but cannot find it anymore, maybe I misremember and msvc is the only one.
>
>>> Yes, but I do not see any difference if the hand-made vtable is in the type-erased class like I did ("inline") or somewhere else ("outline") as you proposed.
>>
>> There is no difference in usage. What I am proposing is that you use the address of the vtable itself to uniquely identify each type rather than the address of one of the functions in the vtable like in your original example, since as stated above each vtable will have a unique address that couldn't be confused with any of the others.
>
>Ah OK, I did not realize that you suggested to use the address of the vtable, I thought you wanted to add a token in the vtable and compare that.
>Yes, that should reduce the chance that they get merged together.
>
>But since the rules for globals having unique addresses are the same as for functions having unique addresses, I fear that using a global object as tag is as effective as using a function pointer as tag.
>
>From a logical perspective, if two identical functions share the same space, why shouldn't two constants?
>I also believe that for most use-cases, folding constants together is a sensible choice, just like for functions.
>At least the msvc documentation mentions it explicitly:
>
>https://learn.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=msvc-170
>
>----
>Because /OPT:ICF can cause the same address to be assigned to different functions or read-only data members (that is, const variables when compiled by using /Gy), it can break a program that depends on unique addresses for functions or read-only data members. For more information, see /Gy (Enable Function-Level Linking).
>----
You are misreading either the documentation or the example I provided.
I'll highlight a specific part of the documentation:
>(that is, const variables when compiled by using /Gy)
And then contrast with the example I provided:
>```
>template<typename T>
>struct unique_object { static char obj; };
>```
Not that `obj` is static, but not const. Hence the linker optimisation won't merge it. Merging writable variables would be a disaster because it would mean the following could return `2`:
```
static int a;
static int b;
int foo()
{
a = 1;
b = 2;
return a;
}
```
Which would be obviously wrong.
I do also think that the quoted optimisation is a case of MSC violating the standard by default (since the standard specifically makes an exception that you can do for string literals what they're doing for all const data, and it would seem odd that the standard would mention that you're explicitly allowed to do something for one specific data type you'd already implicitly be allowed to do for any data type) but such is the world we live in that we must work around Microsoft's defaults.
On 28 April 2025 14:55:34 BST, Federico Kircheis <federico_at_[hidden]> wrote:
>On 28/04/2025 2:51 pm, Jennifier Burnett wrote:
>>> If I use in my code get3 instead of get1 the lifetime of T does not begin.
>>
>> Yes, if you did a ctrl-f replace of "get1" with "get3" that would be incorrect. That's not what we're talking about though.
>>
>> What we are talking about is if the compiler can, after having generated the ASSEMBLY for get1 and get3, eliminate the definition of one of them and redirect the label to the other definition.
>>
>> At this point we are well beyond the boundaries of C++ and into the machine, which doesn't have any concepts of "lifetime" or "type", everything is just bytes.
>
>Mhm, OK, I did not think it this way...
>
>> If you were able to tell the difference between the compiler running one function and another then merging the two definitions would be immediately incorrect, no further discussion needed. The compiler might merge the two functions is specifically because there is no difference between the two of them when fed into the CPU.
>>
>> For example, written in aarch64 assembly the three functions would look like:
>>
>> ```
>> get1:
>> ret
>> get2:
>> ret
>> get3:
>> ret
>> ```
>>
>> What you are suggesting is that jumping to each of those 3 labels will produce different behaviour, which you can tell visually is incorrect.
>>
>>> From the documentation I've read, what merges functions/function pointers together might also merge constants
>>
>> No. This is why [[no_unique_address]] exists. All objects (except empty base classes) will have addresses which are unique amongst all other objects that are not marked [[no_unique_address]].
>
>[[no_unique_address]] is a hint to the compiler, but just like the compiler folds function pointer together, it could do so for other things.
>At least the msvc documentation mentions it explicitly:
>
>https://learn.microsoft.com/en-us/cpp/build/reference/opt-optimizations?view=msvc-170
>
>----
>Because /OPT:ICF can cause the same address to be assigned to different functions or read-only data members (that is, const variables when compiled by using /Gy), it can break a program that depends on unique addresses for functions or read-only data members. For more information, see /Gy (Enable Function-Level Linking).
>----
>
>I thought I had another resource for another compiler/linker but cannot find it anymore, maybe I misremember and msvc is the only one.
>
>>> Yes, but I do not see any difference if the hand-made vtable is in the type-erased class like I did ("inline") or somewhere else ("outline") as you proposed.
>>
>> There is no difference in usage. What I am proposing is that you use the address of the vtable itself to uniquely identify each type rather than the address of one of the functions in the vtable like in your original example, since as stated above each vtable will have a unique address that couldn't be confused with any of the others.
>
>Ah OK, I did not realize that you suggested to use the address of the vtable, I thought you wanted to add a token in the vtable and compare that.
>Yes, that should reduce the chance that they get merged together.
>
>But since the rules for globals having unique addresses are the same as for functions having unique addresses, I fear that using a global object as tag is as effective as using a function pointer as tag.
>
>From a logical perspective, if two identical functions share the same space, why shouldn't two constants?
>I also believe that for most use-cases, folding constants together is a sensible choice, just like for functions.
Received on 2025-04-28 18:39:17