diff --git a/libcxx/benchmarks/vector_operations.bench.cpp b/libcxx/benchmarks/vector_operations.bench.cpp --- a/libcxx/benchmarks/vector_operations.bench.cpp +++ b/libcxx/benchmarks/vector_operations.bench.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -36,5 +37,25 @@ std::vector{}, getRandomStringInputs)->Arg(TestNumInputs); +template +void BM_VectorPushBack(benchmark::State& state) { + T x = {}; + std::vector xs = {}; + for (auto _ : state) { + benchmark::DoNotOptimize(x); + xs.push_back(std::move(x)); + benchmark::DoNotOptimize(xs); + } +} + +// Nontrivial, but trivial for calls --> trivially relocatable. +struct [[clang::trivial_abi]] Nontrivial { + int* x = nullptr; + ~Nontrivial() { benchmark::DoNotOptimize(*this); } +}; + +BENCHMARK_TEMPLATE(BM_VectorPushBack, int*); +BENCHMARK_TEMPLATE(BM_VectorPushBack, std::unique_ptr); +BENCHMARK_TEMPLATE(BM_VectorPushBack, Nontrivial); BENCHMARK_MAIN(); diff --git a/libcxx/include/__memory/allocator_traits.h b/libcxx/include/__memory/allocator_traits.h --- a/libcxx/include/__memory/allocator_traits.h +++ b/libcxx/include/__memory/allocator_traits.h @@ -396,6 +396,17 @@ : __is_cpp17_move_insertable<_Alloc> { }; +// __has_default_allocator_construct +template +struct __has_default_allocator_construct + : integral_constant::value || !__has_construct<_Alloc, _Tp*, _Args...>::value) > {}; + +// __has_default_allocator_destroy +template +struct __has_default_allocator_destroy + : integral_constant::value || !__has_destroy<_Alloc, _Tp*>::value) > {}; + #undef _LIBCPP_ALLOCATOR_TRAITS_HAS_XXX _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/include/memory b/libcxx/include/memory --- a/libcxx/include/memory +++ b/libcxx/include/memory @@ -853,32 +853,36 @@ _LIBCPP_BEGIN_NAMESPACE_STD +// __construct_forward_with_exception_guarantees(__a, __begin1, __end1, __begin2, __use_memcpy) +// +// If __use_memcpy is true_type, this constructs using memcpy. Otherwise, it uses the move constructor. + template _LIBCPP_INLINE_VISIBILITY -void __construct_forward_with_exception_guarantees(_Alloc& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __begin2) { +void __construct_forward_with_exception_guarantees(_Alloc& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __begin2, false_type) { static_assert(__is_cpp17_move_insertable<_Alloc>::value, "The specified type does not meet the requirements of Cpp17MoveInsertable"); typedef allocator_traits<_Alloc> _Traits; for (; __begin1 != __end1; ++__begin1, (void)++__begin2) { - _Traits::construct(__a, _VSTD::__to_address(__begin2), + _Traits::construct(__a, std::__to_address(__begin2), #ifdef _LIBCPP_NO_EXCEPTIONS - _VSTD::move(*__begin1) + std::move(*__begin1) #else - _VSTD::move_if_noexcept(*__begin1) + std::move_if_noexcept(*__begin1) #endif ); } } -template ::value || !__has_construct<_Alloc, _Tp*, _Tp>::value) && - is_trivially_move_constructible<_Tp>::value ->::type> +template _LIBCPP_INLINE_VISIBILITY -void __construct_forward_with_exception_guarantees(_Alloc&, _Tp* __begin1, _Tp* __end1, _Tp*& __begin2) { +void __construct_forward_with_exception_guarantees(_Alloc&, _Ptr __begin1, _Ptr __end1, _Ptr& __begin2, true_type) { + typedef typename iterator_traits<_Ptr>::value_type _Tp; + // Note: const_cast used to support const value types until support is removed from libc++. + typedef typename remove_const<_Tp>::type _Vp; ptrdiff_t _Np = __end1 - __begin1; if (_Np > 0) { - _VSTD::memcpy(__begin2, __begin1, _Np * sizeof(_Tp)); + std::memcpy(const_cast<_Vp*>(std::__to_address(__begin2)), std::__to_address(__begin1), _Np * sizeof(_Tp)); __begin2 += _Np; } } @@ -888,7 +892,7 @@ void __construct_range_forward(_Alloc& __a, _Iter __begin1, _Iter __end1, _Ptr& __begin2) { typedef allocator_traits<_Alloc> _Traits; for (; __begin1 != __end1; ++__begin1, (void) ++__begin2) { - _Traits::construct(__a, _VSTD::__to_address(__begin2), *__begin1); + _Traits::construct(__a, std::__to_address(__begin2), *__begin1); } } @@ -899,45 +903,44 @@ typename enable_if< is_trivially_copy_constructible<_Dest>::value && is_same<_RawSource, _RawDest>::value && - (__is_default_allocator<_Alloc>::value || !__has_construct<_Alloc, _Dest*, _Source&>::value) + __has_default_allocator_construct<_Alloc, _Dest, _Source>::value >::type> _LIBCPP_INLINE_VISIBILITY void __construct_range_forward(_Alloc&, _Source* __begin1, _Source* __end1, _Dest*& __begin2) { ptrdiff_t _Np = __end1 - __begin1; if (_Np > 0) { - _VSTD::memcpy(const_cast<_RawDest*>(__begin2), __begin1, _Np * sizeof(_Dest)); + std::memcpy(const_cast<_RawDest*>(__begin2), __begin1, _Np * sizeof(_Dest)); __begin2 += _Np; } } template _LIBCPP_INLINE_VISIBILITY -void __construct_backward_with_exception_guarantees(_Alloc& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __end2) { +void __construct_backward_with_exception_guarantees(_Alloc& __a, _Ptr __begin1, _Ptr __end1, _Ptr& __end2, false_type) { static_assert(__is_cpp17_move_insertable<_Alloc>::value, "The specified type does not meet the requirements of Cpp17MoveInsertable"); typedef allocator_traits<_Alloc> _Traits; while (__end1 != __begin1) { - _Traits::construct(__a, _VSTD::__to_address(__end2 - 1), + _Traits::construct(__a, std::__to_address(__end2 - 1), #ifdef _LIBCPP_NO_EXCEPTIONS - _VSTD::move(*--__end1) + std::move(*--__end1) #else - _VSTD::move_if_noexcept(*--__end1) + std::move_if_noexcept(*--__end1) #endif ); --__end2; } } -template ::value || !__has_construct<_Alloc, _Tp*, _Tp>::value) && - is_trivially_move_constructible<_Tp>::value ->::type> +template _LIBCPP_INLINE_VISIBILITY -void __construct_backward_with_exception_guarantees(_Alloc&, _Tp* __begin1, _Tp* __end1, _Tp*& __end2) { +void __construct_backward_with_exception_guarantees(_Alloc&, _Ptr __begin1, _Ptr __end1, _Ptr& __end2, true_type) { + typedef typename iterator_traits<_Ptr>::value_type _Tp; + typedef typename remove_const<_Tp>::type _Vp; ptrdiff_t _Np = __end1 - __begin1; __end2 -= _Np; if (_Np > 0) - _VSTD::memcpy(static_cast(__end2), static_cast(__begin1), _Np * sizeof(_Tp)); + std::memcpy(const_cast<_Vp*>(std::__to_address(__end2)), std::__to_address(__begin1), _Np * sizeof(_Tp)); } struct __destruct_n @@ -1098,7 +1101,6 @@ } }; - _LIBCPP_END_NAMESPACE_STD #if defined(_LIBCPP_HAS_PARALLEL_ALGORITHMS) && _LIBCPP_STD_VER >= 17 diff --git a/libcxx/include/type_traits b/libcxx/include/type_traits --- a/libcxx/include/type_traits +++ b/libcxx/include/type_traits @@ -3101,6 +3101,21 @@ // is_nothrow_constructible + + +// __libcpp_is_trivially_relocatable + +template +struct _LIBCPP_TEMPLATE_VIS __libcpp_is_trivially_relocatable + : public integral_constant::type>::value && + is_trivially_destructible::type>::value +#endif + > {}; + #if __has_keyword(__is_nothrow_constructible) template diff --git a/libcxx/include/vector b/libcxx/include/vector --- a/libcxx/include/vector +++ b/libcxx/include/vector @@ -321,6 +321,27 @@ _LIBCPP_BEGIN_NAMESPACE_STD +// Whether vector can trivially relocate a type, with this allocator. +template +struct __vector_trivial_relocate : integral_constant::value && + __has_default_allocator_construct<_Allocator, _Tp, _Tp&&>::value && + __has_default_allocator_destroy<_Allocator, _Tp>::value +> {}; + +// Whether to use trivial destruction (i.e. no destructor) during a relocation operation. +template +using __vector_relocate_trivial_destroy = __vector_trivial_relocate<_Tp, _Allocator>; + +// Whether to use trivial moves (memcpy) during a relocation operation. +template +struct __vector_relocate_trivial_move : integral_constant::value || + (is_trivially_move_constructible<_Tp>::value && + __has_default_allocator_construct<_Allocator, _Tp, _Tp&&>::value) +> {}; + + template */> class _LIBCPP_TEMPLATE_VIS vector { @@ -694,6 +715,8 @@ void __swap_out_circular_buffer(__split_buffer& __v); pointer __swap_out_circular_buffer(__split_buffer& __v, pointer __p); void __move_range(pointer __from_s, pointer __from_e, pointer __to); + template + void __relocate_upward_and_insert(pointer __from_s, pointer __from_e, _Up&& __elt); void __move_assign(vector& __c, true_type) _NOEXCEPT_(is_nothrow_move_assignable::value); void __move_assign(vector& __c, false_type) @@ -897,13 +920,16 @@ void vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer& __v) { + using __move_via_memcpy = __vector_relocate_trivial_move<_Tp, _Allocator>; + using __destroy_via_noop = __vector_relocate_trivial_destroy<_Tp, _Allocator>; __annotate_delete(); - _VSTD::__construct_backward_with_exception_guarantees(this->__alloc(), this->__begin_, this->__end_, __v.__begin_); + _VSTD::__construct_backward_with_exception_guarantees(this->__alloc(), this->__begin_, this->__end_, __v.__begin_, __move_via_memcpy()); _VSTD::swap(this->__begin_, __v.__begin_); _VSTD::swap(this->__end_, __v.__end_); _VSTD::swap(this->__end_cap(), __v.__end_cap()); __v.__first_ = __v.__begin_; + __v.__destruct_at_end(__v.__begin_, __destroy_via_noop()); __annotate_new(size()); __invalidate_all_iterators(); } @@ -912,14 +938,18 @@ typename vector<_Tp, _Allocator>::pointer vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer& __v, pointer __p) { + using __move_via_memcpy = __vector_relocate_trivial_move<_Tp, _Allocator>; + using __destroy_via_noop = __vector_relocate_trivial_destroy<_Tp, _Allocator>; + __annotate_delete(); pointer __r = __v.__begin_; - _VSTD::__construct_backward_with_exception_guarantees(this->__alloc(), this->__begin_, __p, __v.__begin_); - _VSTD::__construct_forward_with_exception_guarantees(this->__alloc(), __p, this->__end_, __v.__end_); + _VSTD::__construct_backward_with_exception_guarantees(this->__alloc(), this->__begin_, __p, __v.__begin_, __move_via_memcpy()); + _VSTD::__construct_forward_with_exception_guarantees(this->__alloc(), __p, this->__end_, __v.__end_, __move_via_memcpy()); _VSTD::swap(this->__begin_, __v.__begin_); _VSTD::swap(this->__end_, __v.__end_); _VSTD::swap(this->__end_cap(), __v.__end_cap()); __v.__first_ = __v.__begin_; + __v.__destruct_at_end(__v.__begin_, __destroy_via_noop()); __annotate_new(size()); __invalidate_all_iterators(); return __r; @@ -1618,14 +1648,23 @@ typename vector<_Tp, _Allocator>::iterator vector<_Tp, _Allocator>::erase(const_iterator __position) { - _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(_VSTD::addressof(__position)) == this, + _LIBCPP_DEBUG_ASSERT(__get_const_db()->__find_c_from_i(std::addressof(__position)) == this, "vector::erase(iterator) called with an iterator not referring to this vector"); _LIBCPP_ASSERT(__position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator"); difference_type __ps = __position - cbegin(); - pointer __p = this->__begin_ + __ps; - this->__destruct_at_end(_VSTD::move(__p + 1, this->__end_, __p)); - this->__invalidate_iterators_past(__p-1); + pointer __p = __begin_ + __ps; + if (__vector_trivial_relocate<_Tp, _Allocator>::value) { + _Tp *__rawp = std::__to_address(__p); + __alloc_traits::destroy(__alloc(), __rawp); + --__end_; + if (__p != __end_) { + std::memmove(__rawp, __rawp + 1, (__end_ - __p) * sizeof(*__rawp)); + } + } else { + __destruct_at_end(std::move(__p + 1, __end_, __p)); + } + __invalidate_iterators_past(__p-1); iterator __r = __make_iter(__p); return __r; } @@ -1649,6 +1688,26 @@ return __r; } +template +struct __is_nothrow_constructible_alloc : integral_constant::value && (__is_default_allocator<_Allocator>::value || !__has_construct<_Allocator, _Tp*, _Tp>::value )> {}; + +template +template +void +vector<_Tp, _Allocator>::__relocate_upward_and_insert(pointer __from_s, pointer __from_e, _Up&& __elt) +{ + if (__vector_trivial_relocate<_Tp, _Allocator>::value && __is_nothrow_constructible_alloc<_Allocator, _Tp, _Up&&>::value) { + std::memmove(std::__to_address(__from_s + 1), std::__to_address(__from_s), (__from_e - __from_s) * sizeof(_Tp)); + ++this->__end_; + __alloc_traits::construct(this->__alloc(), + std::__to_address(__from_s), + std::forward<_Up>(__elt)); + } else { + __move_range(__from_s, __from_e, __from_s + 1); + *__from_s = std::forward<_Up>(__elt); + } +} + template void vector<_Tp, _Allocator>::__move_range(pointer __from_s, pointer __from_e, pointer __to) @@ -1683,11 +1742,10 @@ } else { - __move_range(__p, this->__end_, __p + 1); const_pointer __xr = pointer_traits::pointer_to(__x); if (__p <= __xr && __xr < this->__end_) ++__xr; - *__p = *__xr; + __relocate_upward_and_insert(__p, this->__end_, *__xr); } } else @@ -1717,8 +1775,7 @@ } else { - __move_range(__p, this->__end_, __p + 1); - *__p = _VSTD::move(__x); + __relocate_upward_and_insert(__p, this->__end_, _VSTD::move(__x)); } } else @@ -1748,8 +1805,7 @@ else { __temp_value __tmp(this->__alloc(), _VSTD::forward<_Args>(__args)...); - __move_range(__p, this->__end_, __p + 1); - *__p = _VSTD::move(__tmp.get()); + __relocate_upward_and_insert(__p, this->__end_, _VSTD::move(__tmp.get())); } } else diff --git a/libcxx/test/libcxx/containers/sequences/vector/insert_trivially_relocatable.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/insert_trivially_relocatable.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/containers/sequences/vector/insert_trivially_relocatable.pass.cpp @@ -0,0 +1,89 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// void insert(const_iterator it, const value_type& x); + +// UNSUPPORTED: c++03 + +#include +#include +#include + +struct NotTriviallyRelocatable { + ~NotTriviallyRelocatable() {} +}; + +struct TriviallyRelocatable {}; + +// This class is trivial for calls, and therefore __trivially_relocatable, iff TR. +// on compilers +template +struct [[clang::trivial_abi]] MyClass : std::conditional::type { + int* assignments; + int value; + explicit MyClass(int* assign_counter, int i) : assignments(assign_counter), value(i) {} + MyClass(const MyClass& rhs) { + assignments = rhs.assignments; + value = rhs.value; + } + MyClass(MyClass&& rhs) noexcept { + assignments = rhs.assignments; + value = rhs.value; + } + MyClass& operator=(const MyClass& rhs) { + ++*assignments; + value = rhs.value; + return *this; + } + MyClass& operator=(MyClass&& rhs) { + ++*assignments; + value = rhs.value; + return *this; + } + ~MyClass() {} +}; + +static_assert(!std::__libcpp_is_trivially_relocatable>::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable>::value == __has_keyword(__is_trivially_relocatable), + ""); + +template +void test() { + int assignments; + using T = MyClass; + constexpr bool trivially_relocatable = std::__libcpp_is_trivially_relocatable::value; + std::vector vec; + vec.reserve(5); + vec.push_back(T(&assignments, 1)); + vec.push_back(T(&assignments, 2)); + vec.push_back(T(&assignments, 3)); + + assignments = 0; + vec.insert(vec.begin() + 2, T(&assignments, 4)); + if (trivially_relocatable) { + assert(assignments == 0); // relocate upward without assigning + } else { + assert(assignments >= 1); + } + + assignments = 0; + vec.insert(vec.begin() + 2, T(&assignments, 5)); + if (trivially_relocatable) { + assert(assignments == 0); // relocate upward without assigning + } else { + assert(assignments >= 1); + } +} + +int main(int, char**) { + test(); + test(); + return 0; +} diff --git a/libcxx/test/libcxx/utilities/meta/meta.unary.prop/is_trivially_relocatable.compile.pass.cpp b/libcxx/test/libcxx/utilities/meta/meta.unary.prop/is_trivially_relocatable.compile.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/libcxx/utilities/meta/meta.unary.prop/is_trivially_relocatable.compile.pass.cpp @@ -0,0 +1,88 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// type_traits + +// __libcpp_is_trivially_relocatable +// __libcpp_is_trivially_relocatable_v + +// UNSUPPORTED: c++03 + +#include +#include +#include "test_macros.h" + +class Empty {}; + +class Polymorphic { +public: + virtual ~Polymorphic(); +}; + +union Union {}; + +struct bit_zero { + int : 0; +}; + +class Abstract { +public: + virtual ~Abstract() = 0; +}; + +struct NontrivialCopyOnly { + NontrivialCopyOnly(const NontrivialCopyOnly&); +}; + +struct TrivialCopyOnly { + TrivialCopyOnly(const TrivialCopyOnly&) = default; +}; + +#if TEST_STD_VER >= 11 + +struct NontrivialMoveOnly { + NontrivialMoveOnly(NontrivialMoveOnly&&); +}; + +struct TrivialMoveOnly { + TrivialMoveOnly(TrivialMoveOnly&&) = default; +}; +#endif + +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); + +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); + +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); + +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); + +#if TEST_STD_VER >= 11 +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(std::__libcpp_is_trivially_relocatable::value, ""); +static_assert(!std::__libcpp_is_trivially_relocatable::value, ""); +#endif diff --git a/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_exception_safety.pass.cpp b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_exception_safety.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_exception_safety.pass.cpp @@ -0,0 +1,121 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03 + +// + +// void insert(const_iterator it, const value_type& x); + +#include +#include + +static int countdown = 0; +static int constructions = 0; +static int destructions = 0; + +struct MyError {}; + +struct TriviallyRelocatable {}; + +struct NotTriviallyRelocatable { + ~NotTriviallyRelocatable() {} +}; + +template +struct [[clang::trivial_abi]] MyClass : std::conditional::type { + int value; + explicit MyClass(int i) : value(i) { ++constructions; } + MyClass(const MyClass& rhs) _NOEXCEPT_(!CC) { + value = rhs.value; + countdown_if(); + ++constructions; + } + MyClass(MyClass&& rhs) _NOEXCEPT_(!MC) { + value = rhs.value; + countdown_if(); + ++constructions; + } + MyClass& operator=(const MyClass& rhs) _NOEXCEPT_(!CA) { + value = rhs.value; + countdown_if(); + return *this; + } + MyClass& operator=(MyClass&& rhs) _NOEXCEPT_(!MA) { + value = rhs.value; + countdown_if(); + return *this; + } + ~MyClass() { ++destructions; } + template + void countdown_if() { + if (C && countdown-- == 0) + throw MyError(); + } +}; + +template +void test() { + using T = MyClass; + for (int n = 0; true; ++n) { + { + std::vector vec; + vec.reserve(5); + try { + countdown = n; + vec.push_back(T(1)); + vec.push_back(T(2)); + vec.push_back(T(3)); + vec.insert(vec.begin() + 2, T(4)); + vec.insert(vec.begin() + 2, T(5)); + // if we reach here, no exception was thrown + break; + } catch (const MyError&) { + assert(constructions == destructions + static_cast(vec.size())); + } + } + // destroy the vector and check the invariant again + assert(constructions == destructions); + } +} + +int main(int, char**) { + test<0x00>(); + test<0x01>(); + test<0x02>(); + test<0x03>(); + test<0x04>(); + test<0x05>(); + test<0x06>(); + test<0x07>(); + test<0x08>(); + test<0x09>(); + test<0x0a>(); + test<0x0b>(); + test<0x0c>(); + test<0x0d>(); + test<0x0e>(); + test<0x0f>(); + test<0x10>(); + test<0x11>(); + test<0x12>(); + test<0x13>(); + test<0x14>(); + test<0x15>(); + test<0x16>(); + test<0x17>(); + test<0x18>(); + test<0x19>(); + test<0x1a>(); + test<0x1b>(); + test<0x1c>(); + test<0x1d>(); + test<0x1e>(); + test<0x1f>(); + return 0; +}