Skip to content

Commit a5104dc

Browse files
committed
Add u32 as the first unsigned type
1 parent a9e25ba commit a5104dc

9 files changed

+3510
-860
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ add_library(subspace STATIC
6666
"num/__private/unsigned_integer_macros.h"
6767
"num/i32.h"
6868
"num/num_concepts.h"
69+
"num/u32.h"
6970
"option/__private/is_option_type.h"
7071
"option/option.h"
7172
"tuple/__private/storage.h"
@@ -108,6 +109,7 @@ add_executable(subspace_unittests
108109
"mem/swap_unittest.cc"
109110
"mem/take_unittest.cc"
110111
"num/i32_unittest.cc"
112+
"num/u32_unittest.cc"
111113
"option/option_unittest.cc"
112114
"option/option_types_unittest.cc"
113115
"tuple/tuple_unittest.cc"

num/__private/intrinsics.h

Lines changed: 174 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#pragma once
1616

17+
#include <stddef.h>
1718
#include <stdint.h>
1819

1920
#include <type_traits>
@@ -43,13 +44,19 @@ template <class T>
4344
requires(std::is_integral_v<T> && sizeof(T) <= 8)
4445
sus_always_inline constexpr auto high_bit() noexcept {
4546
if constexpr (sizeof(T) == 1)
46-
return T{0x80};
47+
return static_cast<T>(0x80);
4748
else if constexpr (sizeof(T) == 2)
48-
return T{0x8000};
49+
return static_cast<T>(0x8000);
4950
else if constexpr (sizeof(T) == 4)
50-
return T{0x80000000};
51+
return static_cast<T>(0x80000000);
5152
else
52-
return T{0x8000000000000000};
53+
return static_cast<T>(0x8000000000000000);
54+
}
55+
56+
template <class T>
57+
requires(std::is_integral_v<T> && !std::is_signed_v<T>)
58+
sus_always_inline constexpr auto max_value() noexcept {
59+
return ~T{0};
5360
}
5461

5562
template <class T>
@@ -65,10 +72,16 @@ sus_always_inline constexpr auto max_value() noexcept {
6572
return T{0x7fffffffffffffff};
6673
}
6774

75+
template <class T>
76+
requires(std::is_integral_v<T> && !std::is_signed_v<T>)
77+
sus_always_inline constexpr auto min_value() noexcept {
78+
return T{0};
79+
}
80+
6881
template <class T>
6982
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
7083
sus_always_inline constexpr auto min_value() noexcept {
71-
return -max_value<T>() - 1;
84+
return -max_value<T>() - T{1};
7285
}
7386

7487
template <class T>
@@ -344,6 +357,17 @@ sus_always_inline constexpr auto into_unsigned(T x) noexcept {
344357
return static_cast<uint64_t>(x);
345358
}
346359

360+
template <class T>
361+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 4)
362+
sus_always_inline constexpr auto into_widened(T x) noexcept {
363+
if constexpr (sizeof(x) == 1)
364+
return static_cast<uint16_t>(x);
365+
else if constexpr (sizeof(x) == 2)
366+
return static_cast<uint32_t>(x);
367+
else
368+
return static_cast<uint64_t>(x);
369+
}
370+
347371
template <class T>
348372
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 4)
349373
sus_always_inline constexpr auto into_widened(T x) noexcept {
@@ -381,11 +405,21 @@ sus_always_inline constexpr bool sign_bit(T x) noexcept {
381405
return x & (T(1) << 63) != 0;
382406
}
383407

408+
template <class T>
409+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
410+
sus_always_inline
411+
constexpr OverflowOut<T> add_with_overflow(T x, T y) noexcept {
412+
return OverflowOut<T>{
413+
.overflow = x > max_value<T>() - y,
414+
.value = x + y,
415+
};
416+
}
417+
384418
template <class T>
385419
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
386420
sus_always_inline
387421
constexpr OverflowOut<T> add_with_overflow(T x, T y) noexcept {
388-
auto out = into_signed(into_unsigned(x) + into_unsigned(y));
422+
const auto out = into_signed(into_unsigned(x) + into_unsigned(y));
389423
return OverflowOut<T>{
390424
.overflow = y >= 0 != out >= x,
391425
.value = out,
@@ -397,18 +431,28 @@ template <class T, class U = decltype(to_unsigned(std::declval<T>()))>
397431
sizeof(T) == sizeof(U))
398432
sus_always_inline
399433
constexpr OverflowOut<T> add_with_overflow_unsigned(T x, U y) noexcept {
400-
auto out = into_signed(into_unsigned(x) + y);
434+
const auto out = into_signed(into_unsigned(x) + y);
401435
return OverflowOut<T>{
402436
.overflow = static_cast<U>(max_value<T>()) - static_cast<U>(x) < y,
403437
.value = out,
404438
};
405439
}
406440

441+
template <class T>
442+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
443+
sus_always_inline
444+
constexpr OverflowOut<T> sub_with_overflow(T x, T y) noexcept {
445+
return OverflowOut<T>{
446+
.overflow = x < min_value<T>() + y,
447+
.value = x - y,
448+
};
449+
}
450+
407451
template <class T>
408452
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
409453
sus_always_inline
410454
constexpr OverflowOut<T> sub_with_overflow(T x, T y) noexcept {
411-
auto out = into_signed(into_unsigned(x) - into_unsigned(y));
455+
const auto out = into_signed(into_unsigned(x) - into_unsigned(y));
412456
return OverflowOut<T>{
413457
.overflow = y >= 0 != out <= x,
414458
.value = out,
@@ -420,47 +464,63 @@ template <class T, class U = decltype(to_unsigned(std::declval<T>()))>
420464
sizeof(T) == sizeof(U))
421465
sus_always_inline
422466
constexpr OverflowOut<T> sub_with_overflow_unsigned(T x, U y) noexcept {
423-
auto out = into_signed(into_unsigned(x) - y);
467+
const auto out = into_signed(into_unsigned(x) - y);
424468
return OverflowOut<T>{
425469
.overflow = static_cast<U>(x) - static_cast<U>(min_value<T>()) < y,
426470
.value = out,
427471
};
428472
}
429473

474+
template <class T>
475+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 4)
476+
sus_always_inline
477+
constexpr OverflowOut<T> mul_with_overflow(T x, T y) noexcept {
478+
// TODO: Can we use compiler intrinsics?
479+
auto out = into_widened(x) * into_widened(y);
480+
using Wide = decltype(out);
481+
return OverflowOut{.overflow = out > Wide{max_value<T>()}, .value = static_cast<T>(out)};
482+
}
483+
484+
template <class T>
485+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) == 8)
486+
sus_always_inline
487+
constexpr OverflowOut<T> mul_with_overflow(T x, T y) noexcept {
488+
// TODO: For GCC/Clang, use __uint128_t:
489+
// https://quuxplusone.github.io/blog/2019/02/28/is-int128-integral/
490+
// For MSVC, use _umul128, but what about constexpr?? If we can't do
491+
// it then make the whole function non-constexpr?
492+
// https://docs.microsoft.com/en-us/cpp/intrinsics/umul128
493+
static_assert(sizeof(T) != 8);
494+
return OverflowOut<T>(false, T(0));
495+
}
496+
430497
template <class T>
431498
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 4)
432499
sus_always_inline
433500
constexpr OverflowOut<T> mul_with_overflow(T x, T y) noexcept {
434-
constexpr auto max = max_value<T>();
435-
constexpr auto min = min_value<T>();
436-
// TODO: Optimize this. Use compiler intrinsics.
501+
// TODO: Can we use compiler intrinsics?
437502
auto out = into_widened(x) * into_widened(y);
438503
using Wide = decltype(out);
439-
if (out <= max && out >= min) [[likely]] {
440-
return OverflowOut{.overflow = false, .value = static_cast<T>(out)};
441-
} else {
442-
// TODO: Do we really need to loop here though, we just a signed modulo.
443-
while (out > Wide{max}) out -= Wide{max} - Wide{min} + 1;
444-
while (out < Wide{min}) out += Wide{max} - Wide{min} + 1;
445-
return OverflowOut<T>{.overflow = true, .value = static_cast<T>(out)};
446-
}
504+
return OverflowOut{
505+
.overflow = out > Wide{max_value<T>()} || out < Wide{min_value<T>()},
506+
.value = static_cast<T>(out)};
447507
}
448508

449509
template <class T>
450510
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) == 8)
451511
sus_always_inline
452512
constexpr OverflowOut<T> mul_with_overflow(T x, T y) noexcept {
453-
// TODO: For GCC/Clang, use __int128:
513+
// TODO: For GCC/Clang, use __int128_t:
454514
// https://quuxplusone.github.io/blog/2019/02/28/is-int128-integral/
455-
// For MSVC, use _mult128, but what about constexpr?? If we can't do
515+
// For MSVC, use _mul128, but what about constexpr?? If we can't do
456516
// it then make the whole function non-constexpr?
457-
// https://docs.microsoft.com/en-us/cpp/intrinsics/mul128?view=msvc-170
517+
// https://docs.microsoft.com/en-us/cpp/intrinsics/mul128
458518
static_assert(sizeof(T) != 8);
459519
return OverflowOut<T>(false, T(0));
460520
}
461521

462522
template <class T>
463-
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
523+
requires(std::is_integral_v<T> && sizeof(T) <= 8)
464524
sus_always_inline
465525
constexpr OverflowOut<T> pow_with_overflow(T base, uint32_t exp) noexcept {
466526
if (exp == 0) return OverflowOut<T>{.overflow = false, .value = T{1}};
@@ -481,6 +541,21 @@ sus_always_inline
481541
return OverflowOut<T>{.overflow = overflow || r.overflow, .value = r.value};
482542
}
483543

544+
template <class T>
545+
requires(std::is_integral_v<T> && !std::is_signed_v<T> &&
546+
(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
547+
sizeof(T) == 8))
548+
sus_always_inline
549+
constexpr OverflowOut<T> shl_with_overflow(T x, uint32_t shift) noexcept {
550+
// Using `num_bits<T>() - 1` as a mask only works if num_bits<T>() is a power
551+
// of two, so we verify that sizeof(T) is a power of 2, which implies the
552+
// number of bits is as well (since each byte is 2^3 bits).
553+
const bool overflow = shift >= num_bits<T>();
554+
if (overflow) [[unlikely]]
555+
shift = shift & (num_bits<T>() - 1);
556+
return OverflowOut<T>{.overflow = overflow, .value = x << shift};
557+
}
558+
484559
template <class T>
485560
requires(std::is_integral_v<T> && std::is_signed_v<T> &&
486561
(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
@@ -497,6 +572,21 @@ sus_always_inline
497572
.value = into_signed(into_unsigned(x) << shift)};
498573
}
499574

575+
template <class T>
576+
requires(std::is_integral_v<T> && !std::is_signed_v<T> &&
577+
(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
578+
sizeof(T) == 8))
579+
sus_always_inline
580+
constexpr OverflowOut<T> shr_with_overflow(T x, uint32_t shift) noexcept {
581+
// Using `num_bits<T>() - 1` as a mask only works if num_bits<T>() is a power
582+
// of two, so we verify that sizeof(T) is a power of 2, which implies the
583+
// number of bits is as well (since each byte is 2^3 bits).
584+
const bool overflow = shift >= num_bits<T>();
585+
if (overflow) [[unlikely]]
586+
shift = shift & (num_bits<T>() - 1);
587+
return OverflowOut<T>{.overflow = overflow, .value = x >> shift};
588+
}
589+
500590
template <class T>
501591
requires(std::is_integral_v<T> && std::is_signed_v<T> &&
502592
(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 ||
@@ -513,6 +603,17 @@ sus_always_inline
513603
.value = into_signed(into_unsigned(x) >> shift)};
514604
}
515605

606+
template <class T>
607+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
608+
sus_always_inline constexpr T saturating_add(T x, T y) noexcept {
609+
// TODO: Optimize this? Use intrinsics?
610+
const auto out = add_with_overflow(x, y);
611+
if (!out.overflow) [[likely]]
612+
return out.value;
613+
else
614+
return max_value<T>();
615+
}
616+
516617
template <class T>
517618
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
518619
sus_always_inline constexpr T saturating_add(T x, T y) noexcept {
@@ -530,6 +631,17 @@ sus_always_inline constexpr T saturating_add(T x, T y) noexcept {
530631
}
531632
}
532633

634+
template <class T>
635+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
636+
sus_always_inline constexpr T saturating_sub(T x, T y) noexcept {
637+
// TODO: Optimize this? Use intrinsics?
638+
const auto out = sub_with_overflow(x, y);
639+
if (!out.overflow) [[likely]]
640+
return out.value;
641+
else
642+
return min_value<T>();
643+
}
644+
533645
template <class T>
534646
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
535647
sus_always_inline constexpr T saturating_sub(T x, T y) noexcept {
@@ -547,11 +659,22 @@ sus_always_inline constexpr T saturating_sub(T x, T y) noexcept {
547659
}
548660
}
549661

662+
template <class T>
663+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
664+
sus_always_inline constexpr T saturating_mul(T x, T y) noexcept {
665+
// TODO: Optimize this? Use intrinsics?
666+
const auto out = mul_with_overflow(x, y);
667+
if (!out.overflow) [[likely]]
668+
return out.value;
669+
else
670+
return max_value<T>();
671+
}
672+
550673
template <class T>
551674
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
552675
sus_always_inline constexpr T saturating_mul(T x, T y) noexcept {
553676
// TODO: Optimize this? Use intrinsics?
554-
auto out = mul_with_overflow(x, y);
677+
const auto out = mul_with_overflow(x, y);
555678
if (!out.overflow) [[likely]]
556679
return out.value;
557680
else if (x > 0 == y > 0)
@@ -560,27 +683,53 @@ sus_always_inline constexpr T saturating_mul(T x, T y) noexcept {
560683
return min_value<T>();
561684
}
562685

686+
template <class T>
687+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
688+
sus_always_inline constexpr T wrapping_add(T x, T y) noexcept {
689+
return x + y;
690+
}
691+
563692
template <class T>
564693
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
565694
sus_always_inline constexpr T wrapping_add(T x, T y) noexcept {
566695
// TODO: Are there cheaper intrinsics?
567696
return add_with_overflow(x, y).value;
568697
}
569698

699+
template <class T>
700+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
701+
sus_always_inline constexpr T wrapping_sub(T x, T y) noexcept {
702+
return x - y;
703+
}
704+
570705
template <class T>
571706
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
572707
sus_always_inline constexpr T wrapping_sub(T x, T y) noexcept {
573708
// TODO: Are there cheaper intrinsics?
574709
return sub_with_overflow(x, y).value;
575710
}
576711

712+
template <class T>
713+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
714+
sus_always_inline constexpr T wrapping_mul(T x, T y) noexcept {
715+
return x * y;
716+
}
717+
577718
template <class T>
578719
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
579720
sus_always_inline constexpr T wrapping_mul(T x, T y) noexcept {
580721
// TODO: Are there cheaper intrinsics?
581722
return mul_with_overflow(x, y).value;
582723
}
583724

725+
template <class T>
726+
requires(std::is_integral_v<T> && !std::is_signed_v<T> && sizeof(T) <= 8)
727+
sus_always_inline constexpr T wrapping_pow(T base, uint32_t exp) noexcept {
728+
// TODO: Don't need to track overflow and unsigned wraps by default, so this
729+
// can be cheaper.
730+
return pow_with_overflow(base, exp).value;
731+
}
732+
584733
template <class T>
585734
requires(std::is_integral_v<T> && std::is_signed_v<T> && sizeof(T) <= 8)
586735
sus_always_inline constexpr T wrapping_pow(T base, uint32_t exp) noexcept {

0 commit comments

Comments
 (0)