update openal

This commit is contained in:
AzaezelX 2024-06-30 14:35:57 -05:00
parent 62f3b93ff9
commit 6721a6b021
287 changed files with 33851 additions and 27325 deletions

View file

@ -0,0 +1,38 @@
#include "alassert.h"
#include <exception>
#include <stdexcept>
#include <string>
namespace al {
[[noreturn]]
void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept
{
std::string errstr{filename};
errstr += ':';
errstr += std::to_string(linenum);
errstr += ": ";
errstr += funcname;
errstr += ": ";
errstr += message;
/* Calling std::terminate in a catch block hopefully causes the system to
* provide info about the caught exception in the error dialog. At least on
* Linux, this results in the process printing
*
* terminate called after throwing an instance of 'std::runtime_error'
* what(): <message here>
*
* before terminating from a SIGABRT. Hopefully Windows and Mac will do the
* appropriate things with the message for an abnormal termination.
*/
try {
throw std::runtime_error{errstr};
}
catch(...) {
std::terminate();
}
}
} /* namespace al */

View file

@ -0,0 +1,24 @@
#ifndef AL_ASSERT_H
#define AL_ASSERT_H
#include <array>
#include "opthelpers.h"
namespace al {
[[noreturn]]
void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept;
} /* namespace al */
/* A custom assert macro that is not compiled out for Release/NDEBUG builds,
* making it an appropriate replacement for assert() checks that must not be
* ignored.
*/
#define alassert(cond) do { \
if(!(cond)) UNLIKELY \
al::do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, std::data(__func__)); \
} while(0)
#endif /* AL_ASSERT_H */

View file

@ -1,8 +1,13 @@
#ifndef AL_BIT_H
#define AL_BIT_H
#include <array>
#ifndef __GNUC__
#include <cstdint>
#endif
#include <cstring>
#include <limits>
#include <new>
#include <type_traits>
#if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64))
#include <intrin.h>
@ -10,6 +15,16 @@
namespace al {
template<typename To, typename From>
std::enable_if_t<sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From>
&& std::is_trivially_copyable_v<To>,
To> bit_cast(const From &src) noexcept
{
alignas(To) std::array<char,sizeof(To)> dst;
std::memcpy(dst.data(), &src, sizeof(To));
return *std::launder(reinterpret_cast<To*>(dst.data()));
}
#ifdef __BYTE_ORDER__
enum class endian {
little = __ORDER_LITTLE_ENDIAN__,

View file

@ -1,17 +0,0 @@
#ifndef AL_BYTE_H
#define AL_BYTE_H
#include <cstddef>
#include <cstdint>
#include <limits>
#include <type_traits>
using uint = unsigned int;
namespace al {
using byte = unsigned char;
} // namespace al
#endif /* AL_BYTE_H */

View file

@ -4,10 +4,11 @@
#include "alcomplex.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <functional>
#include <iterator>
#include <utility>
#include "albit.h"
@ -20,36 +21,38 @@ namespace {
using ushort = unsigned short;
using ushort2 = std::pair<ushort,ushort>;
using complex_d = std::complex<double>;
constexpr size_t BitReverseCounter(size_t log2_size) noexcept
constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept
{
/* Some magic math that calculates the number of swaps needed for a
* sequence of bit-reversed indices when index < reversed_index.
*/
return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u));
return (1_zu<<(log2_size-1)) - (1_zu<<((log2_size-1_zu)/2_zu));
}
template<size_t N>
template<std::size_t N>
struct BitReverser {
static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table.");
ushort2 mData[BitReverseCounter(N)]{};
std::array<ushort2,BitReverseCounter(N)> mData{};
constexpr BitReverser()
{
const size_t fftsize{1u << N};
size_t ret_i{0};
const std::size_t fftsize{1u << N};
std::size_t ret_i{0};
/* Bit-reversal permutation applied to a sequence of fftsize items. */
for(size_t idx{1u};idx < fftsize-1;++idx)
for(std::size_t idx{1u};idx < fftsize-1;++idx)
{
size_t revidx{0u}, imask{idx};
for(size_t i{0};i < N;++i)
{
revidx = (revidx<<1) | (imask&1);
imask >>= 1;
}
std::size_t revidx{idx};
revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1);
revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2);
revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4);
revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8);
revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16);
revidx >>= 32-N;
if(idx < revidx)
{
@ -58,14 +61,13 @@ struct BitReverser {
++ret_i;
}
}
assert(ret_i == al::size(mData));
assert(ret_i == std::size(mData));
}
};
/* These bit-reversal swap tables support up to 10-bit indices (1024 elements),
* which is the largest used by OpenAL Soft's filters and effects. Larger FFT
* requests, used by some utilities where performance is less important, will
* use a slower table-less path.
/* These bit-reversal swap tables support up to 11-bit indices (2048 elements),
* which is large enough for the filters and effects in OpenAL Soft. Larger FFT
* requests will use a slower table-less path.
*/
constexpr BitReverser<2> BitReverser2{};
constexpr BitReverser<3> BitReverser3{};
@ -76,7 +78,8 @@ constexpr BitReverser<7> BitReverser7{};
constexpr BitReverser<8> BitReverser8{};
constexpr BitReverser<9> BitReverser9{};
constexpr BitReverser<10> BitReverser10{};
constexpr std::array<al::span<const ushort2>,11> gBitReverses{{
constexpr BitReverser<11> BitReverser11{};
constexpr std::array<al::span<const ushort2>,12> gBitReverses{{
{}, {},
BitReverser2.mData,
BitReverser3.mData,
@ -86,75 +89,124 @@ constexpr std::array<al::span<const ushort2>,11> gBitReverses{{
BitReverser7.mData,
BitReverser8.mData,
BitReverser9.mData,
BitReverser10.mData
BitReverser10.mData,
BitReverser11.mData
}};
/* Lookup table for std::polar(1, pi / (1<<index)); */
template<typename T>
constexpr std::array<std::complex<T>,gBitReverses.size()-1> gArgAngle{{
{static_cast<T>(-1.00000000000000000e+00), static_cast<T>(0.00000000000000000e+00)},
{static_cast<T>( 0.00000000000000000e+00), static_cast<T>(1.00000000000000000e+00)},
{static_cast<T>( 7.07106781186547524e-01), static_cast<T>(7.07106781186547524e-01)},
{static_cast<T>( 9.23879532511286756e-01), static_cast<T>(3.82683432365089772e-01)},
{static_cast<T>( 9.80785280403230449e-01), static_cast<T>(1.95090322016128268e-01)},
{static_cast<T>( 9.95184726672196886e-01), static_cast<T>(9.80171403295606020e-02)},
{static_cast<T>( 9.98795456205172393e-01), static_cast<T>(4.90676743274180143e-02)},
{static_cast<T>( 9.99698818696204220e-01), static_cast<T>(2.45412285229122880e-02)},
{static_cast<T>( 9.99924701839144541e-01), static_cast<T>(1.22715382857199261e-02)},
{static_cast<T>( 9.99981175282601143e-01), static_cast<T>(6.13588464915447536e-03)},
{static_cast<T>( 9.99995293809576172e-01), static_cast<T>(3.06795676296597627e-03)}
}};
} // namespace
template<typename Real>
std::enable_if_t<std::is_floating_point<Real>::value>
complex_fft(const al::span<std::complex<Real>> buffer, const al::type_identity_t<Real> sign)
void complex_fft(const al::span<std::complex<double>> buffer, const double sign)
{
const size_t fftsize{buffer.size()};
const std::size_t fftsize{buffer.size()};
/* Get the number of bits used for indexing. Simplifies bit-reversal and
* the main loop count.
*/
const size_t log2_size{static_cast<size_t>(al::countr_zero(fftsize))};
const std::size_t log2_size{static_cast<std::size_t>(al::countr_zero(fftsize))};
if(log2_size >= gBitReverses.size()) UNLIKELY
if(log2_size < gBitReverses.size()) LIKELY
{
for(size_t idx{1u};idx < fftsize-1;++idx)
for(auto &rev : gBitReverses[log2_size])
std::swap(buffer[rev.first], buffer[rev.second]);
/* Iterative form of Danielson-Lanczos lemma */
for(std::size_t i{0};i < log2_size;++i)
{
size_t revidx{0u}, imask{idx};
for(size_t i{0};i < log2_size;++i)
const std::size_t step2{1_uz << i};
const std::size_t step{2_uz << i};
/* The first iteration of the inner loop would have u=1, which we
* can simplify to remove a number of complex multiplies.
*/
for(std::size_t k{0};k < fftsize;k+=step)
{
revidx = (revidx<<1) | (imask&1);
imask >>= 1;
}
if(idx < revidx)
std::swap(buffer[idx], buffer[revidx]);
}
}
else for(auto &rev : gBitReverses[log2_size])
std::swap(buffer[rev.first], buffer[rev.second]);
/* Iterative form of Danielson-Lanczos lemma */
const Real pi{al::numbers::pi_v<Real> * sign};
size_t step2{1u};
for(size_t i{0};i < log2_size;++i)
{
const Real arg{pi / static_cast<Real>(step2)};
/* TODO: Would std::polar(1.0, arg) be any better? */
const std::complex<Real> w{std::cos(arg), std::sin(arg)};
std::complex<Real> u{1.0, 0.0};
const size_t step{step2 << 1};
for(size_t j{0};j < step2;j++)
{
for(size_t k{j};k < fftsize;k+=step)
{
std::complex<Real> temp{buffer[k+step2] * u};
const complex_d temp{buffer[k+step2]};
buffer[k+step2] = buffer[k] - temp;
buffer[k] += temp;
}
u *= w;
const complex_d w{gArgAngle<double>[i].real(), gArgAngle<double>[i].imag()*sign};
complex_d u{w};
for(std::size_t j{1};j < step2;j++)
{
for(std::size_t k{j};k < fftsize;k+=step)
{
const complex_d temp{buffer[k+step2] * u};
buffer[k+step2] = buffer[k] - temp;
buffer[k] += temp;
}
u *= w;
}
}
}
else
{
assert(log2_size < 32);
for(std::size_t idx{1u};idx < fftsize-1;++idx)
{
std::size_t revidx{idx};
revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1);
revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2);
revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4);
revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8);
revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16);
revidx >>= 32-log2_size;
if(idx < revidx)
std::swap(buffer[idx], buffer[revidx]);
}
step2 <<= 1;
const double pi{al::numbers::pi * sign};
for(std::size_t i{0};i < log2_size;++i)
{
const std::size_t step2{1_uz << i};
const std::size_t step{2_uz << i};
for(std::size_t k{0};k < fftsize;k+=step)
{
const complex_d temp{buffer[k+step2]};
buffer[k+step2] = buffer[k] - temp;
buffer[k] += temp;
}
const double arg{pi / static_cast<double>(step2)};
const complex_d w{std::polar(1.0, arg)};
complex_d u{w};
for(std::size_t j{1};j < step2;j++)
{
for(std::size_t k{j};k < fftsize;k+=step)
{
const complex_d temp{buffer[k+step2] * u};
buffer[k+step2] = buffer[k] - temp;
buffer[k] += temp;
}
u *= w;
}
}
}
}
void complex_hilbert(const al::span<std::complex<double>> buffer)
{
using namespace std::placeholders;
inverse_fft(buffer);
const double inverse_size = 1.0/static_cast<double>(buffer.size());
auto bufiter = buffer.begin();
const auto halfiter = bufiter + (buffer.size()>>1);
const auto halfiter = bufiter + ptrdiff_t(buffer.size()>>1);
*bufiter *= inverse_size; ++bufiter;
bufiter = std::transform(bufiter, halfiter, bufiter,
@ -165,7 +217,3 @@ void complex_hilbert(const al::span<std::complex<double>> buffer)
forward_fft(buffer);
}
template void complex_fft<>(const al::span<std::complex<float>> buffer, const float sign);
template void complex_fft<>(const al::span<std::complex<double>> buffer, const double sign);

View file

@ -2,7 +2,6 @@
#define ALCOMPLEX_H
#include <complex>
#include <type_traits>
#include "alspan.h"
@ -11,27 +10,21 @@
* FFT and 1 is inverse FFT. Applies the Discrete Fourier Transform (DFT) to
* the data supplied in the buffer, which MUST BE power of two.
*/
template<typename Real>
std::enable_if_t<std::is_floating_point<Real>::value>
complex_fft(const al::span<std::complex<Real>> buffer, const al::type_identity_t<Real> sign);
void complex_fft(const al::span<std::complex<double>> buffer, const double sign);
/**
* Calculate the frequency-domain response of the time-domain signal in the
* provided buffer, which MUST BE power of two.
*/
template<typename Real, size_t N>
std::enable_if_t<std::is_floating_point<Real>::value>
forward_fft(const al::span<std::complex<Real>,N> buffer)
{ complex_fft(buffer.subspan(0), -1); }
inline void forward_fft(const al::span<std::complex<double>> buffer)
{ complex_fft(buffer, -1.0); }
/**
* Calculate the time-domain signal of the frequency-domain response in the
* provided buffer, which MUST BE power of two.
*/
template<typename Real, size_t N>
std::enable_if_t<std::is_floating_point<Real>::value>
inverse_fft(const al::span<std::complex<Real>,N> buffer)
{ complex_fft(buffer.subspan(0), 1); }
inline void inverse_fft(const al::span<std::complex<double>> buffer)
{ complex_fft(buffer, +1.0); }
/**
* Calculate the complex helical sequence (discrete-time analytical signal) of

View file

@ -1,16 +0,0 @@
#ifndef ALDEQUE_H
#define ALDEQUE_H
#include <deque>
#include "almalloc.h"
namespace al {
template<typename T>
using deque = std::deque<T, al::allocator<T>>;
} // namespace al
#endif /* ALDEQUE_H */

View file

@ -1,26 +0,0 @@
#include "config.h"
#include "alfstream.h"
#include "strutils.h"
#ifdef _WIN32
namespace al {
ifstream::ifstream(const char *filename, std::ios_base::openmode mode)
: std::ifstream{utf8_to_wstr(filename).c_str(), mode}
{ }
void ifstream::open(const char *filename, std::ios_base::openmode mode)
{
std::wstring wstr{utf8_to_wstr(filename)};
std::ifstream::open(wstr.c_str(), mode);
}
ifstream::~ifstream() = default;
} // namespace al
#endif

View file

@ -1,45 +0,0 @@
#ifndef AL_FSTREAM_H
#define AL_FSTREAM_H
#ifdef _WIN32
#include <string>
#include <fstream>
namespace al {
// Inherit from std::ifstream to accept UTF-8 filenames
class ifstream final : public std::ifstream {
public:
explicit ifstream(const char *filename, std::ios_base::openmode mode=std::ios_base::in);
explicit ifstream(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in)
: ifstream{filename.c_str(), mode} { }
explicit ifstream(const wchar_t *filename, std::ios_base::openmode mode=std::ios_base::in)
: std::ifstream{filename, mode} { }
explicit ifstream(const std::wstring &filename, std::ios_base::openmode mode=std::ios_base::in)
: ifstream{filename.c_str(), mode} { }
void open(const char *filename, std::ios_base::openmode mode=std::ios_base::in);
void open(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in)
{ open(filename.c_str(), mode); }
~ifstream() override;
};
} // namespace al
#else /* _WIN32 */
#include <fstream>
namespace al {
using ifstream = std::ifstream;
} // namespace al
#endif /* _WIN32 */
#endif /* AL_FSTREAM_H */

View file

@ -1,61 +0,0 @@
#include "config.h"
#include "almalloc.h"
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <memory>
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
void *al_malloc(size_t alignment, size_t size)
{
assert((alignment & (alignment-1)) == 0);
alignment = std::max(alignment, alignof(std::max_align_t));
#if defined(HAVE_POSIX_MEMALIGN)
void *ret{};
if(posix_memalign(&ret, alignment, size) == 0)
return ret;
return nullptr;
#elif defined(HAVE__ALIGNED_MALLOC)
return _aligned_malloc(size, alignment);
#else
size_t total_size{size + alignment-1 + sizeof(void*)};
void *base{std::malloc(total_size)};
if(base != nullptr)
{
void *aligned_ptr{static_cast<char*>(base) + sizeof(void*)};
total_size -= sizeof(void*);
std::align(alignment, size, aligned_ptr, total_size);
*(static_cast<void**>(aligned_ptr)-1) = base;
base = aligned_ptr;
}
return base;
#endif
}
void *al_calloc(size_t alignment, size_t size)
{
void *ret{al_malloc(alignment, size)};
if(ret) std::memset(ret, 0, size);
return ret;
}
void al_free(void *ptr) noexcept
{
#if defined(HAVE_POSIX_MEMALIGN)
std::free(ptr);
#elif defined(HAVE__ALIGNED_MALLOC)
_aligned_free(ptr);
#else
if(ptr != nullptr)
std::free(*(static_cast<void**>(ptr) - 1));
#endif
}

View file

@ -3,49 +3,24 @@
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <limits>
#include <memory>
#include <new>
#include <type_traits>
#include <utility>
#include "pragmadefs.h"
#include <variant>
void al_free(void *ptr) noexcept;
[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]]
void *al_malloc(size_t alignment, size_t size);
[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]]
void *al_calloc(size_t alignment, size_t size);
namespace gsl {
template<typename T> using owner = T;
}
#define DISABLE_ALLOC() \
#define DISABLE_ALLOC \
void *operator new(size_t) = delete; \
void *operator new[](size_t) = delete; \
void operator delete(void*) noexcept = delete; \
void operator delete[](void*) noexcept = delete;
#define DEF_NEWDEL(T) \
void *operator new(size_t size) \
{ \
static_assert(&operator new == &T::operator new, \
"Incorrect container type specified"); \
if(void *ret{al_malloc(alignof(T), size)}) \
return ret; \
throw std::bad_alloc(); \
} \
void *operator new[](size_t size) { return operator new(size); } \
void operator delete(void *block) noexcept { al_free(block); } \
void operator delete[](void *block) noexcept { operator delete(block); }
#define DEF_PLACE_NEWDEL() \
void *operator new(size_t /*size*/, void *ptr) noexcept { return ptr; } \
void *operator new[](size_t /*size*/, void *ptr) noexcept { return ptr; } \
void operator delete(void *block, void*) noexcept { al_free(block); } \
void operator delete(void *block) noexcept { al_free(block); } \
void operator delete[](void *block, void*) noexcept { al_free(block); } \
void operator delete[](void *block) noexcept { al_free(block); }
enum FamCount : size_t { };
@ -58,56 +33,64 @@ enum FamCount : size_t { };
sizeof(T)); \
} \
\
void *operator new(size_t /*size*/, FamCount count) \
gsl::owner<void*> operator new(size_t /*size*/, FamCount count) \
{ \
if(void *ret{al_malloc(alignof(T), T::Sizeof(count))}) \
return ret; \
throw std::bad_alloc(); \
const auto alignment = std::align_val_t{alignof(T)}; \
return ::operator new[](T::Sizeof(count), alignment); \
} \
void operator delete(gsl::owner<void*> block, FamCount) noexcept \
{ ::operator delete[](block, std::align_val_t{alignof(T)}); } \
void operator delete(gsl::owner<void*> block) noexcept \
{ ::operator delete[](block, std::align_val_t{alignof(T)}); } \
void *operator new[](size_t /*size*/) = delete; \
void operator delete(void *block, FamCount) { al_free(block); } \
void operator delete(void *block) noexcept { al_free(block); } \
void operator delete[](void* /*block*/) = delete;
namespace al {
template<typename T, std::size_t Align=alignof(T)>
template<typename T, std::size_t AlignV=alignof(T)>
struct allocator {
static constexpr std::size_t alignment{std::max(Align, alignof(T))};
static constexpr auto Alignment = std::max(AlignV, alignof(T));
static constexpr auto AlignVal = std::align_val_t{Alignment};
using value_type = T;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using const_pointer = const T*;
using value_type = std::remove_cv_t<std::remove_reference_t<T>>;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using is_always_equal = std::true_type;
template<typename U>
template<typename U, std::enable_if_t<alignof(U) <= Alignment,bool> = true>
struct rebind {
using other = allocator<U, Align>;
using other = allocator<U,Alignment>;
};
constexpr explicit allocator() noexcept = default;
template<typename U, std::size_t N>
constexpr explicit allocator(const allocator<U,N>&) noexcept { }
constexpr explicit allocator(const allocator<U,N>&) noexcept
{ static_assert(Alignment == allocator<U,N>::Alignment); }
T *allocate(std::size_t n)
gsl::owner<T*> allocate(std::size_t n)
{
if(n > std::numeric_limits<std::size_t>::max()/sizeof(T)) throw std::bad_alloc();
if(auto p = al_malloc(alignment, n*sizeof(T))) return static_cast<T*>(p);
throw std::bad_alloc();
return static_cast<gsl::owner<T*>>(::operator new[](n*sizeof(T), AlignVal));
}
void deallocate(T *p, std::size_t) noexcept { al_free(p); }
void deallocate(gsl::owner<T*> p, std::size_t) noexcept
{ ::operator delete[](gsl::owner<void*>{p}, AlignVal); }
};
template<typename T, std::size_t N, typename U, std::size_t M>
constexpr bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept { return true; }
constexpr bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept
{ return allocator<T,N>::Alignment == allocator<U,M>::Alignment; }
template<typename T, std::size_t N, typename U, std::size_t M>
constexpr bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept { return false; }
constexpr bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept
{ return allocator<T,N>::Alignment != allocator<U,M>::Alignment; }
#ifdef __cpp_lib_to_address
using std::to_address;
#else
template<typename T>
constexpr T *to_address(T *p) noexcept
{
@ -117,194 +100,55 @@ constexpr T *to_address(T *p) noexcept
template<typename T>
constexpr auto to_address(const T &p) noexcept
{ return to_address(p.operator->()); }
{
return ::al::to_address(p.operator->());
}
#endif
template<typename T, typename ...Args>
constexpr T* construct_at(T *ptr, Args&& ...args)
noexcept(std::is_nothrow_constructible<T, Args...>::value)
{ return ::new(static_cast<void*>(ptr)) T{std::forward<Args>(args)...}; }
/* At least VS 2015 complains that 'ptr' is unused when the given type's
* destructor is trivial (a no-op). So disable that warning for this call.
*/
DIAGNOSTIC_PUSH
msc_pragma(warning(disable : 4100))
template<typename T>
constexpr std::enable_if_t<!std::is_array<T>::value>
destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<T>::value)
{ ptr->~T(); }
DIAGNOSTIC_POP
template<typename T>
constexpr std::enable_if_t<std::is_array<T>::value>
destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<std::remove_all_extents_t<T>>::value)
noexcept(std::is_nothrow_constructible_v<T, Args...>)
{
for(auto &elem : *ptr)
al::destroy_at(std::addressof(elem));
}
template<typename T>
constexpr void destroy(T first, T end) noexcept(noexcept(al::destroy_at(std::addressof(*first))))
{
while(first != end)
{
al::destroy_at(std::addressof(*first));
++first;
}
}
template<typename T, typename N>
constexpr std::enable_if_t<std::is_integral<N>::value,T>
destroy_n(T first, N count) noexcept(noexcept(al::destroy_at(std::addressof(*first))))
{
if(count != 0)
{
do {
al::destroy_at(std::addressof(*first));
++first;
} while(--count);
}
return first;
/* NOLINTBEGIN(cppcoreguidelines-owning-memory) construct_at doesn't
* necessarily handle the address from an owner, while placement new
* expects to.
*/
return ::new(static_cast<void*>(ptr)) T{std::forward<Args>(args)...};
/* NOLINTEND(cppcoreguidelines-owning-memory) */
}
template<typename T, typename N>
inline std::enable_if_t<std::is_integral<N>::value,
T> uninitialized_default_construct_n(T first, N count)
{
using ValueT = typename std::iterator_traits<T>::value_type;
T current{first};
if(count != 0)
template<typename SP, typename PT, typename ...Args>
class out_ptr_t {
static_assert(!std::is_same_v<PT,void*>);
SP &mRes;
std::variant<PT,void*> mPtr{};
public:
out_ptr_t(SP &res) : mRes{res} { }
~out_ptr_t()
{
try {
do {
::new(static_cast<void*>(std::addressof(*current))) ValueT;
++current;
} while(--count);
}
catch(...) {
al::destroy(first, current);
throw;
}
auto set_res = [this](auto &ptr)
{ mRes.reset(static_cast<PT>(ptr)); };
std::visit(set_res, mPtr);
}
return current;
}
out_ptr_t(const out_ptr_t&) = delete;
out_ptr_t& operator=(const out_ptr_t&) = delete;
operator PT*() noexcept
{ return &std::get<PT>(mPtr); }
/* Storage for flexible array data. This is trivially destructible if type T is
* trivially destructible.
*/
template<typename T, size_t alignment, bool = std::is_trivially_destructible<T>::value>
struct FlexArrayStorage {
const size_t mSize;
union {
char mDummy;
alignas(alignment) T mArray[1];
};
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
{
const size_t len{sizeof(T)*count};
return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base;
}
FlexArrayStorage(size_t size) : mSize{size}
{ al::uninitialized_default_construct_n(mArray, mSize); }
~FlexArrayStorage() = default;
FlexArrayStorage(const FlexArrayStorage&) = delete;
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
operator void**() noexcept
{ return &mPtr.template emplace<void*>(); }
};
template<typename T, size_t alignment>
struct FlexArrayStorage<T,alignment,false> {
const size_t mSize;
union {
char mDummy;
alignas(alignment) T mArray[1];
};
static constexpr size_t Sizeof(size_t count, size_t base) noexcept
{
const size_t len{sizeof(T)*count};
return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base;
}
FlexArrayStorage(size_t size) : mSize{size}
{ al::uninitialized_default_construct_n(mArray, mSize); }
~FlexArrayStorage() { al::destroy_n(mArray, mSize); }
FlexArrayStorage(const FlexArrayStorage&) = delete;
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
};
/* A flexible array type. Used either standalone or at the end of a parent
* struct, with placement new, to have a run-time-sized array that's embedded
* with its size.
*/
template<typename T, size_t alignment=alignof(T)>
struct FlexArray {
using element_type = T;
using value_type = std::remove_cv_t<T>;
using index_type = size_t;
using difference_type = ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using Storage_t_ = FlexArrayStorage<element_type,alignment>;
Storage_t_ mStore;
static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept
{ return Storage_t_::Sizeof(count, base); }
static std::unique_ptr<FlexArray> Create(index_type count)
{
void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))};
return std::unique_ptr<FlexArray>{al::construct_at(static_cast<FlexArray*>(ptr), count)};
}
FlexArray(index_type size) : mStore{size} { }
~FlexArray() = default;
index_type size() const noexcept { return mStore.mSize; }
bool empty() const noexcept { return mStore.mSize == 0; }
pointer data() noexcept { return mStore.mArray; }
const_pointer data() const noexcept { return mStore.mArray; }
reference operator[](index_type i) noexcept { return mStore.mArray[i]; }
const_reference operator[](index_type i) const noexcept { return mStore.mArray[i]; }
reference front() noexcept { return mStore.mArray[0]; }
const_reference front() const noexcept { return mStore.mArray[0]; }
reference back() noexcept { return mStore.mArray[mStore.mSize-1]; }
const_reference back() const noexcept { return mStore.mArray[mStore.mSize-1]; }
iterator begin() noexcept { return mStore.mArray; }
const_iterator begin() const noexcept { return mStore.mArray; }
const_iterator cbegin() const noexcept { return mStore.mArray; }
iterator end() noexcept { return mStore.mArray + mStore.mSize; }
const_iterator end() const noexcept { return mStore.mArray + mStore.mSize; }
const_iterator cend() const noexcept { return mStore.mArray + mStore.mSize; }
reverse_iterator rbegin() noexcept { return end(); }
const_reverse_iterator rbegin() const noexcept { return end(); }
const_reverse_iterator crbegin() const noexcept { return cend(); }
reverse_iterator rend() noexcept { return begin(); }
const_reverse_iterator rend() const noexcept { return begin(); }
const_reverse_iterator crend() const noexcept { return cbegin(); }
DEF_PLACE_NEWDEL()
};
template<typename T=void, typename SP, typename ...Args>
auto out_ptr(SP &res)
{
using ptype = typename SP::element_type*;
return out_ptr_t<SP,ptype>{res};
}
} // namespace al

View file

@ -1,11 +1,9 @@
#ifndef COMMON_ALNUMBERS_H
#define COMMON_ALNUMBERS_H
#include <utility>
#include <type_traits>
namespace al {
namespace numbers {
namespace al::numbers {
namespace detail_ {
template<typename T>
@ -13,24 +11,22 @@ namespace detail_ {
} // detail_
template<typename T>
static constexpr auto pi_v = detail_::as_fp<T>(3.141592653589793238462643383279502884L);
inline constexpr auto pi_v = detail_::as_fp<T>(3.141592653589793238462643383279502884L);
template<typename T>
static constexpr auto inv_pi_v = detail_::as_fp<T>(0.318309886183790671537767526745028724L);
inline constexpr auto inv_pi_v = detail_::as_fp<T>(0.318309886183790671537767526745028724L);
template<typename T>
static constexpr auto sqrt2_v = detail_::as_fp<T>(1.414213562373095048801688724209698079L);
inline constexpr auto sqrt2_v = detail_::as_fp<T>(1.414213562373095048801688724209698079L);
template<typename T>
static constexpr auto sqrt3_v = detail_::as_fp<T>(1.732050807568877293527446341505872367L);
inline constexpr auto sqrt3_v = detail_::as_fp<T>(1.732050807568877293527446341505872367L);
static constexpr auto pi = pi_v<double>;
static constexpr auto inv_pi = inv_pi_v<double>;
static constexpr auto sqrt2 = sqrt2_v<double>;
static constexpr auto sqrt3 = sqrt3_v<double>;
inline constexpr auto pi = pi_v<double>;
inline constexpr auto inv_pi = inv_pi_v<double>;
inline constexpr auto sqrt2 = sqrt2_v<double>;
inline constexpr auto sqrt3 = sqrt3_v<double>;
} // namespace numbers
} // namespace al
} // namespace al::numbers
#endif /* COMMON_ALNUMBERS_H */

View file

@ -2,9 +2,12 @@
#define AL_NUMERIC_H
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <type_traits>
#ifdef HAVE_INTRIN_H
#include <intrin.h>
#endif
@ -12,75 +15,34 @@
#include <xmmintrin.h>
#endif
#include "albit.h"
#include "altraits.h"
#include "opthelpers.h"
inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast<int64_t>(n); }
inline constexpr uint64_t operator "" _u64(unsigned long long int n) noexcept { return static_cast<uint64_t>(n); }
constexpr auto operator "" _i64(unsigned long long n) noexcept { return static_cast<std::int64_t>(n); }
constexpr auto operator "" _u64(unsigned long long n) noexcept { return static_cast<std::uint64_t>(n); }
constexpr auto operator "" _z(unsigned long long n) noexcept
{ return static_cast<std::make_signed_t<std::size_t>>(n); }
constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_cast<std::size_t>(n); }
constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast<std::size_t>(n); }
constexpr inline float minf(float a, float b) noexcept
{ return ((a > b) ? b : a); }
constexpr inline float maxf(float a, float b) noexcept
{ return ((a > b) ? a : b); }
constexpr inline float clampf(float val, float min, float max) noexcept
{ return minf(max, maxf(min, val)); }
constexpr inline double mind(double a, double b) noexcept
{ return ((a > b) ? b : a); }
constexpr inline double maxd(double a, double b) noexcept
{ return ((a > b) ? a : b); }
constexpr inline double clampd(double val, double min, double max) noexcept
{ return mind(max, maxd(min, val)); }
constexpr inline unsigned int minu(unsigned int a, unsigned int b) noexcept
{ return ((a > b) ? b : a); }
constexpr inline unsigned int maxu(unsigned int a, unsigned int b) noexcept
{ return ((a > b) ? a : b); }
constexpr inline unsigned int clampu(unsigned int val, unsigned int min, unsigned int max) noexcept
{ return minu(max, maxu(min, val)); }
constexpr inline int mini(int a, int b) noexcept
{ return ((a > b) ? b : a); }
constexpr inline int maxi(int a, int b) noexcept
{ return ((a > b) ? a : b); }
constexpr inline int clampi(int val, int min, int max) noexcept
{ return mini(max, maxi(min, val)); }
constexpr inline int64_t mini64(int64_t a, int64_t b) noexcept
{ return ((a > b) ? b : a); }
constexpr inline int64_t maxi64(int64_t a, int64_t b) noexcept
{ return ((a > b) ? a : b); }
constexpr inline int64_t clampi64(int64_t val, int64_t min, int64_t max) noexcept
{ return mini64(max, maxi64(min, val)); }
constexpr inline uint64_t minu64(uint64_t a, uint64_t b) noexcept
{ return ((a > b) ? b : a); }
constexpr inline uint64_t maxu64(uint64_t a, uint64_t b) noexcept
{ return ((a > b) ? a : b); }
constexpr inline uint64_t clampu64(uint64_t val, uint64_t min, uint64_t max) noexcept
{ return minu64(max, maxu64(min, val)); }
constexpr inline size_t minz(size_t a, size_t b) noexcept
{ return ((a > b) ? b : a); }
constexpr inline size_t maxz(size_t a, size_t b) noexcept
{ return ((a > b) ? a : b); }
constexpr inline size_t clampz(size_t val, size_t min, size_t max) noexcept
{ return minz(max, maxz(min, val)); }
constexpr auto GetCounterSuffix(size_t count) noexcept -> const char*
{
auto &suffix = (((count%100)/10) == 1) ? "th" :
((count%10) == 1) ? "st" :
((count%10) == 2) ? "nd" :
((count%10) == 3) ? "rd" : "th";
return std::data(suffix);
}
constexpr inline float lerpf(float val1, float val2, float mu) noexcept
{ return val1 + (val2-val1)*mu; }
constexpr inline float cubic(float val1, float val2, float val3, float val4, float mu) noexcept
{
const float mu2{mu*mu}, mu3{mu2*mu};
const float a0{-0.5f*mu3 + mu2 + -0.5f*mu};
const float a1{ 1.5f*mu3 + -2.5f*mu2 + 1.0f};
const float a2{-1.5f*mu3 + 2.0f*mu2 + 0.5f*mu};
const float a3{ 0.5f*mu3 + -0.5f*mu2};
return val1*a0 + val2*a1 + val3*a2 + val4*a3;
}
constexpr inline double lerpd(double val1, double val2, double mu) noexcept
{ return val1 + (val2-val1)*mu; }
/** Find the next power-of-2 for non-power-of-2 numbers. */
@ -125,21 +87,18 @@ inline int fastf2i(float f) noexcept
#if defined(HAVE_SSE_INTRINSICS)
return _mm_cvt_ss2si(_mm_set_ss(f));
#elif defined(_MSC_VER) && defined(_M_IX86_FP)
#elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0
int i;
__asm fld f
__asm fistp i
return i;
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
&& !defined(__SSE_MATH__)
int i;
#ifdef __SSE_MATH__
__asm__("cvtss2si %1, %0" : "=r"(i) : "x"(f));
#else
__asm__ __volatile__("fistpl %0" : "=m"(i) : "t"(f) : "st");
#endif
return i;
#else
@ -159,21 +118,16 @@ inline int float2int(float f) noexcept
#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \
|| ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
&& !defined(__SSE_MATH__))
int sign, shift, mant;
union {
float f;
int i;
} conv;
const int conv_i{al::bit_cast<int>(f)};
conv.f = f;
sign = (conv.i>>31) | 1;
shift = ((conv.i>>23)&0xff) - (127+23);
const int sign{(conv_i>>31) | 1};
const int shift{((conv_i>>23)&0xff) - (127+23)};
/* Over/underflow */
if(shift >= 31 || shift < -23) UNLIKELY
return 0;
mant = (conv.i&0x7fffff) | 0x800000;
const int mant{(conv_i&0x7fffff) | 0x800000};
if(shift < 0) LIKELY
return (mant >> -shift) * sign;
return (mant << shift) * sign;
@ -195,25 +149,19 @@ inline int double2int(double d) noexcept
#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \
|| ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
&& !defined(__SSE2_MATH__))
int sign, shift;
int64_t mant;
union {
double d;
int64_t i64;
} conv;
const int64_t conv_i64{al::bit_cast<int64_t>(d)};
conv.d = d;
sign = (conv.i64 >> 63) | 1;
shift = ((conv.i64 >> 52) & 0x7ff) - (1023 + 52);
const int sign{static_cast<int>(conv_i64 >> 63) | 1};
const int shift{(static_cast<int>(conv_i64 >> 52) & 0x7ff) - (1023 + 52)};
/* Over/underflow */
if(shift >= 63 || shift < -52) UNLIKELY
return 0;
mant = (conv.i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64;
const int64_t mant{(conv_i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64};
if(shift < 0) LIKELY
return (int)(mant >> -shift) * sign;
return (int)(mant << shift) * sign;
return static_cast<int>(mant >> -shift) * sign;
return static_cast<int>(mant << shift) * sign;
#else
@ -246,19 +194,14 @@ inline float fast_roundf(float f) noexcept
/* Integral limit, where sub-integral precision is not available for
* floats.
*/
static const float ilim[2]{
static constexpr std::array ilim{
8388608.0f /* 0x1.0p+23 */,
-8388608.0f /* -0x1.0p+23 */
};
unsigned int sign, expo;
union {
float f;
unsigned int i;
} conv;
const unsigned int conv_i{al::bit_cast<unsigned int>(f)};
conv.f = f;
sign = (conv.i>>31)&0x01;
expo = (conv.i>>23)&0xff;
const unsigned int sign{(conv_i>>31)&0x01};
const unsigned int expo{(conv_i>>23)&0xff};
if(expo >= 150/*+23*/) UNLIKELY
{
@ -275,20 +218,18 @@ inline float fast_roundf(float f) noexcept
* optimize this out because of non-associative rules on floating-point
* math (as long as you don't use -fassociative-math,
* -funsafe-math-optimizations, -ffast-math, or -Ofast, in which case this
* may break).
* may break without __builtin_assoc_barrier support).
*/
#if HAS_BUILTIN(__builtin_assoc_barrier)
return __builtin_assoc_barrier(f + ilim[sign]) - ilim[sign];
#else
f += ilim[sign];
return f - ilim[sign];
#endif
#endif
}
template<typename T>
constexpr const T& clamp(const T& value, const T& min_value, const T& max_value) noexcept
{
return std::min(std::max(value, min_value), max_value);
}
// Converts level (mB) to gain.
inline float level_mb_to_gain(float x)
{
@ -302,7 +243,7 @@ inline float gain_to_level_mb(float x)
{
if (x <= 0.0f)
return -10'000.0f;
return maxf(std::log10(x) * 2'000.0f, -10'000.0f);
return std::max(std::log10(x) * 2'000.0f, -10'000.0f);
}
#endif /* AL_NUMERIC_H */

View file

@ -1,353 +0,0 @@
#ifndef AL_OPTIONAL_H
#define AL_OPTIONAL_H
#include <initializer_list>
#include <type_traits>
#include <utility>
#include "almalloc.h"
namespace al {
struct nullopt_t { };
struct in_place_t { };
constexpr nullopt_t nullopt{};
constexpr in_place_t in_place{};
#define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__))
namespace detail_ {
/* Base storage struct for an optional. Defines a trivial destructor, for types
* that can be trivially destructed.
*/
template<typename T, bool = std::is_trivially_destructible<T>::value>
struct optstore_base {
bool mHasValue{false};
union {
char mDummy{};
T mValue;
};
constexpr optstore_base() noexcept { }
template<typename ...Args>
constexpr explicit optstore_base(in_place_t, Args&& ...args)
noexcept(std::is_nothrow_constructible<T, Args...>::value)
: mHasValue{true}, mValue{std::forward<Args>(args)...}
{ }
~optstore_base() = default;
};
/* Specialization needing a non-trivial destructor. */
template<typename T>
struct optstore_base<T, false> {
bool mHasValue{false};
union {
char mDummy{};
T mValue;
};
constexpr optstore_base() noexcept { }
template<typename ...Args>
constexpr explicit optstore_base(in_place_t, Args&& ...args)
noexcept(std::is_nothrow_constructible<T, Args...>::value)
: mHasValue{true}, mValue{std::forward<Args>(args)...}
{ }
~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); }
};
/* Next level of storage, which defines helpers to construct and destruct the
* stored object.
*/
template<typename T>
struct optstore_helper : public optstore_base<T> {
using optstore_base<T>::optstore_base;
template<typename... Args>
constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible<T, Args...>::value)
{
al::construct_at(std::addressof(this->mValue), std::forward<Args>(args)...);
this->mHasValue = true;
}
constexpr void reset() noexcept
{
if(this->mHasValue)
al::destroy_at(std::addressof(this->mValue));
this->mHasValue = false;
}
constexpr void assign(const optstore_helper &rhs)
noexcept(std::is_nothrow_copy_constructible<T>::value
&& std::is_nothrow_copy_assignable<T>::value)
{
if(!rhs.mHasValue)
this->reset();
else if(this->mHasValue)
this->mValue = rhs.mValue;
else
this->construct(rhs.mValue);
}
constexpr void assign(optstore_helper&& rhs)
noexcept(std::is_nothrow_move_constructible<T>::value
&& std::is_nothrow_move_assignable<T>::value)
{
if(!rhs.mHasValue)
this->reset();
else if(this->mHasValue)
this->mValue = std::move(rhs.mValue);
else
this->construct(std::move(rhs.mValue));
}
};
/* Define copy and move constructors and assignment operators, which may or may
* not be trivial.
*/
template<typename T, bool trivial_copy = std::is_trivially_copy_constructible<T>::value,
bool trivial_move = std::is_trivially_move_constructible<T>::value,
/* Trivial assignment is dependent on trivial construction+destruction. */
bool = trivial_copy && std::is_trivially_copy_assignable<T>::value
&& std::is_trivially_destructible<T>::value,
bool = trivial_move && std::is_trivially_move_assignable<T>::value
&& std::is_trivially_destructible<T>::value>
struct optional_storage;
/* Some versions of GCC have issues with 'this' in the following noexcept(...)
* statements, so this macro is a workaround.
*/
#define _this std::declval<optional_storage*>()
/* Completely trivial. */
template<typename T>
struct optional_storage<T, true, true, true, true> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage&) = default;
constexpr optional_storage& operator=(optional_storage&&) = default;
};
/* Non-trivial move assignment. */
template<typename T>
struct optional_storage<T, true, true, true, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage&) = default;
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Non-trivial move construction. */
template<typename T>
struct optional_storage<T, true, false, true, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
constexpr optional_storage& operator=(const optional_storage&) = default;
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Non-trivial copy assignment. */
template<typename T>
struct optional_storage<T, true, true, false, true> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&&) = default;
};
/* Non-trivial copy construction. */
template<typename T>
struct optional_storage<T, false, true, false, true> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&&) = default;
};
/* Non-trivial assignment. */
template<typename T>
struct optional_storage<T, true, true, false, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Non-trivial assignment, non-trivial move construction. */
template<typename T>
struct optional_storage<T, true, false, false, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage&) = default;
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Non-trivial assignment, non-trivial copy construction. */
template<typename T>
struct optional_storage<T, false, true, false, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
constexpr optional_storage(optional_storage&&) = default;
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
/* Completely non-trivial. */
template<typename T>
struct optional_storage<T, false, false, false, false> : public optstore_helper<T> {
using optstore_helper<T>::optstore_helper;
constexpr optional_storage() noexcept = default;
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
{ this->assign(rhs); return *this; }
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
{ this->assign(std::move(rhs)); return *this; }
};
#undef _this
} // namespace detail_
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
template<typename T>
class optional {
using storage_t = detail_::optional_storage<T>;
storage_t mStore{};
public:
using value_type = T;
constexpr optional() = default;
constexpr optional(const optional&) = default;
constexpr optional(optional&&) = default;
constexpr optional(nullopt_t) noexcept { }
template<typename ...Args>
constexpr explicit optional(in_place_t, Args&& ...args)
NOEXCEPT_AS(storage_t{al::in_place, std::forward<Args>(args)...})
: mStore{al::in_place, std::forward<Args>(args)...}
{ }
template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
&& !std::is_same<std::decay_t<U>, al::in_place_t>::value
&& !std::is_same<std::decay_t<U>, optional<T>>::value
&& std::is_convertible<U&&, T>::value)>
constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
: mStore{al::in_place, std::forward<U>(rhs)}
{ }
template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
&& !std::is_same<std::decay_t<U>, al::in_place_t>::value
&& !std::is_same<std::decay_t<U>, optional<T>>::value
&& !std::is_convertible<U&&, T>::value)>
constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
: mStore{al::in_place, std::forward<U>(rhs)}
{ }
~optional() = default;
constexpr optional& operator=(const optional&) = default;
constexpr optional& operator=(optional&&) = default;
constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; }
template<typename U=T>
constexpr std::enable_if_t<std::is_constructible<T, U>::value
&& std::is_assignable<T&, U>::value
&& !std::is_same<std::decay_t<U>, optional<T>>::value
&& (!std::is_same<std::decay_t<U>, T>::value || !std::is_scalar<U>::value),
optional&> operator=(U&& rhs)
{
if(mStore.mHasValue)
mStore.mValue = std::forward<U>(rhs);
else
mStore.construct(std::forward<U>(rhs));
return *this;
}
constexpr const T* operator->() const { return std::addressof(mStore.mValue); }
constexpr T* operator->() { return std::addressof(mStore.mValue); }
constexpr const T& operator*() const& { return mStore.mValue; }
constexpr T& operator*() & { return mStore.mValue; }
constexpr const T&& operator*() const&& { return std::move(mStore.mValue); }
constexpr T&& operator*() && { return std::move(mStore.mValue); }
constexpr explicit operator bool() const noexcept { return mStore.mHasValue; }
constexpr bool has_value() const noexcept { return mStore.mHasValue; }
constexpr T& value() & { return mStore.mValue; }
constexpr const T& value() const& { return mStore.mValue; }
constexpr T&& value() && { return std::move(mStore.mValue); }
constexpr const T&& value() const&& { return std::move(mStore.mValue); }
template<typename U>
constexpr T value_or(U&& defval) const&
{ return bool(*this) ? **this : static_cast<T>(std::forward<U>(defval)); }
template<typename U>
constexpr T value_or(U&& defval) &&
{ return bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(defval)); }
template<typename ...Args>
constexpr T& emplace(Args&& ...args)
{
mStore.reset();
mStore.construct(std::forward<Args>(args)...);
return mStore.mValue;
}
template<typename U, typename ...Args>
constexpr std::enable_if_t<std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value,
T&> emplace(std::initializer_list<U> il, Args&& ...args)
{
mStore.reset();
mStore.construct(il, std::forward<Args>(args)...);
return mStore.mValue;
}
constexpr void reset() noexcept { mStore.reset(); }
};
template<typename T>
constexpr optional<std::decay_t<T>> make_optional(T&& arg)
{ return optional<std::decay_t<T>>{in_place, std::forward<T>(arg)}; }
template<typename T, typename... Args>
constexpr optional<T> make_optional(Args&& ...args)
{ return optional<T>{in_place, std::forward<Args>(args)...}; }
template<typename T, typename U, typename... Args>
constexpr optional<T> make_optional(std::initializer_list<U> il, Args&& ...args)
{ return optional<T>{in_place, il, std::forward<Args>(args)...}; }
#undef REQUIRES
#undef NOEXCEPT_AS
} // namespace al
#endif /* AL_OPTIONAL_H */

View file

@ -20,8 +20,7 @@
#include "config.h"
#include "opthelpers.h"
#include "threads.h"
#include "alsem.h"
#include <system_error>
@ -32,44 +31,14 @@
#include <limits>
void althrd_setname(const char *name)
{
#if defined(_MSC_VER) && !defined(_M_ARM)
#define MS_VC_EXCEPTION 0x406D1388
#pragma pack(push,8)
struct {
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} info;
#pragma pack(pop)
info.dwType = 0x1000;
info.szName = name;
info.dwThreadID = ~DWORD{0};
info.dwFlags = 0;
__try {
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
}
__except(EXCEPTION_CONTINUE_EXECUTION) {
}
#undef MS_VC_EXCEPTION
#else
(void)name;
#endif
}
namespace al {
semaphore::semaphore(unsigned int initial)
{
if(initial > static_cast<unsigned int>(std::numeric_limits<int>::max()))
throw std::system_error(std::make_error_code(std::errc::value_too_large));
mSem = CreateSemaphore(nullptr, initial, std::numeric_limits<int>::max(), nullptr);
mSem = CreateSemaphoreW(nullptr, static_cast<LONG>(initial), std::numeric_limits<int>::max(),
nullptr);
if(mSem == nullptr)
throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again));
}
@ -93,49 +62,8 @@ bool semaphore::try_wait() noexcept
#else
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#endif
#include <tuple>
namespace {
using setname_t1 = int(*)(const char*);
using setname_t2 = int(*)(pthread_t, const char*);
using setname_t3 = void(*)(pthread_t, const char*);
using setname_t4 = int(*)(pthread_t, const char*, void*);
void setname_caller(setname_t1 func, const char *name)
{ func(name); }
void setname_caller(setname_t2 func, const char *name)
{ func(pthread_self(), name); }
void setname_caller(setname_t3 func, const char *name)
{ func(pthread_self(), name); }
void setname_caller(setname_t4 func, const char *name)
{ func(pthread_self(), "%s", static_cast<void*>(const_cast<char*>(name))); }
} // namespace
void althrd_setname(const char *name)
{
#if defined(HAVE_PTHREAD_SET_NAME_NP)
setname_caller(pthread_set_name_np, name);
#elif defined(HAVE_PTHREAD_SETNAME_NP)
setname_caller(pthread_setname_np, name);
#endif
/* Avoid unused function/parameter warnings. */
std::ignore = name;
std::ignore = static_cast<void(*)(setname_t1,const char*)>(&setname_caller);
std::ignore = static_cast<void(*)(setname_t2,const char*)>(&setname_caller);
std::ignore = static_cast<void(*)(setname_t3,const char*)>(&setname_caller);
std::ignore = static_cast<void(*)(setname_t4,const char*)>(&setname_caller);
}
#ifdef __APPLE__
/* Do not try using libdispatch on systems where it is absent. */
#if defined(AL_APPLE_HAVE_DISPATCH)
namespace al {

View file

@ -0,0 +1,43 @@
#ifndef COMMON_ALSEM_H
#define COMMON_ALSEM_H
#if defined(__APPLE__)
#include <AvailabilityMacros.h>
#include <TargetConditionals.h>
#if (((MAC_OS_X_VERSION_MIN_REQUIRED > 1050) && !defined(__ppc__)) || TARGET_OS_IOS || TARGET_OS_TV)
#include <dispatch/dispatch.h>
#define AL_APPLE_HAVE_DISPATCH 1
#else
#include <semaphore.h> /* Fallback option for Apple without a working libdispatch */
#endif
#elif !defined(_WIN32)
#include <semaphore.h>
#endif
namespace al {
class semaphore {
#ifdef _WIN32
using native_type = void*;
#elif defined(AL_APPLE_HAVE_DISPATCH)
using native_type = dispatch_semaphore_t;
#else
using native_type = sem_t;
#endif
native_type mSem{};
public:
semaphore(unsigned int initial=0);
semaphore(const semaphore&) = delete;
~semaphore();
semaphore& operator=(const semaphore&) = delete;
void post();
void wait() noexcept;
bool try_wait() noexcept;
};
} // namespace al
#endif /* COMMON_ALSEM_H */

View file

@ -1,180 +1,250 @@
#ifndef AL_SPAN_H
#define AL_SPAN_H
#include <array>
#include <cassert>
#include <cstddef>
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <tuple>
#include <type_traits>
#include <utility>
#include "alassert.h"
#include "almalloc.h"
#include "altraits.h"
namespace al {
/* This is here primarily to help ensure proper behavior for span's iterators,
* being an actual object with member functions instead of a raw pointer (which
* has requirements like + and - working with ptrdiff_t). This also helps
* silence clang-tidy's pointer arithmetic warnings for span and FlexArray
* iterators. It otherwise behaves like a plain pointer and should optimize
* accordingly.
*
* Shouldn't be needed once we use std::span in C++20.
*/
template<typename T>
constexpr auto size(const T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size())
{ return cont.size(); }
class ptr_wrapper {
static_assert(std::is_pointer_v<T>);
T mPointer{};
template<typename T, size_t N>
constexpr size_t size(const T (&)[N]) noexcept
{ return N; }
public:
using value_type = std::remove_pointer_t<T>;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
using iterator_category = std::random_access_iterator_tag;
explicit constexpr ptr_wrapper(T ptr) : mPointer{ptr} { }
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
constexpr auto operator++() noexcept -> ptr_wrapper& { ++mPointer; return *this; }
constexpr auto operator--() noexcept -> ptr_wrapper& { --mPointer; return *this; }
constexpr auto operator++(int) noexcept -> ptr_wrapper
{
auto temp = *this;
++*this;
return temp;
}
constexpr auto operator--(int) noexcept -> ptr_wrapper
{
auto temp = *this;
--*this;
return temp;
}
constexpr
auto operator+=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer += n; return *this; }
constexpr
auto operator-=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer -= n; return *this; }
[[nodiscard]] constexpr auto operator*() const noexcept -> value_type& { return *mPointer; }
[[nodiscard]] constexpr auto operator->() const noexcept -> value_type* { return mPointer; }
[[nodiscard]] constexpr
auto operator[](std::size_t idx) const noexcept -> value_type& {return mPointer[idx];}
[[nodiscard]] friend constexpr
auto operator+(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper
{ return ptr_wrapper{lhs.mPointer + n}; }
[[nodiscard]] friend constexpr
auto operator+(std::ptrdiff_t n, const ptr_wrapper &rhs) noexcept -> ptr_wrapper
{ return ptr_wrapper{n + rhs.mPointer}; }
[[nodiscard]] friend constexpr
auto operator-(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper
{ return ptr_wrapper{lhs.mPointer - n}; }
[[nodiscard]] friend constexpr
auto operator-(const ptr_wrapper &lhs, const ptr_wrapper &rhs)noexcept->std::ptrdiff_t
{ return lhs.mPointer - rhs.mPointer; }
[[nodiscard]] friend constexpr
auto operator==(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
{ return lhs.mPointer == rhs.mPointer; }
[[nodiscard]] friend constexpr
auto operator!=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
{ return lhs.mPointer != rhs.mPointer; }
[[nodiscard]] friend constexpr
auto operator<=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
{ return lhs.mPointer <= rhs.mPointer; }
[[nodiscard]] friend constexpr
auto operator>=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
{ return lhs.mPointer >= rhs.mPointer; }
[[nodiscard]] friend constexpr
auto operator<(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
{ return lhs.mPointer < rhs.mPointer; }
[[nodiscard]] friend constexpr
auto operator>(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
{ return lhs.mPointer > rhs.mPointer; }
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
};
template<typename T>
constexpr auto data(T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data())
{ return cont.data(); }
inline constexpr std::size_t dynamic_extent{static_cast<std::size_t>(-1)};
template<typename T>
constexpr auto data(const T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data())
{ return cont.data(); }
template<typename T, size_t N>
constexpr T* data(T (&arr)[N]) noexcept
{ return arr; }
template<typename T>
constexpr const T* data(std::initializer_list<T> list) noexcept
{ return list.begin(); }
constexpr size_t dynamic_extent{static_cast<size_t>(-1)};
template<typename T, size_t E=dynamic_extent>
template<typename T, std::size_t E=dynamic_extent>
class span;
namespace detail_ {
template<typename... Ts>
using void_t = void;
template<typename T>
struct is_span_ : std::false_type { };
template<typename T, size_t E>
template<typename T, std::size_t E>
struct is_span_<span<T,E>> : std::true_type { };
template<typename T>
constexpr bool is_span_v = is_span_<std::remove_cv_t<T>>::value;
inline constexpr bool is_span_v = is_span_<std::remove_cv_t<T>>::value;
template<typename T>
struct is_std_array_ : std::false_type { };
template<typename T, size_t N>
template<typename T, std::size_t N>
struct is_std_array_<std::array<T,N>> : std::true_type { };
template<typename T>
constexpr bool is_std_array_v = is_std_array_<std::remove_cv_t<T>>::value;
inline constexpr bool is_std_array_v = is_std_array_<std::remove_cv_t<T>>::value;
template<typename T, typename = void>
constexpr bool has_size_and_data = false;
inline constexpr bool has_size_and_data = false;
template<typename T>
constexpr bool has_size_and_data<T,
void_t<decltype(al::size(std::declval<T>())), decltype(al::data(std::declval<T>()))>>
inline constexpr bool has_size_and_data<T,
std::void_t<decltype(std::size(std::declval<T>())),decltype(std::data(std::declval<T>()))>>
= true;
template<typename C>
inline constexpr bool is_valid_container_type = !is_span_v<C> && !is_std_array_v<C>
&& !std::is_array<C>::value && has_size_and_data<C>;
template<typename T, typename U>
constexpr bool is_array_compatible = std::is_convertible<T(*)[],U(*)[]>::value;
inline constexpr bool is_array_compatible = std::is_convertible<T(*)[],U(*)[]>::value; /* NOLINT(*-avoid-c-arrays) */
template<typename C, typename T>
constexpr bool is_valid_container = !is_span_v<C> && !is_std_array_v<C>
&& !std::is_array<C>::value && has_size_and_data<C>
&& is_array_compatible<std::remove_pointer_t<decltype(al::data(std::declval<C&>()))>,T>;
inline constexpr bool is_valid_container = is_valid_container_type<C>
&& is_array_compatible<std::remove_pointer_t<decltype(std::data(std::declval<C&>()))>,T>;
} // namespace detail_
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
template<typename T, size_t E>
template<typename T, std::size_t E>
class span {
public:
using element_type = T;
using value_type = std::remove_cv_t<T>;
using index_type = size_t;
using difference_type = ptrdiff_t;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using iterator = pointer;
using const_iterator = const_pointer;
using iterator = ptr_wrapper<pointer>;
using const_iterator = ptr_wrapper<const_pointer>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
static constexpr size_t extent{E};
static constexpr std::size_t extent{E};
template<bool is0=(extent == 0), REQUIRES(is0)>
constexpr span() noexcept { }
template<typename U>
constexpr explicit span(U iter, index_type) : mData{to_address(iter)} { }
template<typename U, typename V, REQUIRES(!std::is_convertible<V,size_t>::value)>
constexpr explicit span(U first, V) : mData{to_address(first)} { }
constexpr explicit span(U iter, size_type size_) : mData{::al::to_address(iter)}
{ alassert(size_ == extent); }
template<typename U, typename V, REQUIRES(!std::is_convertible<V,std::size_t>::value)>
constexpr explicit span(U first, V last) : mData{::al::to_address(first)}
{ alassert(static_cast<std::size_t>(last-first) == extent); }
constexpr span(type_identity_t<element_type> (&arr)[E]) noexcept
: span{al::data(arr), al::size(arr)}
{ }
constexpr span(std::array<value_type,E> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
template<typename U=T, REQUIRES(std::is_const<U>::value)>
constexpr span(const std::array<value_type,E> &arr) noexcept
: span{al::data(arr), al::size(arr)}
{ }
template<std::size_t N>
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */
: mData{std::data(arr)}
{ static_assert(N == extent); }
template<std::size_t N>
constexpr span(std::array<value_type,N> &arr) noexcept : mData{std::data(arr)}
{ static_assert(N == extent); }
template<typename U=T, std::size_t N, REQUIRES(std::is_const<U>::value)>
constexpr span(const std::array<value_type,N> &arr) noexcept : mData{std::data(arr)}
{ static_assert(N == extent); }
template<typename U, REQUIRES(detail_::is_valid_container<U, element_type>)>
constexpr explicit span(U&& cont) : span{al::data(cont), al::size(cont)} { }
constexpr explicit span(U&& cont) : span{std::data(cont), std::size(cont)} { }
template<typename U, index_type N, REQUIRES(!std::is_same<element_type,U>::value
template<typename U, std::size_t N, REQUIRES(!std::is_same<element_type,U>::value
&& detail_::is_array_compatible<U,element_type> && N == dynamic_extent)>
constexpr explicit span(const span<U,N> &span_) noexcept
: span{al::data(span_), al::size(span_)}
{ }
template<typename U, index_type N, REQUIRES(!std::is_same<element_type,U>::value
constexpr explicit span(const span<U,N> &span_) noexcept : mData{std::data(span_)}
{ alassert(std::size(span_) == extent); }
template<typename U, std::size_t N, REQUIRES(!std::is_same<element_type,U>::value
&& detail_::is_array_compatible<U,element_type> && N == extent)>
constexpr span(const span<U,N> &span_) noexcept : span{al::data(span_), al::size(span_)} { }
constexpr span(const span<U,N> &span_) noexcept : mData{std::data(span_)} { }
constexpr span(const span&) noexcept = default;
constexpr span& operator=(const span &rhs) noexcept = default;
constexpr reference front() const { return *mData; }
constexpr reference back() const { return *(mData+E-1); }
constexpr reference operator[](index_type idx) const { return mData[idx]; }
constexpr pointer data() const noexcept { return mData; }
[[nodiscard]] constexpr auto front() const -> reference { return mData[0]; }
[[nodiscard]] constexpr auto back() const -> reference { return mData[E-1]; }
[[nodiscard]] constexpr auto operator[](size_type idx) const -> reference { return mData[idx]; }
[[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; }
constexpr index_type size() const noexcept { return E; }
constexpr index_type size_bytes() const noexcept { return E * sizeof(value_type); }
constexpr bool empty() const noexcept { return E == 0; }
[[nodiscard]] constexpr auto size() const noexcept -> size_type { return E; }
[[nodiscard]] constexpr auto size_bytes() const noexcept -> size_type { return E * sizeof(value_type); }
[[nodiscard]] constexpr auto empty() const noexcept -> bool { return E == 0; }
constexpr iterator begin() const noexcept { return mData; }
constexpr iterator end() const noexcept { return mData+E; }
constexpr const_iterator cbegin() const noexcept { return mData; }
constexpr const_iterator cend() const noexcept { return mData+E; }
[[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; }
[[nodiscard]] constexpr auto end() const noexcept -> iterator { return iterator{mData+E}; }
[[nodiscard]] constexpr
auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; }
[[nodiscard]] constexpr
auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; }
constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; }
constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; }
constexpr const_reverse_iterator crbegin() const noexcept
{ return const_reverse_iterator{cend()}; }
constexpr const_reverse_iterator crend() const noexcept
{ return const_reverse_iterator{cbegin()}; }
[[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); }
[[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); }
[[nodiscard]] constexpr
auto crbegin() const noexcept -> const_reverse_iterator { return cend(); }
[[nodiscard]] constexpr
auto crend() const noexcept -> const_reverse_iterator { return cbegin(); }
template<size_t C>
constexpr span<element_type,C> first() const
template<std::size_t C>
[[nodiscard]] constexpr auto first() const noexcept -> span<element_type,C>
{
static_assert(E >= C, "New size exceeds original capacity");
return span<element_type,C>{mData, C};
}
template<size_t C>
constexpr span<element_type,C> last() const
template<std::size_t C>
[[nodiscard]] constexpr auto last() const noexcept -> span<element_type,C>
{
static_assert(E >= C, "New size exceeds original capacity");
return span<element_type,C>{mData+(E-C), C};
}
template<size_t O, size_t C>
constexpr auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
template<std::size_t O, std::size_t C>
[[nodiscard]] constexpr
auto subspan() const noexcept -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
{
static_assert(E >= O, "Offset exceeds extent");
static_assert(E-O >= C, "New size exceeds original capacity");
return span<element_type,C>{mData+O, C};
}
template<size_t O, size_t C=dynamic_extent>
constexpr auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,E-O>>
template<std::size_t O, std::size_t C=dynamic_extent>
[[nodiscard]] constexpr
auto subspan() const noexcept -> std::enable_if_t<C==dynamic_extent,span<element_type,E-O>>
{
static_assert(E >= O, "Offset exceeds extent");
return span<element_type,E-O>{mData+O, E-O};
@ -184,10 +254,13 @@ public:
* defining the specialization. As a result, these methods need to be
* defined later.
*/
constexpr span<element_type,dynamic_extent> first(size_t count) const;
constexpr span<element_type,dynamic_extent> last(size_t count) const;
constexpr span<element_type,dynamic_extent> subspan(size_t offset,
size_t count=dynamic_extent) const;
[[nodiscard]] constexpr
auto first(std::size_t count) const noexcept -> span<element_type,dynamic_extent>;
[[nodiscard]] constexpr
auto last(std::size_t count) const noexcept -> span<element_type,dynamic_extent>;
[[nodiscard]] constexpr
auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept
-> span<element_type,dynamic_extent>;
private:
pointer mData{nullptr};
@ -198,7 +271,7 @@ class span<T,dynamic_extent> {
public:
using element_type = T;
using value_type = std::remove_cv_t<T>;
using index_type = size_t;
using size_type = std::size_t;
using difference_type = ptrdiff_t;
using pointer = T*;
@ -206,146 +279,175 @@ public:
using reference = T&;
using const_reference = const T&;
using iterator = pointer;
using const_iterator = const_pointer;
using iterator = ptr_wrapper<pointer>;
using const_iterator = ptr_wrapper<const_pointer>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
static constexpr size_t extent{dynamic_extent};
static constexpr std::size_t extent{dynamic_extent};
constexpr span() noexcept = default;
template<typename U>
constexpr span(U iter, index_type count)
: mData{to_address(iter)}, mDataEnd{to_address(iter)+count}
constexpr span(U iter, size_type count) : mData{::al::to_address(iter)}, mDataLength{count}
{ }
template<typename U, typename V, REQUIRES(!std::is_convertible<V,size_t>::value)>
constexpr span(U first, V last) : span{to_address(first), static_cast<size_t>(last-first)}
template<typename U, typename V, REQUIRES(!std::is_convertible<V,std::size_t>::value)>
constexpr span(U first, V last)
: span{::al::to_address(first), static_cast<std::size_t>(last-first)}
{ }
template<size_t N>
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept
: span{al::data(arr), al::size(arr)}
template<std::size_t N>
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */
: mData{std::data(arr)}, mDataLength{std::size(arr)}
{ }
template<size_t N>
constexpr span(std::array<value_type,N> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
template<size_t N, typename U=T, REQUIRES(std::is_const<U>::value)>
template<std::size_t N>
constexpr span(std::array<value_type,N> &arr) noexcept
: mData{std::data(arr)}, mDataLength{std::size(arr)}
{ }
template<std::size_t N, typename U=T, REQUIRES(std::is_const<U>::value)>
constexpr span(const std::array<value_type,N> &arr) noexcept
: span{al::data(arr), al::size(arr)}
: mData{std::data(arr)}, mDataLength{std::size(arr)}
{ }
template<typename U, REQUIRES(detail_::is_valid_container<U, element_type>)>
constexpr span(U&& cont) : span{al::data(cont), al::size(cont)} { }
constexpr span(U&& cont) : span{std::data(cont), std::size(cont)} { }
template<typename U, size_t N, REQUIRES((!std::is_same<element_type,U>::value || extent != N)
&& detail_::is_array_compatible<U,element_type>)>
constexpr span(const span<U,N> &span_) noexcept : span{al::data(span_), al::size(span_)} { }
template<typename U, std::size_t N, REQUIRES(detail_::is_array_compatible<U,element_type>
&& (!std::is_same<element_type,U>::value || extent != N))>
constexpr span(const span<U,N> &span_) noexcept : span{std::data(span_), std::size(span_)} { }
constexpr span(const span&) noexcept = default;
constexpr span& operator=(const span &rhs) noexcept = default;
constexpr reference front() const { return *mData; }
constexpr reference back() const { return *(mDataEnd-1); }
constexpr reference operator[](index_type idx) const { return mData[idx]; }
constexpr pointer data() const noexcept { return mData; }
[[nodiscard]] constexpr auto front() const -> reference { return mData[0]; }
[[nodiscard]] constexpr auto back() const -> reference { return mData[mDataLength-1]; }
[[nodiscard]] constexpr auto operator[](size_type idx) const -> reference {return mData[idx];}
[[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; }
constexpr index_type size() const noexcept { return static_cast<index_type>(mDataEnd-mData); }
constexpr index_type size_bytes() const noexcept
{ return static_cast<index_type>(mDataEnd-mData) * sizeof(value_type); }
constexpr bool empty() const noexcept { return mData == mDataEnd; }
[[nodiscard]] constexpr auto size() const noexcept -> size_type { return mDataLength; }
[[nodiscard]] constexpr
auto size_bytes() const noexcept -> size_type { return mDataLength * sizeof(value_type); }
[[nodiscard]] constexpr auto empty() const noexcept -> bool { return mDataLength == 0; }
constexpr iterator begin() const noexcept { return mData; }
constexpr iterator end() const noexcept { return mDataEnd; }
constexpr const_iterator cbegin() const noexcept { return mData; }
constexpr const_iterator cend() const noexcept { return mDataEnd; }
[[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; }
[[nodiscard]] constexpr
auto end() const noexcept -> iterator { return iterator{mData+mDataLength}; }
[[nodiscard]] constexpr
auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; }
[[nodiscard]] constexpr
auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; }
constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; }
constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; }
constexpr const_reverse_iterator crbegin() const noexcept
{ return const_reverse_iterator{cend()}; }
constexpr const_reverse_iterator crend() const noexcept
{ return const_reverse_iterator{cbegin()}; }
[[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); }
[[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); }
[[nodiscard]] constexpr
auto crbegin() const noexcept -> const_reverse_iterator { return cend(); }
[[nodiscard]] constexpr
auto crend() const noexcept -> const_reverse_iterator { return cbegin(); }
template<size_t C>
constexpr span<element_type,C> first() const
{ return span<element_type,C>{mData, C}; }
constexpr span first(size_t count) const
{ return (count >= size()) ? *this : span{mData, mData+count}; }
template<size_t C>
constexpr span<element_type,C> last() const
{ return span<element_type,C>{mDataEnd-C, C}; }
constexpr span last(size_t count) const
{ return (count >= size()) ? *this : span{mDataEnd-count, mDataEnd}; }
template<size_t O, size_t C>
constexpr auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
{ return span<element_type,C>{mData+O, C}; }
template<size_t O, size_t C=dynamic_extent>
constexpr auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,C>>
{ return span<element_type,C>{mData+O, mDataEnd}; }
constexpr span subspan(size_t offset, size_t count=dynamic_extent) const
template<std::size_t C>
[[nodiscard]] constexpr auto first() const noexcept -> span<element_type,C>
{
return (offset > size()) ? span{} :
(count >= size()-offset) ? span{mData+offset, mDataEnd} :
span{mData+offset, mData+offset+count};
assert(C <= mDataLength);
return span<element_type,C>{mData, C};
}
[[nodiscard]] constexpr auto first(std::size_t count) const noexcept -> span
{
assert(count <= mDataLength);
return span{mData, count};
}
template<std::size_t C>
[[nodiscard]] constexpr auto last() const noexcept -> span<element_type,C>
{
assert(C <= mDataLength);
return span<element_type,C>{mData+mDataLength-C, C};
}
[[nodiscard]] constexpr auto last(std::size_t count) const noexcept -> span
{
assert(count <= mDataLength);
return span{mData+mDataLength-count, count};
}
template<std::size_t O, std::size_t C>
[[nodiscard]] constexpr
auto subspan() const noexcept -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
{
assert(O <= mDataLength);
assert(C <= mDataLength-O);
return span<element_type,C>{mData+O, C};
}
template<std::size_t O, std::size_t C=dynamic_extent>
[[nodiscard]] constexpr
auto subspan() const noexcept -> std::enable_if_t<C==dynamic_extent,span<element_type,C>>
{
assert(O <= mDataLength);
return span<element_type,C>{mData+O, mDataLength-O};
}
[[nodiscard]] constexpr
auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span
{
assert(offset <= mDataLength);
if(count != dynamic_extent)
{
assert(count <= mDataLength-offset);
return span{mData+offset, count};
}
return span{mData+offset, mDataLength-offset};
}
private:
pointer mData{nullptr};
pointer mDataEnd{nullptr};
size_type mDataLength{0};
};
template<typename T, size_t E>
constexpr inline auto span<T,E>::first(size_t count) const -> span<element_type,dynamic_extent>
template<typename T, std::size_t E>
[[nodiscard]] constexpr
auto span<T,E>::first(std::size_t count) const noexcept -> span<element_type,dynamic_extent>
{
return (count >= size()) ? span<element_type>{mData, extent} :
span<element_type>{mData, count};
assert(count <= size());
return span<element_type>{mData, count};
}
template<typename T, size_t E>
constexpr inline auto span<T,E>::last(size_t count) const -> span<element_type,dynamic_extent>
template<typename T, std::size_t E>
[[nodiscard]] constexpr
auto span<T,E>::last(std::size_t count) const noexcept -> span<element_type,dynamic_extent>
{
return (count >= size()) ? span<element_type>{mData, extent} :
span<element_type>{mData+extent-count, count};
assert(count <= size());
return span<element_type>{mData+size()-count, count};
}
template<typename T, size_t E>
constexpr inline auto span<T,E>::subspan(size_t offset, size_t count) const
template<typename T, std::size_t E>
[[nodiscard]] constexpr
auto span<T,E>::subspan(std::size_t offset, std::size_t count) const noexcept
-> span<element_type,dynamic_extent>
{
return (offset > size()) ? span<element_type>{} :
(count >= size()-offset) ? span<element_type>{mData+offset, mData+extent} :
span<element_type>{mData+offset, mData+offset+count};
assert(offset <= size());
if(count != dynamic_extent)
{
assert(count <= size()-offset);
return span<element_type>{mData+offset, count};
}
return span<element_type>{mData+offset, size()-offset};
}
/* Helpers to deal with the lack of user-defined deduction guides (C++17). */
template<typename T, typename U>
constexpr auto as_span(T ptr, U count_or_end)
{
using value_type = typename std::pointer_traits<T>::element_type;
return span<value_type>{ptr, count_or_end};
}
template<typename T, size_t N>
constexpr auto as_span(T (&arr)[N]) noexcept { return span<T,N>{al::data(arr), al::size(arr)}; }
template<typename T, size_t N>
constexpr auto as_span(std::array<T,N> &arr) noexcept
{ return span<T,N>{al::data(arr), al::size(arr)}; }
template<typename T, size_t N>
constexpr auto as_span(const std::array<T,N> &arr) noexcept
{ return span<std::add_const_t<T>,N>{al::data(arr), al::size(arr)}; }
template<typename U, REQUIRES(!detail_::is_span_v<U> && !detail_::is_std_array_v<U>
&& !std::is_array<U>::value && detail_::has_size_and_data<U>)>
constexpr auto as_span(U&& cont)
{
using value_type = std::remove_pointer_t<decltype(al::data(std::declval<U&>()))>;
return span<value_type>{al::data(cont), al::size(cont)};
}
template<typename T, size_t N>
constexpr auto as_span(span<T,N> span_) noexcept { return span_; }
template<typename T, typename EndOrSize>
span(T, EndOrSize) -> span<std::remove_reference_t<decltype(*std::declval<T&>())>>;
template<typename T, std::size_t N>
span(T (&)[N]) -> span<T, N>; /* NOLINT(*-avoid-c-arrays) */
template<typename T, std::size_t N>
span(std::array<T, N>&) -> span<T, N>;
template<typename T, std::size_t N>
span(const std::array<T, N>&) -> span<const T, N>;
template<typename C, REQUIRES(detail_::is_valid_container_type<C>)>
span(C&&) -> span<std::remove_pointer_t<decltype(std::data(std::declval<C&>()))>>;
#undef REQUIRES

View file

@ -3,43 +3,62 @@
#include "alstring.h"
#include <algorithm>
#include <cctype>
#include <cwctype>
#include <cstring>
#include <string>
namespace {
int to_upper(const char ch)
{
using char8_traits = std::char_traits<char>;
return std::toupper(char8_traits::to_int_type(ch));
}
} // namespace
namespace al {
int strcasecmp(const char *str0, const char *str1) noexcept
int case_compare(const std::string_view str0, const std::string_view str1) noexcept
{
do {
const int diff{to_upper(*str0) - to_upper(*str1)};
if(diff < 0) return -1;
if(diff > 0) return 1;
} while(*(str0++) && *(str1++));
using Traits = std::string_view::traits_type;
auto ch0 = str0.cbegin();
auto ch1 = str1.cbegin();
auto ch1end = ch1 + std::min(str0.size(), str1.size());
while(ch1 != ch1end)
{
const int u0{std::toupper(Traits::to_int_type(*ch0))};
const int u1{std::toupper(Traits::to_int_type(*ch1))};
if(const int diff{u0-u1}) return diff;
++ch0; ++ch1;
}
if(str0.size() < str1.size()) return -1;
if(str0.size() > str1.size()) return 1;
return 0;
}
int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept
{
using Traits = std::wstring_view::traits_type;
auto ch0 = str0.cbegin();
auto ch1 = str1.cbegin();
auto ch1end = ch1 + std::min(str0.size(), str1.size());
while(ch1 != ch1end)
{
const auto u0 = std::towupper(Traits::to_int_type(*ch0));
const auto u1 = std::towupper(Traits::to_int_type(*ch1));
if(const auto diff = static_cast<int>(u0-u1)) return diff;
++ch0; ++ch1;
}
if(str0.size() < str1.size()) return -1;
if(str0.size() > str1.size()) return 1;
return 0;
}
int strcasecmp(const char *str0, const char *str1) noexcept
{ return case_compare(str0, str1); }
int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept
{
if(len > 0)
{
do {
const int diff{to_upper(*str0) - to_upper(*str1)};
if(diff < 0) return -1;
if(diff > 0) return 1;
} while(--len && *(str0++) && *(str1++));
}
return 0;
return case_compare(std::string_view{str0, std::min(std::strlen(str0), len)},
std::string_view{str1, std::min(std::strlen(str1), len)});
}
} // namespace al

View file

@ -1,16 +1,41 @@
#ifndef AL_STRING_H
#define AL_STRING_H
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <limits>
#include <string>
#include <string_view>
namespace al {
/* These would be better served by using a string_view-like span/view with
* case-insensitive char traits.
*/
template<typename T, typename Traits>
[[nodiscard]] constexpr
auto sizei(const std::basic_string_view<T,Traits> str) noexcept -> int
{ return static_cast<int>(std::min<std::size_t>(str.size(), std::numeric_limits<int>::max())); }
[[nodiscard]]
constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept
{ return str0.find(str1) != std::string_view::npos; }
[[nodiscard]]
constexpr bool starts_with(const std::string_view str0, const std::string_view str1) noexcept
{ return str0.substr(0, std::min(str0.size(), str1.size())) == str1; }
[[nodiscard]]
constexpr bool ends_with(const std::string_view str0, const std::string_view str1) noexcept
{ return str0.substr(str0.size() - std::min(str0.size(), str1.size())) == str1; }
[[nodiscard]]
int case_compare(const std::string_view str0, const std::string_view str1) noexcept;
[[nodiscard]]
int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept;
[[nodiscard]]
int strcasecmp(const char *str0, const char *str1) noexcept;
[[nodiscard]]
int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept;
} // namespace al

View file

@ -0,0 +1,77 @@
#include "config.h"
#include "althrd_setname.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void althrd_setname(const char *name [[maybe_unused]])
{
#if defined(_MSC_VER) && !defined(_M_ARM)
#define MS_VC_EXCEPTION 0x406D1388
#pragma pack(push,8)
struct InfoStruct {
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
};
#pragma pack(pop)
InfoStruct info{};
info.dwType = 0x1000;
info.szName = name;
info.dwThreadID = ~DWORD{0};
info.dwFlags = 0;
/* FIXME: How to do this on MinGW? */
__try {
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
}
__except(EXCEPTION_CONTINUE_EXECUTION) {
}
#undef MS_VC_EXCEPTION
#endif
}
#else
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
#include <pthread_np.h>
#endif
namespace {
using setname_t1 = int(*)(const char*);
using setname_t2 = int(*)(pthread_t, const char*);
using setname_t3 = void(*)(pthread_t, const char*);
using setname_t4 = int(*)(pthread_t, const char*, void*);
[[maybe_unused]] void setname_caller(setname_t1 func, const char *name)
{ func(name); }
[[maybe_unused]] void setname_caller(setname_t2 func, const char *name)
{ func(pthread_self(), name); }
[[maybe_unused]] void setname_caller(setname_t3 func, const char *name)
{ func(pthread_self(), name); }
[[maybe_unused]] void setname_caller(setname_t4 func, const char *name)
{ func(pthread_self(), "%s", const_cast<char*>(name)); /* NOLINT(*-const-cast) */ }
} // namespace
void althrd_setname(const char *name [[maybe_unused]])
{
#if defined(HAVE_PTHREAD_SET_NAME_NP)
setname_caller(pthread_set_name_np, name);
#elif defined(HAVE_PTHREAD_SETNAME_NP)
setname_caller(pthread_setname_np, name);
#endif
}
#endif

View file

@ -0,0 +1,6 @@
#ifndef COMMON_ALTHRD_SETNAME_H
#define COMMON_ALTHRD_SETNAME_H
void althrd_setname(const char *name);
#endif /* COMMON_ALTHRD_SETNAME_H */

View file

@ -0,0 +1,143 @@
#ifndef AL_THREADS_H
#define AL_THREADS_H
#include <cstdint>
#include <stdexcept>
#include <type_traits>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#elif defined(__APPLE__)
#include <pthread.h>
#else
#include <threads.h>
#endif
#include "albit.h"
namespace al {
template<typename T>
class tss {
static_assert(sizeof(T) <= sizeof(void*));
static_assert(std::is_trivially_destructible_v<T> && std::is_trivially_copy_constructible_v<T>);
[[nodiscard]]
static auto to_ptr(const T &value) noexcept -> void*
{
if constexpr(std::is_pointer_v<T>)
{
if constexpr(std::is_const_v<std::remove_pointer_t<T>>)
return const_cast<void*>(static_cast<const void*>(value)); /* NOLINT(*-const-cast) */
else
return static_cast<void*>(value);
}
else if constexpr(sizeof(T) == sizeof(void*))
return al::bit_cast<void*>(value);
else if constexpr(std::is_integral_v<T>)
return al::bit_cast<void*>(static_cast<std::uintptr_t>(value));
}
[[nodiscard]]
static auto from_ptr(void *ptr) noexcept -> T
{
if constexpr(std::is_pointer_v<T>)
return static_cast<T>(ptr);
else if constexpr(sizeof(T) == sizeof(void*))
return al::bit_cast<T>(ptr);
else if constexpr(std::is_integral_v<T>)
return static_cast<T>(al::bit_cast<std::uintptr_t>(ptr));
}
#ifdef _WIN32
DWORD mTss{TLS_OUT_OF_INDEXES};
public:
tss() : mTss{TlsAlloc()}
{
if(mTss == TLS_OUT_OF_INDEXES)
throw std::runtime_error{"al::tss::tss()"};
}
explicit tss(const T &init) : tss{}
{
if(TlsSetValue(mTss, to_ptr(init)) == FALSE)
throw std::runtime_error{"al::tss::tss(T)"};
}
~tss() { TlsFree(mTss); }
void set(const T &value) const
{
if(TlsSetValue(mTss, to_ptr(value)) == FALSE)
throw std::runtime_error{"al::tss::set(T)"};
}
[[nodiscard]]
auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); }
#elif defined(__APPLE__)
pthread_key_t mTss{};
public:
tss()
{
if(int res{pthread_key_create(&mTss, nullptr)}; res != 0)
throw std::runtime_error{"al::tss::tss()"};
}
explicit tss(const T &init) : tss{}
{
if(int res{pthread_setspecific(mTss, to_ptr(init))}; res != 0)
throw std::runtime_error{"al::tss::tss(T)"};
}
~tss() { pthread_key_delete(mTss); }
void set(const T &value) const
{
if(int res{pthread_setspecific(mTss, to_ptr(value))}; res != 0)
throw std::runtime_error{"al::tss::set(T)"};
}
[[nodiscard]]
auto get() const noexcept -> T { return from_ptr(pthread_getspecific(mTss)); }
#else
tss_t mTss{};
public:
tss()
{
if(int res{tss_create(&mTss, nullptr)}; res != thrd_success)
throw std::runtime_error{"al::tss::tss()"};
}
explicit tss(const T &init) : tss{}
{
if(int res{tss_set(mTss, to_ptr(init))}; res != thrd_success)
throw std::runtime_error{"al::tss::tss(T)"};
}
~tss() { tss_delete(mTss); }
void set(const T &value) const
{
if(int res{tss_set(mTss, to_ptr(value))}; res != thrd_success)
throw std::runtime_error{"al::tss::set(T)"};
}
[[nodiscard]]
auto get() const noexcept -> T { return from_ptr(tss_get(mTss)); }
#endif /* _WIN32 */
tss(const tss&) = delete;
tss(tss&&) = delete;
void operator=(const tss&) = delete;
void operator=(tss&&) = delete;
};
} // namespace al
#endif /* AL_THREADS_H */

View file

@ -2,17 +2,17 @@
#define AL_ATOMIC_H
#include <atomic>
#include <cstddef>
#include <memory>
#include "almalloc.h"
using RefCount = std::atomic<unsigned int>;
inline void InitRef(RefCount &ref, unsigned int value)
{ ref.store(value, std::memory_order_relaxed); }
inline unsigned int ReadRef(RefCount &ref)
{ return ref.load(std::memory_order_acquire); }
inline unsigned int IncrementRef(RefCount &ref)
template<typename T>
auto IncrementRef(std::atomic<T> &ref) noexcept
{ return ref.fetch_add(1u, std::memory_order_acq_rel)+1u; }
inline unsigned int DecrementRef(RefCount &ref)
template<typename T>
auto DecrementRef(std::atomic<T> &ref) noexcept
{ return ref.fetch_sub(1u, std::memory_order_acq_rel)-1u; }
@ -30,4 +30,75 @@ inline void AtomicReplaceHead(std::atomic<T> &head, T newhead)
std::memory_order_acq_rel, std::memory_order_acquire));
}
namespace al {
template<typename T, typename D=std::default_delete<T>>
class atomic_unique_ptr {
std::atomic<gsl::owner<T*>> mPointer{};
using unique_ptr_t = std::unique_ptr<T,D>;
public:
atomic_unique_ptr() = default;
atomic_unique_ptr(const atomic_unique_ptr&) = delete;
explicit atomic_unique_ptr(std::nullptr_t) noexcept { }
explicit atomic_unique_ptr(gsl::owner<T*> ptr) noexcept : mPointer{ptr} { }
explicit atomic_unique_ptr(unique_ptr_t&& rhs) noexcept : mPointer{rhs.release()} { }
~atomic_unique_ptr()
{
if(auto ptr = mPointer.exchange(nullptr, std::memory_order_relaxed))
D{}(ptr);
}
auto operator=(const atomic_unique_ptr&) -> atomic_unique_ptr& = delete;
auto operator=(std::nullptr_t) noexcept -> atomic_unique_ptr&
{
if(auto ptr = mPointer.exchange(nullptr))
D{}(ptr);
return *this;
}
auto operator=(unique_ptr_t&& rhs) noexcept -> atomic_unique_ptr&
{
if(auto ptr = mPointer.exchange(rhs.release()))
D{}(ptr);
return *this;
}
[[nodiscard]]
auto load(std::memory_order m=std::memory_order_seq_cst) const noexcept -> T*
{ return mPointer.load(m); }
void store(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept
{
if(auto oldptr = mPointer.exchange(nullptr, m))
D{}(oldptr);
}
void store(gsl::owner<T*> ptr, std::memory_order m=std::memory_order_seq_cst) noexcept
{
if(auto oldptr = mPointer.exchange(ptr, m))
D{}(oldptr);
}
void store(unique_ptr_t&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept
{
if(auto oldptr = mPointer.exchange(ptr.release(), m))
D{}(oldptr);
}
[[nodiscard]]
auto exchange(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
{ return unique_ptr_t{mPointer.exchange(nullptr, m)}; }
[[nodiscard]]
auto exchange(gsl::owner<T*> ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
{ return unique_ptr_t{mPointer.exchange(ptr, m)}; }
[[nodiscard]]
auto exchange(std::unique_ptr<T>&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
{ return unique_ptr_t{mPointer.exchange(ptr.release(), m)}; }
[[nodiscard]]
auto is_lock_free() const noexcept -> bool { return mPointer.is_lock_free(); }
static constexpr auto is_always_lock_free = std::atomic<gsl::owner<T*>>::is_always_lock_free;
};
} // namespace al
#endif /* AL_ATOMIC_H */

View file

@ -2,49 +2,86 @@
#define COMMON_COMPTR_H
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
#include <variant>
#include "opthelpers.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <objbase.h>
struct ComWrapper {
HRESULT mStatus{};
ComWrapper(void *reserved, DWORD coinit)
: mStatus{CoInitializeEx(reserved, coinit)}
{ }
ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED)
: mStatus{CoInitializeEx(nullptr, coinit)}
{ }
ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); }
ComWrapper(const ComWrapper&) = delete;
~ComWrapper() { if(SUCCEEDED(mStatus)) CoUninitialize(); }
ComWrapper& operator=(ComWrapper&& rhs)
{
if(SUCCEEDED(mStatus))
CoUninitialize();
mStatus = std::exchange(rhs.mStatus, E_FAIL);
return *this;
}
ComWrapper& operator=(const ComWrapper&) = delete;
[[nodiscard]]
HRESULT status() const noexcept { return mStatus; }
explicit operator bool() const noexcept { return SUCCEEDED(status()); }
void uninit()
{
if(SUCCEEDED(mStatus))
CoUninitialize();
mStatus = E_FAIL;
}
};
template<typename T>
class ComPtr {
T *mPtr{nullptr};
struct ComPtr {
using element_type = T;
static constexpr bool RefIsNoexcept{noexcept(std::declval<T&>().AddRef())
&& noexcept(std::declval<T&>().Release())};
public:
ComPtr() noexcept = default;
ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); }
ComPtr(const ComPtr &rhs) noexcept(RefIsNoexcept) : mPtr{rhs.mPtr}
{ if(mPtr) mPtr->AddRef(); }
ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; }
ComPtr(std::nullptr_t) noexcept { }
explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { }
~ComPtr() { if(mPtr) mPtr->Release(); }
ComPtr& operator=(const ComPtr &rhs)
/* NOLINTNEXTLINE(bugprone-unhandled-self-assignment) Yes it is. */
ComPtr& operator=(const ComPtr &rhs) noexcept(RefIsNoexcept)
{
if(!rhs.mPtr)
if constexpr(RefIsNoexcept)
{
if(mPtr)
mPtr->Release();
mPtr = nullptr;
if(rhs.mPtr) rhs.mPtr->AddRef();
if(mPtr) mPtr->Release();
mPtr = rhs.mPtr;
return *this;
}
else
{
rhs.mPtr->AddRef();
try {
if(mPtr)
mPtr->Release();
mPtr = rhs.mPtr;
}
catch(...) {
rhs.mPtr->Release();
throw;
}
ComPtr tmp{rhs};
if(mPtr) mPtr->Release();
mPtr = tmp.release();
return *this;
}
return *this;
}
ComPtr& operator=(ComPtr&& rhs)
ComPtr& operator=(ComPtr&& rhs) noexcept(RefIsNoexcept)
{
if(&rhs != this) LIKELY
if(&rhs != this)
{
if(mPtr) mPtr->Release();
mPtr = std::exchange(rhs.mPtr, nullptr);
@ -52,17 +89,25 @@ public:
return *this;
}
void reset(T *ptr=nullptr) noexcept(RefIsNoexcept)
{
if(mPtr) mPtr->Release();
mPtr = ptr;
}
explicit operator bool() const noexcept { return mPtr != nullptr; }
T& operator*() const noexcept { return *mPtr; }
T* operator->() const noexcept { return mPtr; }
T* get() const noexcept { return mPtr; }
T** getPtr() noexcept { return &mPtr; }
T* release() noexcept { return std::exchange(mPtr, nullptr); }
void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
private:
T *mPtr{nullptr};
};
#endif

View file

@ -3,12 +3,12 @@
#include "dynload.h"
#include "strutils.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "strutils.h"
void *LoadLib(const char *name)
{
std::wstring wname{utf8_to_wstr(name)};

View file

@ -0,0 +1,139 @@
#ifndef AL_FLEXARRAY_H
#define AL_FLEXARRAY_H
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <memory>
#include <new>
#include <type_traits>
#include "almalloc.h"
#include "alspan.h"
namespace al {
/* Storage for flexible array data. This is trivially destructible if type T is
* trivially destructible.
*/
template<typename T, size_t alignment, bool = std::is_trivially_destructible<T>::value>
struct alignas(alignment) FlexArrayStorage : al::span<T> {
/* NOLINTBEGIN(bugprone-sizeof-expression) clang-tidy warns about the
* sizeof(T) being suspicious when T is a pointer type, which it will be
* for flexible arrays of pointers.
*/
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
{ return sizeof(FlexArrayStorage) + sizeof(T)*count + base; }
/* NOLINTEND(bugprone-sizeof-expression) */
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) Flexible
* arrays store their payloads after the end of the object, which must be
* the last in the whole parent chain.
*/
FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
: al::span<T>{::new(static_cast<void*>(this+1)) T[size], size}
{ }
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
~FlexArrayStorage() = default;
FlexArrayStorage(const FlexArrayStorage&) = delete;
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
};
template<typename T, size_t alignment>
struct alignas(alignment) FlexArrayStorage<T,alignment,false> : al::span<T> {
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
{ return sizeof(FlexArrayStorage) + sizeof(T)*count + base; }
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
: al::span<T>{::new(static_cast<void*>(this+1)) T[size], size}
{ }
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
~FlexArrayStorage() { std::destroy(this->begin(), this->end()); }
FlexArrayStorage(const FlexArrayStorage&) = delete;
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
};
/* A flexible array type. Used either standalone or at the end of a parent
* struct, to have a run-time-sized array that's embedded with its size. Should
* be used delicately, ensuring there's no additional data after the FlexArray
* member.
*/
template<typename T, size_t Align=alignof(T)>
struct FlexArray {
using element_type = T;
using value_type = std::remove_cv_t<T>;
using index_type = size_t;
using difference_type = ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
static constexpr std::size_t StorageAlign{std::max(alignof(T), Align)};
using Storage_t_ = FlexArrayStorage<element_type,std::max(alignof(al::span<T>), StorageAlign)>;
using iterator = typename Storage_t_::iterator;
using const_iterator = typename Storage_t_::const_iterator;
using reverse_iterator = typename Storage_t_::reverse_iterator;
using const_reverse_iterator = typename Storage_t_::const_reverse_iterator;
const Storage_t_ mStore;
static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept
{ return Storage_t_::Sizeof(count, base); }
static std::unique_ptr<FlexArray> Create(index_type count)
{ return std::unique_ptr<FlexArray>{new(FamCount{count}) FlexArray{count}}; }
FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v<Storage_t_,index_type>)
: mStore{size}
{ }
~FlexArray() = default;
[[nodiscard]] auto size() const noexcept -> index_type { return mStore.size(); }
[[nodiscard]] auto empty() const noexcept -> bool { return mStore.empty(); }
[[nodiscard]] auto data() noexcept -> pointer { return mStore.data(); }
[[nodiscard]] auto data() const noexcept -> const_pointer { return mStore.data(); }
[[nodiscard]] auto operator[](index_type i) noexcept -> reference { return mStore[i]; }
[[nodiscard]] auto operator[](index_type i) const noexcept -> const_reference { return mStore[i]; }
[[nodiscard]] auto front() noexcept -> reference { return mStore.front(); }
[[nodiscard]] auto front() const noexcept -> const_reference { return mStore.front(); }
[[nodiscard]] auto back() noexcept -> reference { return mStore.back(); }
[[nodiscard]] auto back() const noexcept -> const_reference { return mStore.back(); }
[[nodiscard]] auto begin() noexcept -> iterator { return mStore.begin(); }
[[nodiscard]] auto begin() const noexcept -> const_iterator { return mStore.cbegin(); }
[[nodiscard]] auto cbegin() const noexcept -> const_iterator { return mStore.cbegin(); }
[[nodiscard]] auto end() noexcept -> iterator { return mStore.end(); }
[[nodiscard]] auto end() const noexcept -> const_iterator { return mStore.cend(); }
[[nodiscard]] auto cend() const noexcept -> const_iterator { return mStore.cend(); }
[[nodiscard]] auto rbegin() noexcept -> reverse_iterator { return mStore.rbegin(); }
[[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); }
[[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); }
[[nodiscard]] auto rend() noexcept -> reverse_iterator { return mStore.rend(); }
[[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { return mStore.crend(); }
[[nodiscard]] auto crend() const noexcept -> const_reverse_iterator { return mStore.crend(); }
gsl::owner<void*> operator new(size_t, FamCount count)
{ return ::operator new[](Sizeof(count), std::align_val_t{alignof(FlexArray)}); }
void operator delete(gsl::owner<void*> block, FamCount) noexcept
{ ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); }
void operator delete(gsl::owner<void*> block) noexcept
{ ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); }
void *operator new(size_t size) = delete;
void *operator new[](size_t size) = delete;
void operator delete[](void *block) = delete;
};
} // namespace al
#endif /* AL_FLEXARRAY_H */

View file

@ -1,6 +1,8 @@
#ifndef INTRUSIVE_PTR_H
#define INTRUSIVE_PTR_H
#include <atomic>
#include <cstddef>
#include <utility>
#include "atomic.h"
@ -11,7 +13,7 @@ namespace al {
template<typename T>
class intrusive_ref {
RefCount mRef{1u};
std::atomic<unsigned int> mRef{1u};
public:
unsigned int add_ref() noexcept { return IncrementRef(mRef); }
@ -60,6 +62,9 @@ public:
explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { }
~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); }
/* NOLINTBEGIN(bugprone-unhandled-self-assignment)
* Self-assignment is handled properly here.
*/
intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept
{
static_assert(noexcept(std::declval<T*>()->dec_ref()), "dec_ref must be noexcept");
@ -69,6 +74,7 @@ public:
mPtr = rhs.mPtr;
return *this;
}
/* NOLINTEND(bugprone-unhandled-self-assignment) */
intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept
{
if(&rhs != this) LIKELY
@ -81,9 +87,9 @@ public:
explicit operator bool() const noexcept { return mPtr != nullptr; }
T& operator*() const noexcept { return *mPtr; }
T* operator->() const noexcept { return mPtr; }
T* get() const noexcept { return mPtr; }
[[nodiscard]] auto operator*() const noexcept -> T& { return *mPtr; }
[[nodiscard]] auto operator->() const noexcept -> T* { return mPtr; }
[[nodiscard]] auto get() const noexcept -> T* { return mPtr; }
void reset(T *ptr=nullptr) noexcept
{
@ -98,23 +104,6 @@ public:
void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
};
#define AL_DECL_OP(op) \
template<typename T> \
inline bool operator op(const intrusive_ptr<T> &lhs, const T *rhs) noexcept \
{ return lhs.get() op rhs; } \
template<typename T> \
inline bool operator op(const T *lhs, const intrusive_ptr<T> &rhs) noexcept \
{ return lhs op rhs.get(); }
AL_DECL_OP(==)
AL_DECL_OP(!=)
AL_DECL_OP(<=)
AL_DECL_OP(>=)
AL_DECL_OP(<)
AL_DECL_OP(>)
#undef AL_DECL_OP
} // namespace al
#endif /* INTRUSIVE_PTR_H */

View file

@ -19,10 +19,13 @@
#ifdef __GNUC__
#define force_inline [[gnu::always_inline]] inline
#define NOINLINE [[gnu::noinline]]
#elif defined(_MSC_VER)
#define force_inline __forceinline
#define NOINLINE __declspec(noinline)
#else
#define force_inline inline
#define NOINLINE
#endif
/* Unlike the likely attribute, ASSUME requires the condition to be true or
@ -39,7 +42,7 @@
#elif HAS_BUILTIN(__builtin_unreachable)
#define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0)
#else
#define ASSUME(x) ((void)0)
#define ASSUME(x) (static_cast<void>(0))
#endif
/* This shouldn't be needed since unknown attributes are ignored, but older

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,212 @@
/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
Based on original fortran 77 code from FFTPACKv4 from NETLIB,
authored by Dr Paul Swarztrauber of NCAR, in 1985.
As confirmed by the NCAR fftpack software curators, the following
FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
released under the same terms.
FFTPACK license:
http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
Copyright (c) 2004 the University Corporation for Atmospheric
Research ("UCAR"). All rights reserved. Developed by NCAR's
Computational and Information Systems Laboratory, UCAR,
www.cisl.ucar.edu.
Redistribution and use of the Software in source and binary forms,
with or without modification, is permitted provided that the
following conditions are met:
- Neither the names of NCAR's Computational and Information Systems
Laboratory, the University Corporation for Atmospheric Research,
nor the names of its sponsors or contributors may be used to
endorse or promote products derived from this Software without
specific prior written permission.
- Redistributions of source code must retain the above copyright
notices, this list of conditions, and the disclaimer below.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer below in the
documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
SOFTWARE.
*/
/* PFFFT : a Pretty Fast FFT.
*
* This is basically an adaptation of the single precision fftpack (v4) as
* found on netlib taking advantage of SIMD instructions found on CPUs such as
* Intel x86 (SSE1), PowerPC (Altivec), and Arm (NEON).
*
* For architectures where SIMD instructions aren't available, the code falls
* back to a scalar version.
*
* Restrictions:
*
* - 1D transforms only, with 32-bit single precision.
*
* - supports only transforms for inputs of length N of the form
* N=(2^a)*(3^b)*(5^c), given a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, 144,
* 160, etc are all acceptable lengths). Performance is best for 128<=N<=8192.
*
* - all (float*) pointers for the functions below are expected to have a
* "SIMD-compatible" alignment, that is 16 bytes.
*
* You can allocate such buffers with the pffft_aligned_malloc function, and
* deallocate them with pffft_aligned_free (or with stuff like posix_memalign,
* aligned_alloc, etc).
*
* Note that for the z-domain data of real transforms, when in the canonical
* order (as interleaved complex numbers) both 0-frequency and half-frequency
* components, which are real, are assembled in the first entry as
* F(0)+i*F(n/2+1). The original fftpack placed F(n/2+1) at the end of the
* arrays instead.
*/
#ifndef PFFFT_H
#define PFFFT_H
#include <cstddef>
#include <memory>
#include "almalloc.h"
/* opaque struct holding internal stuff (precomputed twiddle factors) this
* struct can be shared by many threads as it contains only read-only data.
*/
struct PFFFT_Setup;
/* direction of the transform */
enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD };
/* type of transform */
enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX };
void pffft_destroy_setup(gsl::owner<PFFFT_Setup*> setup) noexcept;
struct PFFFTSetupDeleter {
void operator()(gsl::owner<PFFFT_Setup*> setup) const noexcept { pffft_destroy_setup(setup); }
};
using PFFFTSetupPtr = std::unique_ptr<PFFFT_Setup,PFFFTSetupDeleter>;
/**
* Prepare for performing transforms of size N -- the returned PFFFT_Setup
* structure is read-only so it can safely be shared by multiple concurrent
* threads.
*/
PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform);
/**
* Perform a Fourier transform. The z-domain data is stored in the most
* efficient order for transforming back or using for convolution, and as
* such, there's no guarantee to the order of the values. If you need to have
* its content sorted in the usual way, that is as an array of interleaved
* complex numbers, either use pffft_transform_ordered, or call pffft_zreorder
* after the forward fft and before the backward fft.
*
* Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. Typically
* you will want to scale the backward transform by 1/N.
*
* The 'work' pointer must point to an area of N (2*N for complex fft) floats,
* properly aligned. It cannot be NULL.
*
* The input and output parameters may alias.
*/
void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
/**
* Similar to pffft_transform, but handles the complex values in the usual form
* (interleaved complex numbers). This is similar to calling
* pffft_transform(..., PFFFT_FORWARD) followed by
* pffft_zreorder(..., PFFFT_FORWARD), or
* pffft_zreorder(..., PFFFT_BACKWARD) followed by
* pffft_transform(..., PFFFT_BACKWARD), for the given direction.
*
* The input and output parameters may alias.
*/
void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
/**
* Reorder the z-domain data. For PFFFT_FORWARD, it reorders from the internal
* representation to the "canonical" order (as interleaved complex numbers).
* For PFFFT_BACKWARD, it reorders from the canonical order to the internal
* order suitable for pffft_transform(..., PFFFT_BACKWARD) or
* pffft_zconvolve_accumulate.
*
* The input and output parameters should not alias.
*/
void pffft_zreorder(const PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction);
/**
* Perform a multiplication of the z-domain data in dft_a and dft_b, and scale
* and accumulate into dft_ab. The arrays should have been obtained with
* pffft_transform(..., PFFFT_FORWARD) or pffft_zreorder(..., PFFFT_BACKWARD)
* and should *not* be in the usual order (otherwise just perform the operation
* yourself as the dft coeffs are stored as interleaved complex numbers).
*
* The operation performed is: dft_ab += (dft_a * dft_b)*scaling
*
* The dft_a, dft_b, and dft_ab parameters may alias.
*/
void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling);
/**
* Perform a multiplication of the z-domain data in dft_a and dft_b, and
* accumulate into dft_ab.
*
* The operation performed is: dft_ab += dft_a * dft_b
*
* The dft_a, dft_b, and dft_ab parameters may alias.
*/
void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab);
struct PFFFTSetup {
PFFFTSetupPtr mSetup{};
PFFFTSetup() = default;
PFFFTSetup(const PFFFTSetup&) = delete;
PFFFTSetup(PFFFTSetup&& rhs) noexcept = default;
explicit PFFFTSetup(std::nullptr_t) noexcept { }
explicit PFFFTSetup(unsigned int n, pffft_transform_t transform)
: mSetup{pffft_new_setup(n, transform)}
{ }
~PFFFTSetup() = default;
PFFFTSetup& operator=(const PFFFTSetup&) = delete;
PFFFTSetup& operator=(PFFFTSetup&& rhs) noexcept = default;
[[nodiscard]] explicit operator bool() const noexcept { return mSetup != nullptr; }
void transform(const float *input, float *output, float *work, pffft_direction_t direction) const
{ pffft_transform(mSetup.get(), input, output, work, direction); }
void transform_ordered(const float *input, float *output, float *work,
pffft_direction_t direction) const
{ pffft_transform_ordered(mSetup.get(), input, output, work, direction); }
void zreorder(const float *input, float *output, pffft_direction_t direction) const
{ pffft_zreorder(mSetup.get(), input, output, direction); }
void zconvolve_scale_accumulate(const float *dft_a, const float *dft_b, float *dft_ab,
float scaling) const
{ pffft_zconvolve_scale_accumulate(mSetup.get(), dft_a, dft_b, dft_ab, scaling); }
void zconvolve_accumulate(const float *dft_a, const float *dft_b, float *dft_ab) const
{ pffft_zconvolve_accumulate(mSetup.get(), dft_a, dft_b, dft_ab); }
};
#endif // PFFFT_H

View file

@ -8,89 +8,51 @@
#endif
#include <array>
#include <stddef.h>
#include <cmath>
#include <cstddef>
#include "alcomplex.h"
#include "alnumbers.h"
#include "alspan.h"
#include "opthelpers.h"
/* Implements a wide-band +90 degree phase-shift. Note that this should be
* given one sample less of a delay (FilterSize/2 - 1) compared to the direct
* signal delay (FilterSize/2) to properly align.
*/
template<size_t FilterSize>
template<std::size_t FilterSize>
struct PhaseShifterT {
static_assert(FilterSize >= 16, "FilterSize needs to be at least 16");
static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two");
alignas(16) std::array<float,FilterSize/2> mCoeffs{};
/* Some notes on this filter construction.
*
* A wide-band phase-shift filter needs a delay to maintain linearity. A
* dirac impulse in the center of a time-domain buffer represents a filter
* passing all frequencies through as-is with a pure delay. Converting that
* to the frequency domain, adjusting the phase of each frequency bin by
* +90 degrees, then converting back to the time domain, results in a FIR
* filter that applies a +90 degree wide-band phase-shift.
*
* A particularly notable aspect of the time-domain filter response is that
* every other coefficient is 0. This allows doubling the effective size of
* the filter, by storing only the non-0 coefficients and double-stepping
* over the input to apply it.
*
* Additionally, the resulting filter is independent of the sample rate.
* The same filter can be applied regardless of the device's sample rate
* and achieve the same effect.
*/
PhaseShifterT()
{
using complex_d = std::complex<double>;
constexpr size_t fft_size{FilterSize};
constexpr size_t half_size{fft_size / 2};
auto fftBuffer = std::make_unique<complex_d[]>(fft_size);
std::fill_n(fftBuffer.get(), fft_size, complex_d{});
fftBuffer[half_size] = 1.0;
forward_fft(al::as_span(fftBuffer.get(), fft_size));
for(size_t i{0};i < half_size+1;++i)
fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()};
for(size_t i{half_size+1};i < fft_size;++i)
fftBuffer[i] = std::conj(fftBuffer[fft_size - i]);
inverse_fft(al::as_span(fftBuffer.get(), fft_size));
auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1);
for(float &coeff : mCoeffs)
/* Every other coefficient is 0, so we only need to calculate and store
* the non-0 terms and double-step over the input to apply it. The
* calculated coefficients are in reverse to make applying in the time-
* domain more efficient.
*/
for(std::size_t i{0};i < FilterSize/2;++i)
{
coeff = static_cast<float>(fftiter->real() / double{fft_size});
fftiter -= 2;
const int k{static_cast<int>(i*2 + 1) - int{FilterSize/2}};
/* Calculate the Blackman window value for this coefficient. */
const double w{2.0*al::numbers::pi * static_cast<double>(i*2 + 1)
/ double{FilterSize}};
const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w)
- 0.0106411*std::cos(3.0*w)};
const double pk{al::numbers::pi * static_cast<double>(k)};
mCoeffs[i] = static_cast<float>(window * (1.0-std::cos(pk)) / pk);
}
}
void process(al::span<float> dst, const float *RESTRICT src) const;
void process(const al::span<float> dst, const al::span<const float> src) const;
private:
#if defined(HAVE_NEON)
/* There doesn't seem to be NEON intrinsics to do this kind of stipple
* shuffling, so there's two custom methods for it.
*/
static auto shuffle_2020(float32x4_t a, float32x4_t b)
{
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))};
ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1);
ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2);
ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3);
return ret;
}
static auto shuffle_3131(float32x4_t a, float32x4_t b)
{
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))};
ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1);
ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2);
ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3);
return ret;
}
static auto unpacklo(float32x4_t a, float32x4_t b)
{
float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))};
@ -109,105 +71,141 @@ private:
ret = vsetq_lane_f32(d, ret, 3);
return ret;
}
static void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3)
{
float32x4x2_t t0_{vzipq_f32(x0, x2)};
float32x4x2_t t1_{vzipq_f32(x1, x3)};
float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])};
float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])};
x0 = u0_.val[0];
x1 = u0_.val[1];
x2 = u1_.val[0];
x3 = u1_.val[1];
}
#endif
};
template<size_t S>
inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT src) const
template<std::size_t S>
NOINLINE inline
void PhaseShifterT<S>::process(const al::span<float> dst, const al::span<const float> src) const
{
auto in = src.begin();
#ifdef HAVE_SSE_INTRINSICS
if(size_t todo{dst.size()>>1})
if(const std::size_t todo{dst.size()>>2})
{
auto *out = reinterpret_cast<__m64*>(dst.data());
do {
__m128 r04{_mm_setzero_ps()};
__m128 r14{_mm_setzero_ps()};
for(size_t j{0};j < mCoeffs.size();j+=4)
auto out = al::span{reinterpret_cast<__m128*>(dst.data()), todo};
std::generate(out.begin(), out.end(), [&in,this]
{
__m128 r0{_mm_setzero_ps()};
__m128 r1{_mm_setzero_ps()};
__m128 r2{_mm_setzero_ps()};
__m128 r3{_mm_setzero_ps()};
for(std::size_t j{0};j < mCoeffs.size();j+=4)
{
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
const __m128 s0{_mm_loadu_ps(&src[j*2])};
const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])};
const __m128 s0{_mm_loadu_ps(&in[j*2])};
const __m128 s1{_mm_loadu_ps(&in[j*2 + 4])};
const __m128 s2{_mm_movehl_ps(_mm_movelh_ps(s1, s1), s0)};
const __m128 s3{_mm_loadh_pi(_mm_movehl_ps(s1, s1),
reinterpret_cast<const __m64*>(&in[j*2 + 8]))};
__m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))};
r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs));
r0 = _mm_add_ps(r0, _mm_mul_ps(s, coeffs));
s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1));
r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs));
r1 = _mm_add_ps(r1, _mm_mul_ps(s, coeffs));
s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(2, 0, 2, 0));
r2 = _mm_add_ps(r2, _mm_mul_ps(s, coeffs));
s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(3, 1, 3, 1));
r3 = _mm_add_ps(r3, _mm_mul_ps(s, coeffs));
}
src += 2;
in += 4;
__m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))};
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
_mm_storel_pi(out, r4);
++out;
} while(--todo);
_MM_TRANSPOSE4_PS(r0, r1, r2, r3);
return _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3));
});
}
if((dst.size()&1))
if(const std::size_t todo{dst.size()&3})
{
__m128 r4{_mm_setzero_ps()};
for(size_t j{0};j < mCoeffs.size();j+=4)
auto out = dst.last(todo);
std::generate(out.begin(), out.end(), [&in,this]
{
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
}
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
dst.back() = _mm_cvtss_f32(r4);
__m128 r4{_mm_setzero_ps()};
for(std::size_t j{0};j < mCoeffs.size();j+=4)
{
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
const __m128 s{_mm_setr_ps(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])};
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
}
++in;
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
return _mm_cvtss_f32(r4);
});
}
#elif defined(HAVE_NEON)
size_t pos{0};
if(size_t todo{dst.size()>>1})
if(const std::size_t todo{dst.size()>>2})
{
do {
float32x4_t r04{vdupq_n_f32(0.0f)};
float32x4_t r14{vdupq_n_f32(0.0f)};
for(size_t j{0};j < mCoeffs.size();j+=4)
auto out = al::span{reinterpret_cast<float32x4_t*>(dst.data()), todo};
std::generate(out.begin(), out.end(), [&in,this]
{
float32x4_t r0{vdupq_n_f32(0.0f)};
float32x4_t r1{vdupq_n_f32(0.0f)};
float32x4_t r2{vdupq_n_f32(0.0f)};
float32x4_t r3{vdupq_n_f32(0.0f)};
for(std::size_t j{0};j < mCoeffs.size();j+=4)
{
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
const float32x4_t s0{vld1q_f32(&src[j*2])};
const float32x4_t s1{vld1q_f32(&src[j*2 + 4])};
const float32x4_t s0{vld1q_f32(&in[j*2])};
const float32x4_t s1{vld1q_f32(&in[j*2 + 4])};
const float32x4_t s2{vcombine_f32(vget_high_f32(s0), vget_low_f32(s1))};
const float32x4_t s3{vcombine_f32(vget_high_f32(s1), vld1_f32(&in[j*2 + 8]))};
const float32x4x2_t values0{vuzpq_f32(s0, s1)};
const float32x4x2_t values1{vuzpq_f32(s2, s3)};
r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs);
r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs);
r0 = vmlaq_f32(r0, values0.val[0], coeffs);
r1 = vmlaq_f32(r1, values0.val[1], coeffs);
r2 = vmlaq_f32(r2, values1.val[0], coeffs);
r3 = vmlaq_f32(r3, values1.val[1], coeffs);
}
src += 2;
in += 4;
float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))};
float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))};
vst1_f32(&dst[pos], r2);
pos += 2;
} while(--todo);
vtranspose4(r0, r1, r2, r3);
return vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3));
});
}
if((dst.size()&1))
if(const std::size_t todo{dst.size()&3})
{
float32x4_t r4{vdupq_n_f32(0.0f)};
for(size_t j{0};j < mCoeffs.size();j+=4)
auto out = dst.last(todo);
std::generate(out.begin(), out.end(), [&in,this]
{
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
r4 = vmlaq_f32(r4, s, coeffs);
}
r4 = vaddq_f32(r4, vrev64q_f32(r4));
dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
float32x4_t r4{vdupq_n_f32(0.0f)};
for(std::size_t j{0};j < mCoeffs.size();j+=4)
{
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
const float32x4_t s{load4(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])};
r4 = vmlaq_f32(r4, s, coeffs);
}
++in;
r4 = vaddq_f32(r4, vrev64q_f32(r4));
return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
});
}
#else
for(float &output : dst)
std::generate(dst.begin(), dst.end(), [&in,this]
{
float ret{0.0f};
for(size_t j{0};j < mCoeffs.size();++j)
ret += src[j*2] * mCoeffs[j];
output = ret;
++src;
}
for(std::size_t j{0};j < mCoeffs.size();++j)
ret += in[j*2] * mCoeffs[j];
++in;
return ret;
});
#endif
}

View file

@ -3,16 +3,61 @@
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <numeric>
#include <tuple>
#include "alnumbers.h"
#include "opthelpers.h"
using uint = unsigned int;
namespace {
constexpr double Epsilon{1e-9};
using uint = unsigned int;
#if __cpp_lib_math_special_functions >= 201603L
using std::cyl_bessel_i;
#else
/* The zero-order modified Bessel function of the first kind, used for the
* Kaiser window.
*
* I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
* = sum_{k=0}^inf ((x / 2)^k / k!)^2
*
* This implementation only handles nu = 0, and isn't the most precise (it
* starts with the largest value and accumulates successively smaller values,
* compounding the rounding and precision error), but it's good enough.
*/
template<typename T, typename U>
U cyl_bessel_i(T nu, U x)
{
if(nu != T{0})
throw std::runtime_error{"cyl_bessel_i: nu != 0"};
/* Start at k=1 since k=0 is trivial. */
const double x2{x/2.0};
double term{1.0};
double sum{1.0};
int k{1};
/* Let the integration converge until the term of the sum is no longer
* significant.
*/
double last_sum{};
do {
const double y{x2 / k};
++k;
last_sum = sum;
term *= y * y;
sum += term;
} while(sum != last_sum);
return static_cast<U>(sum);
}
#endif
/* This is the normalized cardinal sine (sinc) function.
*
@ -26,33 +71,6 @@ double Sinc(const double x)
return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
}
/* The zero-order modified Bessel function of the first kind, used for the
* Kaiser window.
*
* I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
* = sum_{k=0}^inf ((x / 2)^k / k!)^2
*/
constexpr double BesselI_0(const double x)
{
// Start at k=1 since k=0 is trivial.
const double x2{x/2.0};
double term{1.0};
double sum{1.0};
int k{1};
// Let the integration converge until the term of the sum is no longer
// significant.
double last_sum{};
do {
const double y{x2 / k};
++k;
last_sum = sum;
term *= y * y;
sum += term;
} while(sum != last_sum);
return sum;
}
/* Calculate a Kaiser window from the given beta value and a normalized k
* [-1, 1].
*
@ -67,23 +85,11 @@ constexpr double BesselI_0(const double x)
*
* k = 2 i / M - 1, where 0 <= i <= M.
*/
double Kaiser(const double b, const double k)
double Kaiser(const double beta, const double k, const double besseli_0_beta)
{
if(!(k >= -1.0 && k <= 1.0))
return 0.0;
return BesselI_0(b * std::sqrt(1.0 - k*k)) / BesselI_0(b);
}
// Calculates the greatest common divisor of a and b.
constexpr uint Gcd(uint x, uint y)
{
while(y > 0)
{
const uint z{y};
y = x % y;
x = z;
}
return x;
return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta;
}
/* Calculates the size (order) of the Kaiser window. Rejection is in dB and
@ -124,11 +130,11 @@ constexpr double CalcKaiserBeta(const double rejection)
* p -- gain compensation factor when sampling
* f_t -- normalized center frequency (or cutoff; 0.5 is nyquist)
*/
double SincFilter(const uint l, const double b, const double gain, const double cutoff,
const uint i)
double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain,
const double cutoff, const uint i)
{
const double x{static_cast<double>(i) - l};
return Kaiser(b, x / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x);
return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x);
}
} // namespace
@ -137,7 +143,7 @@ double SincFilter(const uint l, const double b, const double gain, const double
// that's used to cut frequencies above the destination nyquist.
void PPhaseResampler::init(const uint srcRate, const uint dstRate)
{
const uint gcd{Gcd(srcRate, dstRate)};
const uint gcd{std::gcd(srcRate, dstRate)};
mP = dstRate / gcd;
mQ = srcRate / gcd;
@ -145,78 +151,70 @@ void PPhaseResampler::init(const uint srcRate, const uint dstRate)
* ends before the nyquist (0.5). Both are scaled by the downsampling
* factor.
*/
double cutoff, width;
if(mP > mQ)
{
cutoff = 0.475 / mP;
width = 0.05 / mP;
}
else
{
cutoff = 0.475 / mQ;
width = 0.05 / mQ;
}
const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP)
: std::make_tuple(0.475 / mQ, 0.05 / mQ);
// A rejection of -180 dB is used for the stop band. Round up when
// calculating the left offset to avoid increasing the transition width.
const uint l{(CalcKaiserOrder(180.0, width)+1) / 2};
const double beta{CalcKaiserBeta(180.0)};
const double besseli_0_beta{cyl_bessel_i(0, beta)};
mM = l*2 + 1;
mL = l;
mF.resize(mM);
for(uint i{0};i < mM;i++)
mF[i] = SincFilter(l, beta, mP, cutoff, i);
mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i);
}
// Perform the upsample-filter-downsample resampling operation using a
// polyphase filter implementation.
void PPhaseResampler::process(const uint inN, const double *in, const uint outN, double *out)
void PPhaseResampler::process(const al::span<const double> in, const al::span<double> out)
{
if(outN == 0) UNLIKELY
if(out.empty()) UNLIKELY
return;
// Handle in-place operation.
std::vector<double> workspace;
double *work{out};
if(work == in) UNLIKELY
al::span work{out};
if(work.data() == in.data()) UNLIKELY
{
workspace.resize(outN);
work = workspace.data();
workspace.resize(out.size());
work = workspace;
}
// Resample the input.
const uint p{mP}, q{mQ}, m{mM}, l{mL};
const double *f{mF.data()};
for(uint i{0};i < outN;i++)
const al::span<const double> f{mF};
for(uint i{0};i < out.size();i++)
{
// Input starts at l to compensate for the filter delay. This will
// drop any build-up from the first half of the filter.
size_t j_f{(l + q*i) % p};
size_t j_s{(l + q*i) / p};
std::size_t j_f{(l + q*i) % p};
std::size_t j_s{(l + q*i) / p};
// Only take input when 0 <= j_s < inN.
// Only take input when 0 <= j_s < in.size().
double r{0.0};
if(j_f < m) LIKELY
{
size_t filt_len{(m-j_f+p-1) / p};
if(j_s+1 > inN) LIKELY
std::size_t filt_len{(m-j_f+p-1) / p};
if(j_s+1 > in.size()) LIKELY
{
size_t skip{std::min<size_t>(j_s+1 - inN, filt_len)};
std::size_t skip{std::min(j_s+1 - in.size(), filt_len)};
j_f += p*skip;
j_s -= skip;
filt_len -= skip;
}
if(size_t todo{std::min<size_t>(j_s+1, filt_len)}) LIKELY
std::size_t todo{std::min(j_s+1, filt_len)};
while(todo)
{
do {
r += f[j_f] * in[j_s];
j_f += p;
--j_s;
} while(--todo);
r += f[j_f] * in[j_s];
j_f += p; --j_s;
--todo;
}
}
work[i] = r;
}
// Clean up after in-place operation.
if(work != out)
std::copy_n(work, outN, out);
if(work.data() != out.data())
std::copy(work.cbegin(), work.cend(), out.begin());
}

View file

@ -3,6 +3,8 @@
#include <vector>
#include "alspan.h"
using uint = unsigned int;
@ -35,12 +37,12 @@ using uint = unsigned int;
struct PPhaseResampler {
void init(const uint srcRate, const uint dstRate);
void process(const uint inN, const double *in, const uint outN, double *out);
void process(const al::span<const double> in, const al::span<double> out);
explicit operator bool() const noexcept { return !mF.empty(); }
private:
uint mP, mQ, mM, mL;
uint mP{}, mQ{}, mM{}, mL{};
std::vector<double> mF;
};

View file

@ -23,202 +23,150 @@
#include "ringbuffer.h"
#include <algorithm>
#include <climits>
#include <cstdint>
#include <limits>
#include <stdexcept>
#include <tuple>
#include "almalloc.h"
#include "alnumeric.h"
RingBufferPtr RingBuffer::Create(size_t sz, size_t elem_sz, int limit_writes)
auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr
{
size_t power_of_two{0u};
std::size_t power_of_two{0u};
if(sz > 0)
{
power_of_two = sz;
power_of_two = sz - 1;
power_of_two |= power_of_two>>1;
power_of_two |= power_of_two>>2;
power_of_two |= power_of_two>>4;
power_of_two |= power_of_two>>8;
power_of_two |= power_of_two>>16;
#if SIZE_MAX > UINT_MAX
power_of_two |= power_of_two>>32;
#endif
if constexpr(sizeof(size_t) > sizeof(uint32_t))
power_of_two |= power_of_two>>32;
}
++power_of_two;
if(power_of_two <= sz || power_of_two > std::numeric_limits<size_t>::max()/elem_sz)
if(power_of_two < sz || power_of_two > std::numeric_limits<std::size_t>::max()>>1
|| power_of_two > std::numeric_limits<std::size_t>::max()/elem_sz)
throw std::overflow_error{"Ring buffer size overflow"};
const size_t bufbytes{power_of_two * elem_sz};
RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{bufbytes}};
rb->mWriteSize = limit_writes ? sz : (power_of_two-1);
rb->mSizeMask = power_of_two - 1;
rb->mElemSize = elem_sz;
const std::size_t bufbytes{power_of_two * elem_sz};
RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{limit_writes ? sz : power_of_two,
power_of_two-1, elem_sz, bufbytes}};
return rb;
}
void RingBuffer::reset() noexcept
{
mWritePtr.store(0, std::memory_order_relaxed);
mReadPtr.store(0, std::memory_order_relaxed);
std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, al::byte{});
mWriteCount.store(0, std::memory_order_relaxed);
mReadCount.store(0, std::memory_order_relaxed);
std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, std::byte{});
}
size_t RingBuffer::read(void *dest, size_t cnt) noexcept
auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t
{
const size_t free_cnt{readSpace()};
if(free_cnt == 0) return 0;
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
const std::size_t readable{w - r};
if(readable == 0) return 0;
const size_t to_read{std::min(cnt, free_cnt)};
size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask};
const std::size_t to_read{std::min(count, readable)};
const std::size_t read_idx{r & mSizeMask};
size_t n1, n2;
const size_t cnt2{read_ptr + to_read};
if(cnt2 > mSizeMask+1)
{
n1 = mSizeMask+1 - read_ptr;
n2 = cnt2 & mSizeMask;
}
else
{
n1 = to_read;
n2 = 0;
}
const std::size_t rdend{read_idx + to_read};
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz)
: std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask);
auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize,
static_cast<al::byte*>(dest));
read_ptr += n1;
auto dstbytes = al::span{static_cast<std::byte*>(dest), count*mElemSize};
auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize,
dstbytes.begin());
if(n2 > 0)
{
std::copy_n(mBuffer.begin(), n2*mElemSize, outiter);
read_ptr += n2;
}
mReadPtr.store(read_ptr, std::memory_order_release);
mReadCount.store(r+n1+n2, std::memory_order_release);
return to_read;
}
size_t RingBuffer::peek(void *dest, size_t cnt) const noexcept
auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size_t
{
const size_t free_cnt{readSpace()};
if(free_cnt == 0) return 0;
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
const std::size_t readable{w - r};
if(readable == 0) return 0;
const size_t to_read{std::min(cnt, free_cnt)};
size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask};
const std::size_t to_read{std::min(count, readable)};
const std::size_t read_idx{r & mSizeMask};
size_t n1, n2;
const size_t cnt2{read_ptr + to_read};
if(cnt2 > mSizeMask+1)
{
n1 = mSizeMask+1 - read_ptr;
n2 = cnt2 & mSizeMask;
}
else
{
n1 = to_read;
n2 = 0;
}
const std::size_t rdend{read_idx + to_read};
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz)
: std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask);
auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize,
static_cast<al::byte*>(dest));
auto dstbytes = al::span{static_cast<std::byte*>(dest), count*mElemSize};
auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize,
dstbytes.begin());
if(n2 > 0)
std::copy_n(mBuffer.begin(), n2*mElemSize, outiter);
return to_read;
}
size_t RingBuffer::write(const void *src, size_t cnt) noexcept
auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size_t
{
const size_t free_cnt{writeSpace()};
if(free_cnt == 0) return 0;
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
const std::size_t writable{mWriteSize - (w - r)};
if(writable == 0) return 0;
const size_t to_write{std::min(cnt, free_cnt)};
size_t write_ptr{mWritePtr.load(std::memory_order_relaxed) & mSizeMask};
const std::size_t to_write{std::min(count, writable)};
const std::size_t write_idx{w & mSizeMask};
size_t n1, n2;
const size_t cnt2{write_ptr + to_write};
if(cnt2 > mSizeMask+1)
{
n1 = mSizeMask+1 - write_ptr;
n2 = cnt2 & mSizeMask;
}
else
{
n1 = to_write;
n2 = 0;
}
const std::size_t wrend{write_idx + to_write};
const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::make_tuple(to_write, 0_uz)
: std::make_tuple(mSizeMask+1 - write_idx, wrend&mSizeMask);
auto srcbytes = static_cast<const al::byte*>(src);
std::copy_n(srcbytes, n1*mElemSize, mBuffer.begin() + write_ptr*mElemSize);
write_ptr += n1;
auto srcbytes = al::span{static_cast<const std::byte*>(src), count*mElemSize};
std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize));
if(n2 > 0)
{
std::copy_n(srcbytes + n1*mElemSize, n2*mElemSize, mBuffer.begin());
write_ptr += n2;
}
mWritePtr.store(write_ptr, std::memory_order_release);
std::copy_n(srcbytes.cbegin() + ptrdiff_t(n1*mElemSize), n2*mElemSize, mBuffer.begin());
mWriteCount.store(w+n1+n2, std::memory_order_release);
return to_write;
}
auto RingBuffer::getReadVector() const noexcept -> DataPair
auto RingBuffer::getReadVector() noexcept -> DataPair
{
DataPair ret;
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
const std::size_t readable{w - r};
const std::size_t read_idx{r & mSizeMask};
size_t w{mWritePtr.load(std::memory_order_acquire)};
size_t r{mReadPtr.load(std::memory_order_acquire)};
w &= mSizeMask;
r &= mSizeMask;
const size_t free_cnt{(w-r) & mSizeMask};
const size_t cnt2{r + free_cnt};
if(cnt2 > mSizeMask+1)
const std::size_t rdend{read_idx + readable};
if(rdend > mSizeMask+1)
{
/* Two part vector: the rest of the buffer after the current read ptr,
* plus some from the start of the buffer. */
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize);
ret.first.len = mSizeMask+1 - r;
ret.second.buf = const_cast<al::byte*>(mBuffer.data());
ret.second.len = cnt2 & mSizeMask;
* plus some from the start of the buffer.
*/
return DataPair{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx},
{mBuffer.data(), rdend&mSizeMask}};
}
else
{
/* Single part vector: just the rest of the buffer */
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize);
ret.first.len = free_cnt;
ret.second.buf = nullptr;
ret.second.len = 0;
}
return ret;
return DataPair{{mBuffer.data() + read_idx*mElemSize, readable}, {}};
}
auto RingBuffer::getWriteVector() const noexcept -> DataPair
auto RingBuffer::getWriteVector() noexcept -> DataPair
{
DataPair ret;
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
const std::size_t writable{mWriteSize - (w - r)};
const std::size_t write_idx{w & mSizeMask};
size_t w{mWritePtr.load(std::memory_order_acquire)};
size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask};
w &= mSizeMask;
r &= mSizeMask;
const size_t free_cnt{(r-w-1) & mSizeMask};
const size_t cnt2{w + free_cnt};
if(cnt2 > mSizeMask+1)
const std::size_t wrend{write_idx + writable};
if(wrend > mSizeMask+1)
{
/* Two part vector: the rest of the buffer after the current write ptr,
* plus some from the start of the buffer. */
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize);
ret.first.len = mSizeMask+1 - w;
ret.second.buf = const_cast<al::byte*>(mBuffer.data());
ret.second.len = cnt2 & mSizeMask;
* plus some from the start of the buffer.
*/
return DataPair{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx},
{mBuffer.data(), wrend&mSizeMask}};
}
else
{
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize);
ret.first.len = free_cnt;
ret.second.buf = nullptr;
ret.second.len = 0;
}
return ret;
return DataPair{{mBuffer.data() + write_idx*mElemSize, writable}, {}};
}

View file

@ -2,111 +2,133 @@
#define RINGBUFFER_H
#include <atomic>
#include <cassert>
#include <cstddef>
#include <memory>
#include <stddef.h>
#include <new>
#include <utility>
#include "albyte.h"
#include "almalloc.h"
#include "flexarray.h"
/* NOTE: This lockless ringbuffer implementation is copied from JACK, extended
* to include an element size. Consequently, parameters and return values for a
* size or count is in 'elements', not bytes. Additionally, it only supports
* size or count are in 'elements', not bytes. Additionally, it only supports
* single-consumer/single-provider operation.
*/
struct RingBuffer {
private:
std::atomic<size_t> mWritePtr{0u};
std::atomic<size_t> mReadPtr{0u};
size_t mWriteSize{0u};
size_t mSizeMask{0u};
size_t mElemSize{0u};
#if defined(__cpp_lib_hardware_interference_size) && !defined(_LIBCPP_VERSION)
static constexpr std::size_t sCacheAlignment{std::hardware_destructive_interference_size};
#else
/* Assume a 64-byte cache line, the most common/likely value. */
static constexpr std::size_t sCacheAlignment{64};
#endif
alignas(sCacheAlignment) std::atomic<std::size_t> mWriteCount{0u};
alignas(sCacheAlignment) std::atomic<std::size_t> mReadCount{0u};
al::FlexArray<al::byte, 16> mBuffer;
alignas(sCacheAlignment) const std::size_t mWriteSize;
const std::size_t mSizeMask;
const std::size_t mElemSize;
al::FlexArray<std::byte, 16> mBuffer;
public:
struct Data {
al::byte *buf;
size_t len;
std::byte *buf;
std::size_t len;
};
using DataPair = std::pair<Data,Data>;
RingBuffer(const size_t count) : mBuffer{count} { }
RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize,
const std::size_t numbytes)
: mWriteSize{writesize}, mSizeMask{mask}, mElemSize{elemsize}, mBuffer{numbytes}
{ }
/** Reset the read and write pointers to zero. This is not thread safe. */
void reset() noexcept;
auto reset() noexcept -> void;
/**
* Return the number of elements available for reading. This is the number
* of elements in front of the read pointer and behind the write pointer.
*/
[[nodiscard]] auto readSpace() const noexcept -> std::size_t
{
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
/* mWriteCount is never more than mWriteSize greater than mReadCount. */
return w - r;
}
/**
* The copying data reader. Copy at most `count' elements into `dest'.
* Returns the actual number of elements copied.
*/
[[nodiscard]] auto read(void *dest, std::size_t count) noexcept -> std::size_t;
/**
* The copying data reader w/o read pointer advance. Copy at most `count'
* elements into `dest'. Returns the actual number of elements copied.
*/
[[nodiscard]] auto peek(void *dest, std::size_t count) const noexcept -> std::size_t;
/**
* The non-copying data reader. Returns two ringbuffer data pointers that
* hold the current readable data. If the readable data is in one segment
* the second segment has zero length.
*/
DataPair getReadVector() const noexcept;
[[nodiscard]] auto getReadVector() noexcept -> DataPair;
/** Advance the read pointer `count' places. */
auto readAdvance(std::size_t count) noexcept -> void
{
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
[[maybe_unused]] const std::size_t readable{w - r};
assert(readable >= count);
mReadCount.store(r+count, std::memory_order_release);
}
/**
* Return the number of elements available for writing. This is the total
* number of writable elements excluding what's readable (already written).
*/
[[nodiscard]] auto writeSpace() const noexcept -> std::size_t
{ return mWriteSize - readSpace(); }
/**
* The copying data writer. Copy at most `count' elements from `src'. Returns
* the actual number of elements copied.
*/
[[nodiscard]] auto write(const void *src, std::size_t count) noexcept -> std::size_t;
/**
* The non-copying data writer. Returns two ringbuffer data pointers that
* hold the current writeable data. If the writeable data is in one segment
* the second segment has zero length.
*/
DataPair getWriteVector() const noexcept;
/**
* Return the number of elements available for reading. This is the number
* of elements in front of the read pointer and behind the write pointer.
*/
size_t readSpace() const noexcept
[[nodiscard]] auto getWriteVector() noexcept -> DataPair;
/** Advance the write pointer `count' places. */
auto writeAdvance(std::size_t count) noexcept -> void
{
const size_t w{mWritePtr.load(std::memory_order_acquire)};
const size_t r{mReadPtr.load(std::memory_order_acquire)};
return (w-r) & mSizeMask;
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
[[maybe_unused]] const std::size_t writable{mWriteSize - (w - r)};
assert(writable >= count);
mWriteCount.store(w+count, std::memory_order_release);
}
/**
* The copying data reader. Copy at most `cnt' elements into `dest'.
* Returns the actual number of elements copied.
*/
size_t read(void *dest, size_t cnt) noexcept;
/**
* The copying data reader w/o read pointer advance. Copy at most `cnt'
* elements into `dest'. Returns the actual number of elements copied.
*/
size_t peek(void *dest, size_t cnt) const noexcept;
/** Advance the read pointer `cnt' places. */
void readAdvance(size_t cnt) noexcept
{ mReadPtr.fetch_add(cnt, std::memory_order_acq_rel); }
/**
* Return the number of elements available for writing. This is the number
* of elements in front of the write pointer and behind the read pointer.
*/
size_t writeSpace() const noexcept
{
const size_t w{mWritePtr.load(std::memory_order_acquire)};
const size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask};
return (r-w-1) & mSizeMask;
}
/**
* The copying data writer. Copy at most `cnt' elements from `src'. Returns
* the actual number of elements copied.
*/
size_t write(const void *src, size_t cnt) noexcept;
/** Advance the write pointer `cnt' places. */
void writeAdvance(size_t cnt) noexcept
{ mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); }
size_t getElemSize() const noexcept { return mElemSize; }
[[nodiscard]] auto getElemSize() const noexcept -> std::size_t { return mElemSize; }
/**
* Create a new ringbuffer to hold at least `sz' elements of `elem_sz'
* bytes. The number of elements is rounded up to the next power of two
* (even if it is already a power of two, to ensure the requested amount
* can be written).
* bytes. The number of elements is rounded up to a power of two. If
* `limit_writes' is true, the writable space will be limited to `sz'
* elements regardless of the rounded size.
*/
static std::unique_ptr<RingBuffer> Create(size_t sz, size_t elem_sz, int limit_writes);
[[nodiscard]] static
auto Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> std::unique_ptr<RingBuffer>;
DEF_FAM_NEWDEL(RingBuffer, mBuffer)
};

View file

@ -5,36 +5,37 @@
#include <cstdlib>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
std::string wstr_to_utf8(const WCHAR *wstr)
#include "alstring.h"
std::string wstr_to_utf8(std::wstring_view wstr)
{
std::string ret;
int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
const int len{WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), nullptr, 0,
nullptr, nullptr)};
if(len > 0)
{
ret.resize(len);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr);
ret.pop_back();
ret.resize(static_cast<size_t>(len));
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), ret.data(), len,
nullptr, nullptr);
}
return ret;
}
std::wstring utf8_to_wstr(const char *str)
std::wstring utf8_to_wstr(std::string_view str)
{
std::wstring ret;
int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
const int len{MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), nullptr, 0)};
if(len > 0)
{
ret.resize(len);
MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len);
ret.pop_back();
ret.resize(static_cast<size_t>(len));
MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), ret.data(), len);
}
return ret;
@ -43,21 +44,25 @@ std::wstring utf8_to_wstr(const char *str)
namespace al {
al::optional<std::string> getenv(const char *envname)
std::optional<std::string> getenv(const char *envname)
{
#ifdef _GAMING_XBOX
const char *str{::getenv(envname)};
#else
const char *str{std::getenv(envname)};
if(str && str[0] != '\0')
#endif
if(str && *str != '\0')
return str;
return al::nullopt;
return std::nullopt;
}
#ifdef _WIN32
al::optional<std::wstring> getenv(const WCHAR *envname)
std::optional<std::wstring> getenv(const WCHAR *envname)
{
const WCHAR *str{_wgetenv(envname)};
if(str && str[0] != L'\0')
if(str && *str != L'\0')
return str;
return al::nullopt;
return std::nullopt;
}
#endif

View file

@ -1,22 +1,22 @@
#ifndef AL_STRUTILS_H
#define AL_STRUTILS_H
#include <optional>
#include <string>
#include "aloptional.h"
#ifdef _WIN32
#include <wchar.h>
#include <cwchar>
#include <string_view>
std::string wstr_to_utf8(const wchar_t *wstr);
std::wstring utf8_to_wstr(const char *str);
std::string wstr_to_utf8(std::wstring_view wstr);
std::wstring utf8_to_wstr(std::string_view str);
#endif
namespace al {
al::optional<std::string> getenv(const char *envname);
std::optional<std::string> getenv(const char *envname);
#ifdef _WIN32
al::optional<std::wstring> getenv(const wchar_t *envname);
std::optional<std::wstring> getenv(const wchar_t *envname);
#endif
} // namespace al

View file

@ -1,48 +0,0 @@
#ifndef AL_THREADS_H
#define AL_THREADS_H
#if defined(__GNUC__) && defined(__i386__)
/* force_align_arg_pointer is required for proper function arguments aligning
* when SSE code is used. Some systems (Windows, QNX) do not guarantee our
* thread functions will be properly aligned on the stack, even though GCC may
* generate code with the assumption that it is. */
#define FORCE_ALIGN __attribute__((force_align_arg_pointer))
#else
#define FORCE_ALIGN
#endif
#if defined(__APPLE__)
#include <dispatch/dispatch.h>
#elif !defined(_WIN32)
#include <semaphore.h>
#endif
void althrd_setname(const char *name);
namespace al {
class semaphore {
#ifdef _WIN32
using native_type = void*;
#elif defined(__APPLE__)
using native_type = dispatch_semaphore_t;
#else
using native_type = sem_t;
#endif
native_type mSem;
public:
semaphore(unsigned int initial=0);
semaphore(const semaphore&) = delete;
~semaphore();
semaphore& operator=(const semaphore&) = delete;
void post();
void wait() noexcept;
bool try_wait() noexcept;
};
} // namespace al
#endif /* AL_THREADS_H */

View file

@ -1,6 +1,7 @@
#ifndef COMMON_VECMAT_H
#define COMMON_VECMAT_H
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
@ -11,22 +12,24 @@
namespace alu {
template<typename T>
class VectorR {
static_assert(std::is_floating_point<T>::value, "Must use floating-point types");
alignas(16) T mVals[4];
class Vector {
alignas(16) std::array<float,4> mVals{};
public:
constexpr VectorR() noexcept = default;
constexpr VectorR(const VectorR&) noexcept = default;
constexpr explicit VectorR(T a, T b, T c, T d) noexcept : mVals{a, b, c, d} { }
constexpr Vector() noexcept = default;
constexpr Vector(const Vector&) noexcept = default;
constexpr Vector(Vector&&) noexcept = default;
constexpr explicit Vector(float a, float b, float c, float d) noexcept : mVals{{a,b,c,d}} { }
constexpr VectorR& operator=(const VectorR&) noexcept = default;
constexpr auto operator=(const Vector&) noexcept -> Vector& = default;
constexpr auto operator=(Vector&&) noexcept -> Vector& = default;
constexpr T& operator[](size_t idx) noexcept { return mVals[idx]; }
constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; }
[[nodiscard]] constexpr
auto operator[](std::size_t idx) noexcept -> float& { return mVals[idx]; }
[[nodiscard]] constexpr
auto operator[](std::size_t idx) const noexcept -> const float& { return mVals[idx]; }
constexpr VectorR& operator+=(const VectorR &rhs) noexcept
constexpr auto operator+=(const Vector &rhs) noexcept -> Vector&
{
mVals[0] += rhs.mVals[0];
mVals[1] += rhs.mVals[1];
@ -35,85 +38,85 @@ public:
return *this;
}
constexpr VectorR operator-(const VectorR &rhs) const noexcept
[[nodiscard]] constexpr
auto operator-(const Vector &rhs) const noexcept -> Vector
{
return VectorR{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
return Vector{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]};
}
constexpr T normalize(T limit = std::numeric_limits<T>::epsilon())
constexpr auto normalize(float limit = std::numeric_limits<float>::epsilon()) -> float
{
limit = std::max(limit, std::numeric_limits<T>::epsilon());
const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]};
limit = std::max(limit, std::numeric_limits<float>::epsilon());
const auto length_sqr = float{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]};
if(length_sqr > limit*limit)
{
const T length{std::sqrt(length_sqr)};
T inv_length{T{1}/length};
const auto length = float{std::sqrt(length_sqr)};
auto inv_length = float{1.0f / length};
mVals[0] *= inv_length;
mVals[1] *= inv_length;
mVals[2] *= inv_length;
return length;
}
mVals[0] = mVals[1] = mVals[2] = T{0};
return T{0};
mVals[0] = mVals[1] = mVals[2] = 0.0f;
return 0.0f;
}
constexpr VectorR cross_product(const alu::VectorR<T> &rhs) const noexcept
[[nodiscard]] constexpr auto cross_product(const Vector &rhs) const noexcept -> Vector
{
return VectorR{
return Vector{
mVals[1]*rhs.mVals[2] - mVals[2]*rhs.mVals[1],
mVals[2]*rhs.mVals[0] - mVals[0]*rhs.mVals[2],
mVals[0]*rhs.mVals[1] - mVals[1]*rhs.mVals[0],
T{0}};
0.0f};
}
constexpr T dot_product(const alu::VectorR<T> &rhs) const noexcept
[[nodiscard]] constexpr auto dot_product(const Vector &rhs) const noexcept -> float
{ return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; }
};
using Vector = VectorR<float>;
template<typename T>
class MatrixR {
static_assert(std::is_floating_point<T>::value, "Must use floating-point types");
alignas(16) T mVals[16];
class Matrix {
alignas(16) std::array<float,16> mVals{};
public:
constexpr MatrixR() noexcept = default;
constexpr MatrixR(const MatrixR&) noexcept = default;
constexpr explicit MatrixR(
T aa, T ab, T ac, T ad,
T ba, T bb, T bc, T bd,
T ca, T cb, T cc, T cd,
T da, T db, T dc, T dd) noexcept
: mVals{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}
constexpr Matrix() noexcept = default;
constexpr Matrix(const Matrix&) noexcept = default;
constexpr Matrix(Matrix&&) noexcept = default;
constexpr explicit Matrix(
float aa, float ab, float ac, float ad,
float ba, float bb, float bc, float bd,
float ca, float cb, float cc, float cd,
float da, float db, float dc, float dd) noexcept
: mVals{{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}}
{ }
constexpr MatrixR& operator=(const MatrixR&) noexcept = default;
constexpr auto operator=(const Matrix&) noexcept -> Matrix& = default;
constexpr auto operator=(Matrix&&) noexcept -> Matrix& = default;
constexpr auto operator[](size_t idx) noexcept { return al::span<T,4>{&mVals[idx*4], 4}; }
constexpr auto operator[](size_t idx) const noexcept
{ return al::span<const T,4>{&mVals[idx*4], 4}; }
[[nodiscard]] constexpr auto operator[](std::size_t idx) noexcept
{ return al::span<float,4>{&mVals[idx*4], 4}; }
[[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept
{ return al::span<const float,4>{&mVals[idx*4], 4}; }
static constexpr MatrixR Identity() noexcept
[[nodiscard]] static constexpr auto Identity() noexcept -> Matrix
{
return MatrixR{
T{1}, T{0}, T{0}, T{0},
T{0}, T{1}, T{0}, T{0},
T{0}, T{0}, T{1}, T{0},
T{0}, T{0}, T{0}, T{1}};
return Matrix{
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f};
}
[[nodiscard]] friend constexpr
auto operator*(const Matrix &mtx, const Vector &vec) noexcept -> Vector
{
return Vector{
vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]};
}
};
using Matrix = MatrixR<float>;
template<typename T>
constexpr VectorR<T> operator*(const MatrixR<T> &mtx, const VectorR<T> &vec) noexcept
{
return VectorR<T>{
vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]};
}
} // namespace alu

View file

@ -1,13 +1,14 @@
#ifndef AL_VECTOR_H
#define AL_VECTOR_H
#include <cstddef>
#include <vector>
#include "almalloc.h"
namespace al {
template<typename T, size_t alignment=alignof(T)>
template<typename T, std::size_t alignment=alignof(T)>
using vector = std::vector<T, al::allocator<T, alignment>>;
} // namespace al

View file

@ -20,14 +20,20 @@
#define STATIC_CAST(...) static_cast<__VA_ARGS__>
#define REINTERPRET_CAST(...) reinterpret_cast<__VA_ARGS__>
#define MAYBE_UNUSED [[maybe_unused]]
#else
#define STATIC_CAST(...) (__VA_ARGS__)
#define REINTERPRET_CAST(...) (__VA_ARGS__)
#ifdef __GNUC__
#define MAYBE_UNUSED __attribute__((__unused__))
#else
#define MAYBE_UNUSED
#endif
#endif
static FILE *my_fopen(const char *fname, const char *mode)
MAYBE_UNUSED static FILE *my_fopen(const char *fname, const char *mode)
{
wchar_t *wname=NULL, *wmode=NULL;
int namelen, modelen;
@ -44,10 +50,11 @@ static FILE *my_fopen(const char *fname, const char *mode)
}
#ifdef __cplusplus
auto strbuf = std::make_unique<wchar_t[]>(static_cast<size_t>(namelen)+modelen);
auto strbuf = std::make_unique<wchar_t[]>(static_cast<size_t>(namelen) +
static_cast<size_t>(modelen));
wname = strbuf.get();
#else
wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + modelen);
wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + (size_t)modelen);
#endif
wmode = wname + namelen;
MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen);