boostorg / container

STL-like containers from Boost
http://www.boost.org/libs/container/
Boost Software License 1.0
96 stars 116 forks source link

small_vector on MSVC x86 call-by-value crash #162

Closed tobias-loew closed 3 years ago

tobias-loew commented 3 years ago

Hi, using small_vector on MSCV (e.g. VS 2019) with x86 (Win32) results in a crash when trying to free the small_vector in the called function. The reason is (referring to the following example): the callee "main" copy-constructs a copy of m and forwards its address to foo. In the preamble of foo the content pointed-to by the argument is mem-copied(!!!) to the local storage of n. This mem-copying destroys the invariants of the small_vector, as the address to the stored data is not adjusted.

I'm not sure, what other platforms are affected by this issue.

best Tobias

#include <boost/container/small_vector.hpp>

using vec_t = boost::container::small_vector<
    double,
    13
    >;

void foo(vec_t n) {
}

int main()
{
    vec_t m;
    foo(m);
    return 0;
}
tobias-loew commented 3 years ago

I think I found out what's going wrong. It's the way MSVC is achieving alignment for x86 (NOT x86-64) code.

The callee does not assume that the arguments on the heap are aligned correctly, so for types with alignment bigger than 4, it allocates additional space and uses memcpy to copy the data into place - which obviously destroys the internal state of a small_vector when it is in "small"-state.

An interesting fact is that the MSVC-compiler doesn't seem to trust itself, as it aligns the data also on the caller-site. So, the memcpy for alignment by the callee (which is the cause for the crash) is unnecessary. (But of course, the compiler couldn't know that for calls from other modules or for exported functions.)

I'll report this to Microsoft.

Tobias

tobias-loew commented 3 years ago

Hi,

I think I found ways, how to work around the faulty MSVC x86 ABI. (The following refers all to that ABI)

First of all: for a sufficiently complex class type S (as far as I found out: not an aggregate) adding alignas(N) yields different asm-code for call-by-value function preambles. (i.e. the additional memcpy to an aligned address)

And that happens even when N is the natural alignment of S, i.e. N == alignof(S).

Furthermore, alignas is viral: if any member or base uses alignas it is implicitly assumed for the whole class.

There are multiple ways to tackle this problem for MSVC x86:

So, currently I don't see a way to use types with alignment > 4 that are not mem-movable with MSVC x86 call-by-value semantics.

Tobias

igaztanaga commented 3 years ago

I've made a special version for MSVC X86 avoiding alignas up to alignment== 8 using builtin types, which is enough for non-overaligned types (your example compiles fine with the modification). For overaligned types it seems there is no solution. The commit is:

https://github.com/boostorg/move/commit/3446ceeaafaf5788cf53140394ccd9e5652c9577

I'm closing the issue, as it seems we can't do more to fix it from the library side.

tobias-loew commented 3 years ago

Thanks a lot. Microsoft also announced to fix the alignment problem for x86 in the next minor visual studio release (hopefully in 16.8 or 16.9).

tobias-loew commented 3 years ago

The issue shall be resolved in VS 2019 16.9 Preview 2 - so hopefully in all VS 2019 16.9 (non-preview) and above. https://developercommunity.visualstudio.com/comments/1209850/view.html

tobias-loew commented 3 years ago

I tested with VS 2019 16.9 Preview 2 and boost 1.74 and it is no longer an issue! So, we can expect that it will also work with all VS 16.9 and later.