• Advertisement
Sign in to follow this  

Running x86 build crashes

Recommended Posts

If you're going to selectively ignore advice then I personally have no interest in continuing to help you.

 

You clearly need to learn a LOT about what makes a Release build different from a Debug build.

You need to read about COMDAT folding.

You need to read about how to debug at the disassembly level.

You need to pay more attention to the real symptoms of nullptr bugs.

You REALLY need to learn how to think of your programs in terms of instructions and memory locations. I can tell this is all magic to you right now and until you choose to work at understanding it as a science it will always give you grief.

 

 

But most importantly you do NOT get to pick and choose. All of these things are important knowledge and interrelated.

I am now going to leave this thread so I don't get more frustrated. Good luck!

Share this post


Link to post
Share on other sites
Advertisement

@ApochPiQ I am not selectively ignoring or accepting possible solutions. I rank all solutions based on the estimated amount of time to apply them and my level of experience with the solution or tools. So I start with the easy ones and report on them based on the results. I haven't forget about the assembly debugging, inspecting the heap, guarding specific addresses, comdat folding, etc. Furthermore, I do not program 24h 7/7 during Holidays.

2 hours ago, ApochPiQ said:

If you're going to selectively ignore advice then I personally have no interest in continuing to help you.

So in that regard, I feel sad that that is your opinion.

Share this post


Link to post
Share on other sites

I used the /OPT::NOICF linker flag to disable COMDAT folding, but the "error location" shown in the editor (not in the call stack) remains the same. So I guess still some instructions are reused to keep the code small.

 

In order to make debugging a bit more manageable. I decided to isolate the error in a minimal code example, since the call stack is pretty clear at pointing where the error might be. And the minimal code example is pretty surprising.

The first part is optional depending on the implementation of your standard libraries. According to the C++ standard, std::vector does not guarantee to handle over-aligned types correctly. Microsoft's implementation of std::vector handles over-aligned types correctly. To be conform with the ISO C++ standard, however, one should use an allocator:

#include <malloc.h>
#include <new>

inline void *AllocAligned(size_t size, size_t alignment) noexcept {
    return _aligned_malloc(size, alignment);
}

template< typename DataT >
inline DataT *AllocAlignedData(size_t count, size_t alignment) noexcept {
    return static_cast< DataT * >(AllocAligned(count * sizeof(DataT), alignment));
}

inline void FreeAligned(void *ptr) noexcept {
    if (!ptr) {
        return;
    }

    _aligned_free(ptr);
}

template< typename DataT, size_t AlignmentS = alignof(DataT) >
struct AlignedAllocator final {

    using value_type = DataT;

    using propagate_on_container_move_assignment = std::true_type;
    using is_always_equal = std::true_type;

    template< typename DataU >
    struct rebind final {
        using other = AlignedAllocator< DataU, AlignmentS >;
    };

    constexpr AlignedAllocator() noexcept = default;
    constexpr AlignedAllocator(
        const AlignedAllocator &allocator) noexcept = default;
    constexpr AlignedAllocator(
        AlignedAllocator &&allocator) noexcept = default;
    template< typename DataU >
    constexpr AlignedAllocator([[maybe_unused]]
        const AlignedAllocator< DataU, AlignmentS > &allocator) noexcept {}
    ~AlignedAllocator() = default;
    AlignedAllocator &operator=(
        const AlignedAllocator &allocator) = delete;
    AlignedAllocator &operator=(
        AlignedAllocator &&allocator) noexcept = default;

    DataT *allocate(size_t count) const {
        DataT * const data = AllocAlignedData< DataT >(count, AlignmentS);
        if (!data) {
            throw std::bad_alloc();
        }

        return data;
    }

    DataT *allocate(size_t count, [[maybe_unused]] const void *hint) const {
        return allocate(count);
    }

    void deallocate(DataT *data, [[maybe_unused]] size_t count) const noexcept {
        FreeAligned((void *)data);
    }

    template< typename DataU >
    constexpr bool operator==([[maybe_unused]]
        const AlignedAllocator< DataU, AlignmentS > &rhs) const noexcept {

        return true;
    }
    template< typename DataU >
    constexpr bool operator!=([[maybe_unused]]
        const AlignedAllocator< DataU, AlignmentS > &rhs) const noexcept {

        return false;
    }
};

Minimal example:

#include <vector>
#include <xmmintrin.h>

struct alignas(16) BS final {
    __m128 m_pr;
};

struct alignas(16) AABB final {
    __m128 m_min;
    __m128 m_max;
};

struct alignas(16) ModelPart final {
    AABB m_aabb;
    BS m_bs;
};

struct ModelOutput final {
    void AddModelPart(ModelPart model_part, bool flag = true) {
        m_model_parts.push_back(std::move(model_part));
    }

    std::vector< ModelPart, AlignedAllocator< ModelPart > > m_model_parts;
};

int main() {
    ModelPart model_part;
    ModelOutput buffer;
    buffer.AddModelPart(std::move(model_part));
    return 0;
}

 

 

 

 

Share this post


Link to post
Share on other sites
3 hours ago, matt77hias said:

struct alignas(16) BS final {     __m128 m_pr; }; struct alignas(16) AABB final {     __m128 m_min;     __m128 m_max; }; struct alignas(16) ModelPart final {     AABB m_aabb;     BS m_bs; };

With regard to minimal, it can still be reduced :D

struct alignas(16) ModelPart final {
    __m128 m;
};

 

ModelPart now looks like a typedef for __m128. If this was an actual typedef, one should change the call convention when passed by value. __vectorcall should be used in this case:

void __vectorcall AddModelPart(ModelPart model_part, bool flag = true) {
        m_model_parts.push_back(std::move(model_part));
}

And now it runs correctly. What surprises me, however, is why would this be necessary if you put extra non __m128 members in ModelPart? Shouldn't the optimizer be a bit more conservative?

 

I added the XM_CALLCONV macro to each method that passes a ModelPart by value. This solved the first part of my problem. Now my binary and non-binary loaders crash at the same spot. To solve this second part, I looked at all methods passing AABB or BS by value. This is only the case for my Model constructor. To fix this, I pass them by const reference instead of value  (since XM_CALLCONV should not be added to constructors).

I used #define _XM_NO_INTRINSICS_ a few times while debugging to rule out __m128 related problems in the first place. But the program still crashed, makes me wonder why you need to change the call convention for just PODs?

Edited by matt77hias

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this  

  • Advertisement