Skip to content

[[msvc::no_unique_address]] differs in layout from MSVC ABI #136685

Open
@Ext3h

Description

@Ext3h

Consider the following examples:

#include <cstddef>

struct S1 {};
struct S2 {};

struct S3 {
    [[no_unique_address]] [[msvc::no_unique_address]] S1 s1;
};

struct ST1 {
    [[no_unique_address]] [[msvc::no_unique_address]] S3 s1;
    S1 s2;
};

struct ST2 {
    [[no_unique_address]] [[msvc::no_unique_address]] S3 s1;
    S2 s2;
};

struct ST3 {
    [[no_unique_address]] [[msvc::no_unique_address]] S3 s1;
    [[no_unique_address]] [[msvc::no_unique_address]] S1 s2;
};

struct ST4 {
    [[no_unique_address]] [[msvc::no_unique_address]] S3 s1;
    [[no_unique_address]] [[msvc::no_unique_address]] S2 s2;
};

static_assert(sizeof(ST1) == 2);
static_assert(offsetof(ST1,s2) != offsetof(ST1,s1.s1), "Subobjects of same type must not have same pointer");

size_t size_st1() {
    return sizeof(ST1);
}

size_t offset_st1() {
    return offsetof(ST1, s2);
}

// Failed by MSVC
static_assert(sizeof(ST2) == 1);
// Failed by MSVC
static_assert(offsetof(ST2,s2) == offsetof(ST2,s1.s1), "Subobjects of different types where at least one is zero-sized may have same pointer");

size_t size_st2() {
    return sizeof(ST2);
}

size_t offset_st2() {
    return offsetof(ST2, s2);
}

// Failed by MSVC
static_assert(sizeof(ST3) == 2);
// Failed by MSVC
static_assert(offsetof(ST3,s2) != offsetof(ST3,s1.s1), "Subobjects of same type must not have same pointer");

size_t size_st3() {
    return sizeof(ST3);
}

size_t offset_st3() {
    return offsetof(ST3, s2);
}

static_assert(sizeof(ST4) == 1);
static_assert(offsetof(ST4,s2) == offsetof(ST4,s1.s1), "Subobjects of different types where at least one is zero-sized may have same pointer");

size_t size_st4() {
    return sizeof(ST4);
}

size_t offset_st4() {
    return offsetof(ST4, s2);
}

Example output: https://godbolt.org/z/xnrT5Tsq1

Relevant part of the spec:

Unless an object is a bit-field or a subobject of zero size, the address of that object is the address of the first
byte it occupies. Two objects with overlapping lifetimes that are not bit-fields may have the same address if
one is nested within the other, or if at least one is a subobject of zero size and they are of different types;
otherwise, they have distinct addresses and occupy disjoint bytes of storage.

For ST4 the behavior of Clang and MSVC matches in all aspects, both compilers optimize to the expected extent.

For ST1 the layout does match, but actually not for the same reasons. MSVC preserves ST1::s1 as a unique address because of the unique sibling ST1::s2. Clang preserves ST1::s1 because otherwise ST1::s2 would alias ST1::s1::s1.

This difference then causes trouble in ST2. Clang applies the optimization as strict aliasing rules render it non-ambiguous, but MSVC doesn't.

In ST3 we can see the opposite behavior. Even though everything is flagged with [[msvc::no_unique_address]], Clang still tries to avoid aliasing of ST1::s2 and ST1::s1::s1. MSVC does not.

For comparison, GCC and ICC both match the behavior of Clang for all 4 examples.

See also microsoft/STL#1364 for the epic depending on ABI equality.

See also https://developercommunity.visualstudio.com/t/msvc::no_unique_address-fails-to-opt/10892814 for the corresponding defect report for MSVC for the cases ST2 and ST3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ABIApplication Binary Interfaceclang:codegenIR generation bugs: mangling, exceptions, etc.diverges-from:msvcDoes the clang frontend diverge from msvc on this issueplatform:windows

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions