Skip to content

<xmemory>: Investigate constexpr approach between _Default_allocator_traits::allocate and _Allocate #1532

@mnatsuhara

Description

@mnatsuhara

In GH-1369, we will be implementing is_constant_evaluated() pathways in both _Default_allocator_traits::allocate and the global _Allocate function that is called from that function if not evaluated in a constexpr context.

_Default_allocator_traits::allocate

STL/stl/inc/xmemory

Lines 661 to 673 in ac4fde7

_NODISCARD static _CONSTEXPR20_DYNALLOC __declspec(allocator) pointer
allocate(_Alloc& _Al, _CRT_GUARDOVERFLOW const size_type _Count) {
#ifdef __cpp_lib_constexpr_dynamic_alloc // TRANSITION, GH-1532
if (_STD is_constant_evaluated()) {
return _Al.allocate(_Count);
} else
#endif // __cpp_lib_constexpr_dynamic_alloc
{
(void) _Al;
return static_cast<pointer>(
_Allocate<_New_alignof<value_type>>(_Get_size_of_n<sizeof(value_type)>(_Count)));
}
}

_Allocate:

STL/stl/inc/xmemory

Lines 173 to 198 in ac4fde7

// FUNCTION TEMPLATES _Allocate and _Deallocate
#ifdef __cpp_aligned_new
template <size_t _Align, class _Traits = _Default_allocate_traits,
enable_if_t<(_Align > __STDCPP_DEFAULT_NEW_ALIGNMENT__), int> = 0>
__declspec(allocator) _CONSTEXPR20_DYNALLOC void* _Allocate(const size_t _Bytes) {
// allocate _Bytes when __cpp_aligned_new && _Align > __STDCPP_DEFAULT_NEW_ALIGNMENT__
if (_Bytes == 0) {
return nullptr;
}
#ifdef __cpp_lib_constexpr_dynamic_alloc // TRANSITION, GH-1532
if (_STD is_constant_evaluated()) {
return _Traits::_Allocate(_Bytes);
} else
#endif // __cpp_lib_constexpr_dynamic_alloc
{
size_t _Passed_align = _Align;
#if defined(_M_IX86) || defined(_M_X64)
if (_Bytes >= _Big_allocation_threshold) {
// boost the alignment of big allocations to help autovectorization
_Passed_align = (_STD max)(_Align, _Big_allocation_alignment);
}
#endif // defined(_M_IX86) || defined(_M_X64)
return _Traits::_Allocate_aligned(_Bytes, _Passed_align);
}
}

Currently, MSVC cannot handle calling the _Allocate function from a constexpr context (_Allocate calls ::operator new which MSVC does not allow in a constexpr function, though Clang does if it is reached transitively from an evaluation of std::allocator::allocate), which is why there is the is_constant_evaluated() block in _Default_allocator_traits::allocate that instead dispatches to std::allocator::allocate (which is permitted by MSVC). Note that _Normal_allocator_traits is not affected by this issue because it does not attempt the optimization at directly calling _Allocate and instead always goes through _Alloc::allocate.

It seems like we should either (1) have _Allocate be completely constexpr-safe (have valid constexpr pathways for all supported compilers), or (2) have _Allocate be a completely non-constexpr function that is just never called in constexpr pathways (and in this case, I believe we could also remove the constexpr pathways in _Default_allocate_traits used in _Allocate, _Deallocate). This depends on whether MSVC will permit the call to _Allocate in the future as the constexpr dynamic allocation implementation matures (in this case, we could adopt approach (1)). Otherwise, we can look into option (2). Regardless, we should pick one of these options to avoid this code duplication and the logic being split up in this way.

Internal Microsoft link only: You can hear more discussion about this issue here: https://msit.microsoftstream.com/video/4740a1ff-0400-b9eb-093e-f1eb3b56cd9a?st=2877

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementSomething can be improved

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions