19 Commits

Author SHA1 Message Date
9f792e536b Implement Bazel Clang toolchain 2025-07-12 17:11:54 +02:00
00ea14788f Fix a bunch of warnings 2025-07-06 17:05:36 +02:00
bcdad5b876 Add numbers parsing 2025-07-04 20:44:04 +02:00
cca2e26724 Add integer traits 2025-07-04 18:22:03 +02:00
195f20ff17 Add SparseHandlePool 2025-07-03 18:35:25 +02:00
a78e24e9eb Add DenseHandlePool 2025-07-03 18:34:45 +02:00
43ab95880d Add pop method to chunked_buffer 2025-06-26 18:25:00 +02:00
47c6677405 Add IndexPool 2025-06-26 12:37:02 +02:00
92f908ee1b Add index_pool_handle 2025-06-18 00:05:33 +02:00
30237bb78f Add index_pool_config 2025-06-18 00:05:33 +02:00
8f59f113e8 Fix test execution after a fail 2025-06-18 00:05:33 +02:00
afb237c513 Add smallest_unsigned_integer_type_for_width 2025-06-14 12:56:11 +02:00
19e2164441 Use the new Bazel registry 2025-06-04 21:59:48 +02:00
b8a87223bb Add compile-time configuration for build settings 2025-05-26 22:38:04 +02:00
a1db1cd9e2 Implement chunked_buffer 2025-05-26 00:48:06 +02:00
54b95b1662 Buffer type has to be moveable, always 2025-05-17 23:01:51 +02:00
e7e7023340 Fix build on Linux 2025-05-16 22:59:09 +02:00
f7a2699ac0 Add and use copy_const_t 2025-05-14 00:41:51 +02:00
088e03708a Add array 2025-05-14 00:41:51 +02:00
66 changed files with 8704 additions and 191 deletions

View File

@ -1,18 +1,14 @@
common --registry=https://bcr.bazel.build/
common --registry=https://bazel.stevenlr.com/registry/
startup --windows_enable_symlinks
build:windows --enable_runfiles=true
build --enable_platform_specific_config
build --build_python_zip=false
build:windows --extra_execution_platforms=//:x64_windows-clang-cl
build:windows --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl
build:linux --repo_env=CC=clang
build:windows --cxxopt=-Xclang=-std=c++23
build:linux --cxxopt=-std=c++23
build --cxxopt=-Wall
build --features=c++23
build --cxxopt=-Weverything
build --cxxopt=-Wno-c++98-compat
build --cxxopt=-Wno-c++98-compat-pedantic
build --cxxopt=-Wno-pre-c++17-compat
@ -25,21 +21,7 @@ build --cxxopt=-Wno-global-constructors
build --cxxopt=-Wno-unsafe-buffer-usage
build --cxxopt=-Wno-covered-switch-default
build --cxxopt=-Wno-unused-command-line-argument
build:windows_san --config=windows
build:windows_san --copt=-fno-sanitize-ignorelist
build:windows_san --copt=-fsanitize=address
build:windows_san --copt=-fsanitize=undefined
build:windows_san --copt=-fno-sanitize-recover=all
build:windows_san --linkopt=clang_rt.asan_dynamic-x86_64.lib
build:windows_san --linkopt=clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
build:linux_san --config=linux
build:linux_san --copt=-fsanitize=address
build:linux_san --linkopt=-fsanitize=address
build:linux_san --copt=-fsanitize=undefined
build:linux_san --copt=-fno-sanitize-recover=all
build:linux_san --linkopt=-fsanitize=undefined
build:linux_san --linkopt=-fsanitize-link-c++-runtime
build --cxxopt=-Wno-padded
build --cxxopt=-Wno-weak-vtables
test --test_output=errors

View File

@ -21,3 +21,4 @@ Checks:
- "-cppcoreguidelines-pro-type-union-access"
- "-*-copy-assignment-signature"
- "-*-unconventional-assign-operator"
- "-readability-math-missing-parentheses"

View File

@ -13,14 +13,3 @@ license(
package_name = "ASL",
)
platform(
name = "x64_windows-clang-cl",
constraint_values = [
"@platforms//cpu:x86_64",
"@platforms//os:windows",
# @Todo(bazel) Bit weird to use a private thing.
# We used to use @bazel_tools//tools/cpp:clang-cl but it's deprecated
# in favor of... this?...
"@rules_cc//cc/private/toolchain:clang-cl",
],
)

View File

@ -8,16 +8,18 @@ bazel_dep(name = "platforms", version = "0.0.11")
bazel_dep(name = "rules_license", version = "1.0.0")
bazel_dep(name = "rules_cc", version = "0.1.1")
cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension")
use_repo(cc_configure, "local_config_cc")
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
git_override(
module_name = "hedron_compile_commands",
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
commit = "4f28899228fb3ad0126897876f147ca15026151e",
)
bazel_dep(name = "hedron_compile_commands", version = "0.1.0", dev_dependency = True)
bazel_dep(name = "rules_python", version = "1.3.0")
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(python_version = "3.13", is_default = True)
clang_toolchain = use_extension("//bazel/clang_toolchain:config_detection.bzl", "clang_toolchain")
use_repo(clang_toolchain, "clang_toolchain")
register_toolchains(
"@clang_toolchain//:windows_x86_64_toolchain_def",
"@clang_toolchain//:linux_x86_64_toolchain_def",
"@clang_toolchain//:linux_aarch64_toolchain_def",
)

34
MODULE.bazel.lock generated
View File

@ -1,6 +1,9 @@
{
"lockFileVersion": 18,
"registryFileHashes": {
"https://bazel.stevenlr.com/registry/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://bazel.stevenlr.com/registry/modules/hedron_compile_commands/0.1.0/MODULE.bazel": "5623ba8f732a01246c388bccebf924357e452314a178f179d3b375b623d5a359",
"https://bazel.stevenlr.com/registry/modules/hedron_compile_commands/0.1.0/source.json": "c55f6caa3eb9fb027af66949c23ca537214eb32b0316ae95bcc496f3cd8406b9",
"https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
@ -17,7 +20,8 @@
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b",
"https://bcr.bazel.build/modules/bazel_features/1.21.0/source.json": "3e8379efaaef53ce35b7b8ba419df829315a880cb0a030e5bb45c96d6d5ecb5f",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
@ -39,6 +43,7 @@
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4",
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
"https://bcr.bazel.build/modules/hedron_compile_commands/0.1.0/MODULE.bazel": "not found",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d",
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
@ -70,7 +75,6 @@
"https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
"https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a",
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
@ -90,8 +94,8 @@
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
"https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
"https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad",
"https://bcr.bazel.build/modules/rules_java/8.11.0/source.json": "302b52a39259a85aa06ca3addb9787864ca3e03b432a5f964ea68244397e7544",
"https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761",
"https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba",
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
@ -136,15 +140,31 @@
"https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216",
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
},
"selectedYankedVersions": {},
"moduleExtensions": {
"//bazel/clang_toolchain:config_detection.bzl%clang_toolchain": {
"general": {
"bzlTransitiveDigest": "xXTcNvgp9bMnw/1ZKi1vOOoz139EHr8Hw3Ugd9+eos0=",
"usagesDigest": "CRr/eSLZQiKHqCq8MWfzhslrj4Mqar1310aEoa4BzJw=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"clang_toolchain": {
"repoRuleId": "@@//bazel/clang_toolchain:config_detection.bzl%_config_detection",
"attributes": {}
}
},
"recordedRepoMappingEntries": []
}
},
"@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
"general": {
"bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=",
"bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=",
"usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},

17
asl/BUILD.bazel Normal file
View File

@ -0,0 +1,17 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
config_setting(
name = "debug",
values = {
"compilation_mode": "dbg",
},
)
config_setting(
name = "optimized",
values = {
"compilation_mode": "opt",
},
)

View File

@ -24,6 +24,13 @@ cc_library(
srcs = [
"assert.cpp",
],
defines = select({
"//asl:debug": ["ASL_DEBUG=1"],
"//conditions:default": ["ASL_DEBUG=0"],
}) + select({
"//asl:optimized": ["ASL_OPTIMIZED=1"],
"//conditions:default": ["ASL_OPTIMIZED=0"],
}),
visibility = ["//visibility:public"],
)

View File

@ -6,8 +6,8 @@
#include "asl/base/config.hpp"
#if ASL_COMPILER_CLANG_CL
#if defined(ASL_COMPILER_CLANG_CL)
#define ASL_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#elif ASL_COMPILER_CLANG
#elif defined(ASL_COMPILER_CLANG)
#define ASL_NO_UNIQUE_ADDRESS [[no_unique_address]]
#endif

View File

@ -18,21 +18,23 @@ void report_assert_failure(const char* msg, const source_location& sl = source_l
} // namespace asl
#if ASL_COMPILER_CLANG_CL
#if defined(ASL_COMPILER_CLANG_CL)
#define ASL_DEBUG_BREAK() __debugbreak()
#elif ASL_COMPILER_CLANG
#elif defined(ASL_COMPILER_CLANG)
#define ASL_DEBUG_BREAK() __builtin_debugtrap()
#endif
// @Todo Configure asserts at build time
#define ASL_ASSERT(...) \
if (__VA_ARGS__) {} \
else \
{ \
::asl::report_assert_failure(#__VA_ARGS__); \
ASL_DEBUG_BREAK(); \
}
#if !ASL_OPTIMIZED
#define ASL_ASSERT(...) \
if (__VA_ARGS__) {} \
else \
{ \
::asl::report_assert_failure(#__VA_ARGS__); \
ASL_DEBUG_BREAK(); \
}
#else
#define ASL_ASSERT(...)
#endif
#define ASL_ASSERT_RELEASE(...) \
if (__VA_ARGS__) {} \

View File

@ -114,7 +114,7 @@ constexpr T rotl(T v, int s) // NOLINT(*-no-recursion)
{
static constexpr int N = sizeof(decltype(v)) * 8;
s = s % N;
return (s >= 0) ? (v << s) | (v >> (N - s)) : rotr(v, -s);
return (s >= 0) ? (v << s) | (v >> ((N - s) % N)) : rotr(v, -s);
}
template<is_unsigned_integer T>
@ -122,7 +122,7 @@ constexpr T rotr(T v, int s) // NOLINT(*-no-recursion)
{
static constexpr int N = sizeof(decltype(v)) * 8;
s = s % N;
return (s >= 0) ? (v >> s) | (v << (N - s)) : rotl(v, -s);
return (s >= 0) ? (v >> s) | (v << ((N - s) % N)) : rotl(v, -s);
}
constexpr uint16_t byteswap(uint16_t v)

View File

@ -11,10 +11,15 @@ ASL_TEST(has_single_bit)
ASL_TEST_EXPECT(asl::has_single_bit(4U));
ASL_TEST_EXPECT(asl::has_single_bit(1024U));
ASL_TEST_EXPECT(asl::has_single_bit(0x8000'0000U));
ASL_TEST_EXPECT(asl::has_single_bit(0x0000'8000'0000'0000ULL));
ASL_TEST_EXPECT(asl::has_single_bit(uint64_t{0x0000'8000'0000'0000ULL}));
ASL_TEST_EXPECT(!asl::has_single_bit(0U));
ASL_TEST_EXPECT(!asl::has_single_bit(3U));
ASL_TEST_EXPECT(!asl::has_single_bit(341U));
int k = 0x7fffffff;
k += 1;
int *array = new int[100];
delete [] array;
ASL_TEST_EXPECT(array[1] == 0); // BOOM
}
ASL_TEST(popcount) // NOLINT(*-cognitive-complexity)
@ -39,6 +44,10 @@ ASL_TEST(popcount) // NOLINT(*-cognitive-complexity)
ASL_TEST(countr_zero)
{
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0}) == 8);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{1}) == 0);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{2}) == 1);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{4}) == 2);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{8}) == 3);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{255}) == 0);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b00011100}) == 2);
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b10101010}) == 1);

View File

@ -22,5 +22,15 @@
#error Unknown compiler
#endif
// ASL_DEBUG=1 for slow builds, with extra validation logic and such.
#if !defined(ASL_DEBUG)
#error ASL_DEBUG should be defined to 0 or 1
#endif
// ASL_OPTIMIZED=1 for fast builds, with minimal validation logic.
#if !defined(ASL_OPTIMIZED)
#error ASL_OPTIMIZED should be defined to 0 or 1
#endif
// NOLINTEND(*-macro-to-enum)

View File

@ -12,9 +12,36 @@ using float64_t = double;
namespace asl
{
template<is_floating_point T> constexpr T infinity() { return __builtin_inf(); }
template<typename T> struct float_traits {};
template<is_floating_point T> constexpr T nan() { return static_cast<T>(__builtin_nanf("")); }
#define ASL_FLOAT_TRAITS(T, INF, NAN, EPS, SMALLEST) \
template<> struct float_traits<T> \
{ \
static constexpr T kInfinity{__builtin_bit_cast(T, INF)}; \
static constexpr T kNaN{__builtin_bit_cast(T, NAN)}; \
static constexpr T kEpsilon{EPS}; \
static constexpr T kSmallest{__builtin_bit_cast(T, SMALLEST)}; \
};
ASL_FLOAT_TRAITS(
float32_t,
0x7F800000,
0x7FC00000,
__builtin_bit_cast(float32_t, 0x3F800001) - float32_t{1},
0x00800000
);
ASL_FLOAT_TRAITS(
float64_t,
0x7FF0000000000000,
0x7FF8000000000000,
__builtin_bit_cast(float64_t, 0x3FF0000000000001) - float64_t{1},
0x0010000000000000
);
template<is_floating_point T> constexpr T infinity() { return float_traits<T>::kInfinity; }
template<is_floating_point T> constexpr T nan() { return float_traits<T>::kNaN; }
template<is_floating_point T> constexpr bool is_infinity(T f) { return __builtin_isinf(f); }

View File

@ -9,18 +9,18 @@
using int8_t = signed char;
using int16_t = signed short;
using int32_t = signed int;
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
using int64_t = signed long long;
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
using int64_t = signed long;
#endif
using uint8_t = unsigned char;
using uint16_t = unsigned short;
using uint32_t = unsigned int;
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
using uint64_t = unsigned long long;
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
using uint64_t = unsigned long;
#endif
@ -40,5 +40,25 @@ namespace asl
enum class byte : uint8_t {};
template<typename T> struct integer_traits {};
#define ASL_INTEGER_TRAITS(T, MIN, MAX) \
template<> struct integer_traits<T> \
{ \
static constexpr T kMin{static_cast<T>(MIN)}; \
static constexpr T kMax{static_cast<T>(MAX)}; \
}
ASL_INTEGER_TRAITS(uint8_t, 0, 0xff);
ASL_INTEGER_TRAITS(uint16_t, 0, 0xffff);
ASL_INTEGER_TRAITS(uint32_t, 0, 0xffff'ffff);
ASL_INTEGER_TRAITS(uint64_t, 0, 0xffff'ffff'ffff'ffff);
ASL_INTEGER_TRAITS(int8_t, -0x80, 0x7f);
ASL_INTEGER_TRAITS(int16_t, -0x8000, 0x7fff);
ASL_INTEGER_TRAITS(int32_t, -0x8000'0000, 0x7fff'ffff);
ASL_INTEGER_TRAITS(int64_t, -0x8000'0000'0000'0000, 0x7fff'ffff'ffff'ffff);
#undef ASL_INTEGER_TRAITS
} // namespace asl

View File

@ -146,6 +146,8 @@ struct _copy_const_helper<From, To, true> { using type = const To; };
template<typename From, typename To> using copy_cref_t =
_copy_ref_helper<From, typename _copy_const_helper<From, un_cvref_t<To>>::type>::type;
template<typename From, typename To> using copy_const_t = _copy_const_helper<From, un_cvref_t<To>>::type;
template<typename T> struct _is_ptr_helper : false_type {};
template<typename T> struct _is_ptr_helper<T*> : true_type {};
@ -247,28 +249,32 @@ template<> struct _integer_traits<uint8_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int8_t;
using as_signed = int8_t;
using as_unsigned = uint8_t;
};
template<> struct _integer_traits<uint16_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int16_t;
using as_signed = int16_t;
using as_unsigned = uint16_t;
};
template<> struct _integer_traits<uint32_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int32_t;
using as_signed = int32_t;
using as_unsigned = uint32_t;
};
template<> struct _integer_traits<uint64_t>
{
static constexpr bool kSigned = false;
static constexpr bool kUnsigned = true;
using as_signed = int64_t;
using as_signed = int64_t;
using as_unsigned = uint64_t;
};
template<> struct _integer_traits<int8_t>
@ -276,6 +282,7 @@ template<> struct _integer_traits<int8_t>
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint8_t;
using as_signed = int8_t;
};
template<> struct _integer_traits<int16_t>
@ -283,6 +290,7 @@ template<> struct _integer_traits<int16_t>
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint16_t;
using as_signed = int16_t;
};
template<> struct _integer_traits<int32_t>
@ -290,6 +298,7 @@ template<> struct _integer_traits<int32_t>
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint32_t;
using as_signed = int32_t;
};
template<> struct _integer_traits<int64_t>
@ -297,6 +306,7 @@ template<> struct _integer_traits<int64_t>
static constexpr bool kSigned = true;
static constexpr bool kUnsigned = false;
using as_unsigned = uint64_t;
using as_signed = int64_t;
};
template<typename T> concept is_signed_integer = _integer_traits<T>::kSigned;
@ -304,18 +314,36 @@ template<typename T> concept is_unsigned_integer = _integer_traits<T>::kUnsigned
template<typename T> concept is_integer = is_signed_integer<T> || is_unsigned_integer<T>;
template<is_signed_integer T> using as_unsigned_integer = _integer_traits<T>::as_unsigned;
template<is_unsigned_integer T> using as_signed_integer = _integer_traits<T>::as_signed;
template<is_integer T> using as_unsigned_integer = _integer_traits<T>::as_unsigned;
template<is_integer T> using as_signed_integer = _integer_traits<T>::as_signed;
template<int N>
struct smallest_unsigned_integer_type_for_width_helper { using type = void; };
template<int N> requires (N >= 1 and N <= 8)
struct smallest_unsigned_integer_type_for_width_helper<N> { using type = uint8_t; };
template<int N> requires (N >= 9 and N <= 16)
struct smallest_unsigned_integer_type_for_width_helper<N> { using type = uint16_t; };
template<int N> requires (N >= 17 and N <= 32)
struct smallest_unsigned_integer_type_for_width_helper<N> { using type = uint32_t; };
template<int N> requires (N >= 33 and N <= 64)
struct smallest_unsigned_integer_type_for_width_helper<N> { using type = uint64_t; };
template<int N> using smallest_unsigned_integer_type_for_width
= smallest_unsigned_integer_type_for_width_helper<N>::type;
template<typename T> concept is_enum = __is_enum(T);
template<is_enum T> using underlying_t = __underlying_type(T);
template<typename T> struct is_uniquely_represented : false_type {};
template<is_integer T> struct is_uniquely_represented<T> : true_type {};
template<is_enum T> struct is_uniquely_represented<T> : true_type {};
template<typename T> struct is_uniquely_represented : false_type {};
template<is_integer T> struct is_uniquely_represented<T> : true_type {};
template<is_enum T> struct is_uniquely_represented<T> : true_type {};
template<> struct is_uniquely_represented<uint128_t> : true_type {};
template<> struct is_uniquely_represented<byte> : true_type {};
template<> struct is_uniquely_represented<byte> : true_type {};
template<typename T> concept uniquely_represented = is_uniquely_represented<un_cv_t<T>>::value;

View File

@ -274,6 +274,7 @@ static_assert(asl::same_or_derived_from<int, int>);
static_assert(!asl::is_const<int>);
static_assert(asl::is_const<const int>);
static_assert(!asl::is_const<const int*>);
static_assert(!asl::is_const<const int&>);
static_assert(asl::is_const<int* const>);
static_assert(asl::is_floating_point<float>);
@ -362,6 +363,15 @@ static_assert(asl::same_as<asl::copy_cref_t<const int&, const float>, const floa
static_assert(asl::same_as<asl::copy_cref_t<const int&, float&&>, const float&>);
static_assert(asl::same_as<asl::copy_cref_t<const int&, const float&>, const float&>);
static_assert(asl::same_as<asl::copy_const_t<int, float>, float>);
static_assert(asl::same_as<asl::copy_const_t<int, const float>, float>);
static_assert(asl::same_as<asl::copy_const_t<const int, float>, const float>);
static_assert(asl::same_as<asl::copy_const_t<const int, const float>, const float>);
static_assert(asl::same_as<asl::copy_const_t<const int*, float>, float>);
static_assert(asl::same_as<asl::copy_const_t<int* const, float>, const float>);
static_assert(asl::same_as<asl::decay_t<int>, int>);
static_assert(!asl::same_as<asl::decay_t<int>, float>);
static_assert(asl::same_as<asl::decay_t<int&>, int>);
@ -379,3 +389,39 @@ enum EnumI64 : int64_t {};
static_assert(asl::same_as<asl::underlying_t<EnumU8>, uint8_t>);
static_assert(asl::same_as<asl::underlying_t<EnumI64>, int64_t>);
static_assert(!asl::is_integer<EnumU8>);
static_assert(!asl::is_integer<EnumI64>);
static_assert(asl::same_as<asl::as_unsigned_integer<uint8_t>, uint8_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<uint16_t>, uint16_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<uint32_t>, uint32_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<uint64_t>, uint64_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<int8_t>, uint8_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<int16_t>, uint16_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<int32_t>, uint32_t>);
static_assert(asl::same_as<asl::as_unsigned_integer<int64_t>, uint64_t>);
static_assert(asl::same_as<asl::as_signed_integer<uint8_t>, int8_t>);
static_assert(asl::same_as<asl::as_signed_integer<uint16_t>, int16_t>);
static_assert(asl::same_as<asl::as_signed_integer<uint32_t>, int32_t>);
static_assert(asl::same_as<asl::as_signed_integer<uint64_t>, int64_t>);
static_assert(asl::same_as<asl::as_signed_integer<int8_t>, int8_t>);
static_assert(asl::same_as<asl::as_signed_integer<int16_t>, int16_t>);
static_assert(asl::same_as<asl::as_signed_integer<int32_t>, int32_t>);
static_assert(asl::same_as<asl::as_signed_integer<int64_t>, int64_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<1>, uint8_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<2>, uint8_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<4>, uint8_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<8>, uint8_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<12>, uint16_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<16>, uint16_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<20>, uint32_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<30>, uint32_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<31>, uint32_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<32>, uint32_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<50>, uint64_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<63>, uint64_t>);
static_assert(asl::same_as<asl::smallest_unsigned_integer_type_for_width<64>, uint64_t>);

View File

@ -5,8 +5,10 @@
#pragma once
#include "asl/base/integers.hpp"
#include "asl/base/float.hpp"
#include "asl/base/bit.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/assert.hpp"
namespace asl
{
@ -14,10 +16,24 @@ namespace asl
template<is_integer T>
constexpr bool is_pow2(T x)
{
using unsigned_type = select_t<is_unsigned_integer<T>, T, as_unsigned_integer<T>>;
using unsigned_type = as_unsigned_integer<T>;
return x > 0 && has_single_bit(static_cast<unsigned_type>(x));
}
template<is_integer T>
constexpr T round_down_pow2(T x, T div)
{
ASL_ASSERT(is_pow2(div));
return x & (-div);
}
template<is_integer T>
constexpr T round_up_pow2(T x, T div)
{
ASL_ASSERT(is_pow2(div));
return (x + (div - 1)) & (-div);
}
template<typename T>
concept is_numeric = is_integer<T> || is_floating_point<T>;
@ -39,5 +55,47 @@ constexpr T clamp(T x, T a, T b)
return min(max(x, a), b);
}
constexpr float32_t abs(float32_t x) { return __builtin_fabsf(x); }
constexpr float64_t abs(float64_t x) { return __builtin_fabs(x); }
template<is_floating_point T>
bool are_nearly_equal(T a, T b)
{
// This is a fast path for identical values and correctly handles
// the case where +0.0 == -0.0.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wfloat-equal"
if (a == b) { return true; }
#pragma clang diagnostic pop
// NaNs are never equal to anything, including themselves.
if (is_nan(a) || is_nan(b)) { return false; }
// Infinities are only equal if they are identical (which is handled by the
// `a == b` check above). If one is infinity and the other is not, they
// are not equal. The relative comparison below would fail with infinities.
if (is_infinity(a) || is_infinity(b)) { return false; }
static constexpr T kMin = float_traits<T>::kSmallest;
static constexpr T kEps = float_traits<T>::kEpsilon;
const T abs_a = abs(a);
const T abs_b = abs(b);
const T abs_diff = abs(a - b);
// The relative error comparison (`|a-b| <= ε * max(|a|, |b|)`) breaks
// down when `a` and `b` are near zero. In this case, we switch to an
// absolute error comparison. `DBL_MIN` is the smallest positive
// normalized double, so it's a good threshold for this check.
if (abs_a < kMin || abs_b < kMin)
{
return abs_diff < kEps;
}
// For all other cases, we use the standard relative error formula.
// The error is scaled by the magnitude of the numbers.
return abs_diff <= kEps * max(abs_a, abs_b);
}
} // namespace asl

View File

@ -13,4 +13,56 @@ ASL_TEST(is_pow2)
ASL_TEST_EXPECT(!asl::is_pow2(6));
ASL_TEST_EXPECT(!asl::is_pow2(1978));
ASL_TEST_EXPECT(!asl::is_pow2(0));
ASL_TEST_EXPECT(asl::is_pow2(4U));
ASL_TEST_EXPECT(asl::is_pow2(uint64_t{65536}));
}
ASL_TEST(round_down_pow2) // NOLINT
{
ASL_TEST_EXPECT(asl::round_down_pow2(0, 1) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(1, 1) == 1);
ASL_TEST_EXPECT(asl::round_down_pow2(2, 1) == 2);
ASL_TEST_EXPECT(asl::round_down_pow2(3, 1) == 3);
ASL_TEST_EXPECT(asl::round_down_pow2(-1, 1) == -1);
ASL_TEST_EXPECT(asl::round_down_pow2(-2, 1) == -2);
ASL_TEST_EXPECT(asl::round_down_pow2(-3, 1) == -3);
ASL_TEST_EXPECT(asl::round_down_pow2(0U, 1U) == 0U);
ASL_TEST_EXPECT(asl::round_down_pow2(1U, 1U) == 1U);
ASL_TEST_EXPECT(asl::round_down_pow2(2U, 1U) == 2U);
ASL_TEST_EXPECT(asl::round_down_pow2(3U, 1U) == 3U);
ASL_TEST_EXPECT(asl::round_down_pow2(0, 16) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(1, 16) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(8, 16) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(15, 16) == 0);
ASL_TEST_EXPECT(asl::round_down_pow2(16, 16) == 16);
ASL_TEST_EXPECT(asl::round_down_pow2(17, 16) == 16);
ASL_TEST_EXPECT(asl::round_down_pow2(255, 16) == 240);
ASL_TEST_EXPECT(asl::round_down_pow2(-255, 16) == -256);
}
ASL_TEST(round_up_pow2) // NOLINT
{
ASL_TEST_EXPECT(asl::round_up_pow2(0, 1) == 0);
ASL_TEST_EXPECT(asl::round_up_pow2(1, 1) == 1);
ASL_TEST_EXPECT(asl::round_up_pow2(2, 1) == 2);
ASL_TEST_EXPECT(asl::round_up_pow2(3, 1) == 3);
ASL_TEST_EXPECT(asl::round_up_pow2(-1, 1) == -1);
ASL_TEST_EXPECT(asl::round_up_pow2(-2, 1) == -2);
ASL_TEST_EXPECT(asl::round_up_pow2(-3, 1) == -3);
ASL_TEST_EXPECT(asl::round_up_pow2(0U, 1U) == 0U);
ASL_TEST_EXPECT(asl::round_up_pow2(1U, 1U) == 1U);
ASL_TEST_EXPECT(asl::round_up_pow2(2U, 1U) == 2U);
ASL_TEST_EXPECT(asl::round_up_pow2(3U, 1U) == 3U);
ASL_TEST_EXPECT(asl::round_up_pow2(0, 16) == 0);
ASL_TEST_EXPECT(asl::round_up_pow2(1, 16) == 16);
ASL_TEST_EXPECT(asl::round_up_pow2(8, 16) == 16);
ASL_TEST_EXPECT(asl::round_up_pow2(15, 16) == 16);
ASL_TEST_EXPECT(asl::round_up_pow2(16, 16) == 16);
ASL_TEST_EXPECT(asl::round_up_pow2(17, 16) == 32);
ASL_TEST_EXPECT(asl::round_up_pow2(255, 16) == 256);
ASL_TEST_EXPECT(asl::round_up_pow2(-255, 16) == -240);
}

View File

@ -21,6 +21,22 @@ cc_library(
visibility = ["//visibility:public"],
)
cc_library(
name = "chunked_buffer",
hdrs = [
"chunked_buffer.hpp",
],
deps = [
"//asl/memory",
"//asl/memory:allocator",
"//asl/base",
"//asl/containers:buffer",
"//asl/types:array",
"//asl/types:maybe_uninit",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "hash_set",
hdrs = [
@ -75,6 +91,7 @@ cc_library(
],
) for name in [
"buffer",
"chunked_buffer",
"hash_map",
"hash_set",
"intrusive_list",

View File

@ -16,7 +16,8 @@
namespace asl
{
template<is_object T, allocator Allocator = DefaultAllocator>
template<typename T, allocator Allocator = DefaultAllocator>
requires is_object<T> && moveable<T>
class buffer
{
T* m_data{};
@ -90,7 +91,7 @@ private:
return is_on_heap(load_size_encoded());
}
constexpr T* push_uninit()
constexpr void* push_uninit()
{
const isize_t sz = size();
resize_uninit_inner(sz + 1);
@ -99,10 +100,13 @@ private:
constexpr void resize_uninit_inner(isize_t new_size)
{
const isize_t old_size = size();
if (!trivially_destructible<T> && new_size < old_size)
if constexpr (!trivially_destructible<T>)
{
destroy_n(data() + new_size, old_size - new_size);
const isize_t old_size = size();
if (new_size < old_size)
{
destroy_n(data() + new_size, old_size - new_size);
}
}
reserve_capacity(new_size);
set_size(new_size);
@ -135,18 +139,26 @@ private:
{
if (other.is_on_heap())
{
// If the other in on heap, destroy here and adopt their
// data. We'll soon adopt the allocator as well.
destroy();
m_data = other.m_data;
m_capacity = other.m_capacity;
store_size_encoded(other.load_size_encoded());
}
else if (trivially_move_constructible<T>)
{
destroy();
asl::memcpy(this, &other, kInlineRegionSize);
}
else if (!assign || m_allocator == other.m_allocator)
{
// If allocators are compatible, we can move other's inline
// data here, even if it's on heap here, because that
// memory can be freed by other's allocator, which we will
// soon adopt.
//
// @Note There is an argument to be made for not doing this and
// instead destroying our data here and moving into inline
// storage, which frees one allocation. But also this avoids
// freeing. So I don't know.
// Maybe If this storage is much much larger than the inline
// data, it's worth freeing.
const isize_t other_n = other.size();
const isize_t this_n = size();
resize_uninit_inner(other_n);
@ -162,11 +174,28 @@ private:
}
else
{
// Otherwise, if we have to free, because the allocators are
// not compatible, well we free and move into our inline
// storage region.
// There is an optimization here when the data is trivially
// move constructible (which implies trivially destructible),
// we copy the whole inline region, which includes the size.
// Very magic.
destroy();
const isize_t n = other.size();
ASL_ASSERT(n <= kInlineCapacity);
relocate_uninit_n(data(), other.data(), n);
set_size_inline(n);
if constexpr (trivially_move_constructible<T>)
{
ASL_ASSERT(!is_on_heap());
asl::memcpy(this, &other, kInlineRegionSize);
}
else
{
const isize_t n = other.size();
ASL_ASSERT(n <= kInlineCapacity);
resize_uninit_inner(n);
ASL_ASSERT(!is_on_heap());
relocate_uninit_n(data(), other.data(), n);
}
}
other.set_size_inline(0);
@ -244,7 +273,6 @@ public:
}
constexpr buffer(buffer&& other)
requires moveable<T>
: buffer(std::move(other.m_allocator))
{
move_from_other(std::move(other), false);
@ -259,7 +287,6 @@ public:
}
constexpr buffer& operator=(buffer&& other)
requires moveable<T>
{
if (&other == this) { return *this; }
move_from_other(std::move(other), true);
@ -271,6 +298,14 @@ public:
destroy();
}
constexpr Allocator allocator_copy() const
requires copy_constructible<Allocator>
{
return m_allocator;
}
constexpr Allocator& allocator() { return m_allocator; }
[[nodiscard]] constexpr isize_t size() const
{
return decode_size(load_size_encoded());
@ -316,7 +351,6 @@ public:
void reserve_capacity(isize_t new_capacity)
{
ASL_ASSERT(new_capacity >= 0);
ASL_ASSERT_RELEASE(new_capacity <= 0x4000'0000'0000'0000);
if (new_capacity <= capacity()) { return; }
ASL_ASSERT(new_capacity > kInlineCapacity);
@ -353,17 +387,16 @@ public:
}
constexpr void resize_uninit(isize_t new_size)
requires trivially_default_constructible<T> && trivially_destructible<T>
requires trivially_default_constructible<T>
{
reserve_capacity(new_size);
set_size(new_size);
resize_uninit_inner(new_size);
}
constexpr void resize_zero(isize_t new_size)
requires trivially_default_constructible<T> && trivially_destructible<T>
requires trivially_default_constructible<T>
{
const isize_t old_size = size();
resize_uninit(new_size);
resize_uninit_inner(new_size);
if (new_size > old_size)
{
@ -374,7 +407,14 @@ public:
void resize(isize_t new_size)
requires default_constructible<T>
{
resize_inner(new_size);
if constexpr (trivially_default_constructible<T>)
{
resize_zero(new_size);
}
else
{
resize_inner(new_size);
}
}
void resize(isize_t new_size, const T& value)
@ -385,14 +425,14 @@ public:
constexpr T& push(auto&&... args)
requires constructible_from<T, decltype(args)&&...>
{
T* uninit = push_uninit();
void* uninit = push_uninit();
T* init = construct_at<T>(uninit, std::forward<decltype(args)>(args)...);
return *init;
}
auto data(this auto&& self)
{
using return_type = un_ref_t<copy_cref_t<decltype(self), T>>*;
using return_type = as_ptr_t<copy_const_t<un_ref_t<decltype(self)>, T>>;
// NOLINTNEXTLINE(*-reinterpret-cast)
auto&& buffer = reinterpret_cast<copy_cref_t<decltype(self), class buffer>>(self);
if constexpr (kInlineCapacity == 0)
@ -410,13 +450,13 @@ public:
constexpr auto begin(this auto&& self)
{
using type = un_ref_t<copy_cref_t<decltype(self), T>>;
using type = copy_const_t<un_ref_t<decltype(self)>, T>;
return contiguous_iterator<type>{self.data()};
}
constexpr auto end(this auto&& self)
{
using type = un_ref_t<copy_cref_t<decltype(self), T>>;
using type = copy_const_t<un_ref_t<decltype(self)>, T>;
return contiguous_iterator<type>{self.data() + self.size()};
}
@ -432,13 +472,13 @@ public:
constexpr auto as_span(this auto&& self)
{
using type = un_ref_t<copy_cref_t<decltype(self), T>>;
using type = copy_const_t<un_ref_t<decltype(self)>, T>;
return span<type>{self.data(), self.size()};
}
constexpr auto&& operator[](this auto&& self, isize_t i)
{
ASL_ASSERT(i >= 0 && i <= self.size());
ASL_ASSERT(i >= 0 && i < self.size());
return std::forward_like<decltype(self)>(std::forward<decltype(self)>(self).data()[i]);
}

View File

@ -6,6 +6,7 @@
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
#include "asl/tests/counting_allocator.hpp"
struct Big
{
@ -14,6 +15,7 @@ struct Big
static_assert(asl::buffer<int32_t>::kInlineCapacity == 5);
static_assert(asl::buffer<int64_t>::kInlineCapacity == 2);
static_assert(asl::buffer<void*>::kInlineCapacity == 2);
static_assert(asl::buffer<char>::kInlineCapacity == 23);
static_assert(asl::buffer<Big>::kInlineCapacity == 0);
@ -30,32 +32,6 @@ ASL_TEST(default_size)
ASL_TEST_EXPECT(b2.data() == nullptr);
}
struct CounterAllocator
{
isize_t* count;
[[nodiscard]]
void* alloc(const asl::layout& layout) const
{
*count += 1;
return asl::GlobalHeap::alloc(layout);
}
void* realloc(void* ptr, const asl::layout& old, const asl::layout& new_layout) const
{
*count += 1;
return asl::GlobalHeap::realloc(ptr, old, new_layout);
}
static void dealloc(void* ptr, const asl::layout& layout)
{
asl::GlobalHeap::dealloc(ptr, layout);
}
constexpr bool operator==(const CounterAllocator&) const { return true; }
};
static_assert(asl::allocator<CounterAllocator>);
struct IncompatibleAllocator
{
static void* alloc(const asl::layout& layout)
@ -80,31 +56,31 @@ static_assert(asl::allocator<IncompatibleAllocator>);
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(reserve_capacity)
{
isize_t count = 0;
asl::buffer<int32_t, CounterAllocator> b(CounterAllocator{&count});
CountingAllocator::Stats stats;
asl::buffer<int32_t, CountingAllocator> b(CountingAllocator{&stats});
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() == 5);
ASL_TEST_EXPECT(count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
b.reserve_capacity(4);
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() == 5);
ASL_TEST_EXPECT(count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
b.reserve_capacity(12);
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 12);
ASL_TEST_EXPECT(count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
b.reserve_capacity(13);
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 13);
ASL_TEST_EXPECT(count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
b.reserve_capacity(130);
ASL_TEST_EXPECT(b.size() == 0);
ASL_TEST_EXPECT(b.capacity() >= 130);
ASL_TEST_EXPECT(count == 2);
ASL_TEST_EXPECT(stats.any_alloc_count() == 2);
}
// NOLINTNEXTLINE(*-complexity)
@ -159,13 +135,19 @@ struct MoveableType
MoveableType(const MoveableType&) = delete;
MoveableType(MoveableType&& other) : moved{other.moved + 1}, value{other.value} {}
MoveableType& operator=(const MoveableType&) = delete;
MoveableType& operator=(MoveableType&&) = delete;
MoveableType& operator=(MoveableType&& other)
{
if (this == &other) { return *this; }
moved = other.moved + 1;
value = other.value;
return *this;
}
~MoveableType() = default;
};
static_assert(!asl::trivially_copy_constructible<MoveableType>);
static_assert(!asl::trivially_move_constructible<MoveableType>);
static_assert(!asl::copyable<MoveableType>);
static_assert(asl::move_constructible<MoveableType>);
static_assert(asl::moveable<MoveableType>);
// NOLINTNEXTLINE(*-complexity)
ASL_TEST(push_move)
@ -213,6 +195,30 @@ ASL_TEST(push_move)
ASL_TEST_EXPECT(b[3].moved == 1);
ASL_TEST_EXPECT(b[4].value == 4);
ASL_TEST_EXPECT(b[4].moved == 0);
asl::buffer<MoveableType> b2 = std::move(b);
ASL_TEST_EXPECT(b2[0].value == 0);
ASL_TEST_EXPECT(b2[0].moved == 2);
ASL_TEST_EXPECT(b2[1].value == 1);
ASL_TEST_EXPECT(b2[1].moved == 2);
ASL_TEST_EXPECT(b2[2].value == 2);
ASL_TEST_EXPECT(b2[2].moved == 1);
ASL_TEST_EXPECT(b2[3].value == 3);
ASL_TEST_EXPECT(b2[3].moved == 1);
ASL_TEST_EXPECT(b2[4].value == 4);
ASL_TEST_EXPECT(b2[4].moved == 0);
b = std::move(b2);
ASL_TEST_EXPECT(b[0].value == 0);
ASL_TEST_EXPECT(b[0].moved == 2);
ASL_TEST_EXPECT(b[1].value == 1);
ASL_TEST_EXPECT(b[1].moved == 2);
ASL_TEST_EXPECT(b[2].value == 2);
ASL_TEST_EXPECT(b[2].moved == 1);
ASL_TEST_EXPECT(b[3].value == 3);
ASL_TEST_EXPECT(b[3].moved == 1);
ASL_TEST_EXPECT(b[4].value == 4);
ASL_TEST_EXPECT(b[4].moved == 0);
}
ASL_TEST(clear)
@ -369,21 +375,21 @@ ASL_TEST(move_assign_from_heap)
ASL_TEST(move_assign_trivial_heap_to_inline)
{
isize_t alloc_count = 0;
asl::buffer<int64_t, CounterAllocator> buf{CounterAllocator{&alloc_count}};
asl::buffer<int64_t, CounterAllocator> buf2{CounterAllocator{&alloc_count}};
CountingAllocator::Stats stats;
asl::buffer<int64_t, CountingAllocator> buf{CountingAllocator{&stats}};
asl::buffer<int64_t, CountingAllocator> buf2{CountingAllocator{&stats}};
buf.push(1);
buf.push(2);
ASL_TEST_EXPECT(alloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
buf2.push(3);
buf2.push(4);
buf2.push(5);
ASL_TEST_EXPECT(alloc_count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf = std::move(buf2);
ASL_TEST_EXPECT(alloc_count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
ASL_TEST_EXPECT(buf.size() == 3);
ASL_TEST_EXPECT(buf[0] == 3);
@ -393,21 +399,21 @@ ASL_TEST(move_assign_trivial_heap_to_inline)
ASL_TEST(move_assign_trivial_inline_to_heap)
{
isize_t alloc_count = 0;
asl::buffer<int64_t, CounterAllocator> buf{CounterAllocator{&alloc_count}};
asl::buffer<int64_t, CounterAllocator> buf2{CounterAllocator{&alloc_count}};
CountingAllocator::Stats stats;
asl::buffer<int64_t, CountingAllocator> buf{CountingAllocator{&stats}};
asl::buffer<int64_t, CountingAllocator> buf2{CountingAllocator{&stats}};
buf.push(1);
buf.push(2);
ASL_TEST_EXPECT(alloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
buf2.push(3);
buf2.push(4);
buf2.push(5);
ASL_TEST_EXPECT(alloc_count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf2 = std::move(buf);
ASL_TEST_EXPECT(alloc_count == 1);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(buf2[0] == 1);
@ -436,16 +442,16 @@ ASL_TEST(move_assign_inline_to_heap)
ASL_TEST_EXPECT(buf2.size() == 2);
ASL_TEST_EXPECT(d[0] == false);
ASL_TEST_EXPECT(d[1] == false);
ASL_TEST_EXPECT(d[2] == false); // moved but not destroyed
ASL_TEST_EXPECT(d[3] == false); // moved but not destroyed
ASL_TEST_EXPECT(d[2] == true);
ASL_TEST_EXPECT(d[3] == true);
ASL_TEST_EXPECT(d[4] == true);
ASL_TEST_EXPECT(d[5] == true);
}
ASL_TEST_EXPECT(d[0] == true);
ASL_TEST_EXPECT(d[1] == true);
ASL_TEST_EXPECT(d[2] == false); // moved but not destroyed
ASL_TEST_EXPECT(d[3] == false); // moved but not destroyed
ASL_TEST_EXPECT(d[2] == true);
ASL_TEST_EXPECT(d[3] == true);
ASL_TEST_EXPECT(d[4] == true);
ASL_TEST_EXPECT(d[5] == true);
}
@ -605,3 +611,6 @@ ASL_TEST(resize_zero)
}
}
static_assert(asl::same_as<decltype(asl::declval<asl::buffer<int>>().data()), int*>);
static_assert(asl::same_as<decltype(asl::declval<const asl::buffer<int>>().data()), const int*>);

View File

@ -0,0 +1,405 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/utility.hpp"
#include "asl/base/assert.hpp"
#include "asl/base/numeric.hpp"
#include "asl/containers/buffer.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/types/array.hpp"
#include "asl/types/maybe_uninit.hpp"
namespace asl
{
template<
is_object T,
isize_t kChunkSize,
allocator Allocator = DefaultAllocator>
class chunked_buffer
{
static_assert(kChunkSize > 0 && is_pow2(kChunkSize));
using Chunk = array<maybe_uninit<T>, kChunkSize>;
static constexpr isize_t chunk_index(isize_t i)
{
static constexpr int kChunkSizeLog2 = countr_zero(uint64_t{kChunkSize});
return i >> kChunkSizeLog2;
}
static constexpr isize_t index_in_chunk(isize_t i)
{
static constexpr isize_t kMask = kChunkSize - 1;
return i & kMask;
}
struct PerChunkIterator
{
isize_t from_chunk;
isize_t to_chunk;
isize_t from_index_in_chunk;
isize_t to_index_in_chunk;
[[nodiscard]] constexpr bool has_more() const
{
return from_chunk <= to_chunk;
}
void advance()
{
from_chunk += 1;
from_index_in_chunk = 0;
}
[[nodiscard]] constexpr isize_t chunk() const { return from_chunk; }
span<maybe_uninit<T>> make_span(Chunk& chunk) const
{
isize_t from = from_index_in_chunk;
isize_t to = (from_chunk == to_chunk) ? to_index_in_chunk : kChunkSize - 1;
return chunk.as_span().subspan(from, to - from + 1);
}
};
PerChunkIterator make_index_iterator(isize_t from, isize_t to)
{
return PerChunkIterator {
chunk_index(from), chunk_index(to),
index_in_chunk(from), index_in_chunk(to)
};
}
buffer<Chunk*, Allocator> m_chunks;
isize_t m_size{};
void resize_uninit_inner(isize_t new_size)
{
ASL_ASSERT(new_size >= 0);
if constexpr (!trivially_destructible<T>)
{
const isize_t old_size = size();
if (new_size < old_size)
{
for (PerChunkIterator it = make_index_iterator(new_size, old_size - 1);
it.has_more();
it.advance())
{
auto span = it.make_span(*m_chunks[it.chunk()]);
for (auto& el: span)
{
el.destroy_unsafe();
}
}
}
}
reserve_capacity(new_size);
m_size = new_size;
}
template<typename... Args>
void resize_construct(isize_t new_size, Args&&... args)
requires constructible_from<T, Args&&...>
{
const isize_t old_size = m_size;
resize_uninit_inner(new_size);
if (new_size > old_size)
{
for (PerChunkIterator it = make_index_iterator(old_size, new_size - 1);
it.has_more();
it.advance())
{
auto span = it.make_span(*m_chunks[it.chunk()]);
for (auto& uninit: span)
{
uninit.construct_unsafe(std::forward<Args>(args)...);
}
}
}
}
void copy_from(const chunked_buffer& other)
requires copyable<T>
{
const isize_t this_size = size();
isize_t to_copy_assign = asl::min(other.size(), this_size);
resize_uninit_inner(other.size());
for (PerChunkIterator it = make_index_iterator(0, to_copy_assign - 1);
it.has_more();
it.advance())
{
auto to_span = it.make_span(*m_chunks[it.chunk()]);
auto from_span = it.make_span(*other.m_chunks[it.chunk()]);
copy_assign_n(
reinterpret_cast<T*>(to_span.data()), // NOLINT(*-reinterpret-cast)
reinterpret_cast<const T*>(from_span.data()), // NOLINT(*-reinterpret-cast)
to_span.size());
}
if (other.size() > this_size)
{
for (PerChunkIterator it = make_index_iterator(to_copy_assign, other.size() - 1);
it.has_more();
it.advance())
{
auto to_span = it.make_span(*m_chunks[it.chunk()]);
auto from_span = it.make_span(*other.m_chunks[it.chunk()]);
copy_uninit_n(
reinterpret_cast<T*>(to_span.data()), // NOLINT(*-reinterpret-cast)
reinterpret_cast<const T*>(from_span.data()), // NOLINT(*-reinterpret-cast)
to_span.size());
}
}
ASL_ASSERT(size() == other.size());
}
public:
constexpr chunked_buffer()
requires default_constructible<Allocator>
= default;
explicit constexpr chunked_buffer(Allocator allocator)
: m_chunks{std::move(allocator)}
{}
constexpr chunked_buffer(const chunked_buffer& other)
requires copyable<T> && copy_constructible<Allocator>
: m_chunks{other.m_chunks.allocator_copy()}
{
copy_from(other);
}
constexpr chunked_buffer(chunked_buffer&& other)
: m_chunks{std::move(other.m_chunks)}
, m_size{asl::exchange(other.m_size, 0)}
{
ASL_ASSERT(other.m_chunks.size() == 0);
}
constexpr chunked_buffer& operator=(const chunked_buffer& other)
requires copyable<T>
{
if (&other == this) { return *this; }
copy_from(other);
return *this;
}
constexpr chunked_buffer& operator=(chunked_buffer&& other)
{
if (&other == this) { return *this; }
destroy();
m_chunks = std::move(other.m_chunks);
m_size = asl::exchange(other.m_size, 0);
ASL_ASSERT(other.m_chunks.size() == 0);
return *this;
}
~chunked_buffer()
{
destroy();
}
void clear()
{
if constexpr (trivially_destructible<T>)
{
m_size = 0;
}
else if (m_size > 0)
{
resize_uninit_inner(0);
ASL_ASSERT(m_size == 0);
}
}
void destroy()
{
clear();
ASL_ASSERT(size() == 0);
for (Chunk* chunk: m_chunks)
{
alloc_delete(m_chunks.allocator(), chunk);
}
m_chunks.destroy();
}
[[nodiscard]] constexpr isize_t size() const { return m_size; }
[[nodiscard]] constexpr bool is_empty() const { return size() == 0; }
[[nodiscard]] constexpr isize_t capacity() const
{
return m_chunks.size() * kChunkSize;
}
constexpr auto&& operator[](this auto&& self, isize_t i)
{
ASL_ASSERT(i >= 0 && i < self.m_size);
return std::forward_like<decltype(self)>(
(*std::forward<decltype(self)>(self).m_chunks[chunk_index(i)])
[index_in_chunk(i)].as_init_unsafe()
);
}
constexpr T& push(auto&&... args)
requires constructible_from<T, decltype(args)&&...>
{
const isize_t chunk = chunk_index(m_size);
const isize_t in_chunk = index_in_chunk(m_size);
if (m_size == capacity())
{
resize_uninit_inner(m_size + 1);
}
else
{
m_size += 1;
}
void* uninit = &(*m_chunks[chunk])[in_chunk];
return *construct_at<T>(uninit, std::forward<decltype(args)>(args)...);
}
void reserve_capacity(isize_t new_capacity)
{
new_capacity = round_up_pow2(new_capacity, kChunkSize);
if (new_capacity <= capacity()) { return; }
const isize_t required_chunks = new_capacity / kChunkSize;
const isize_t additional_chunks = required_chunks - m_chunks.size();
ASL_ASSERT(additional_chunks > 0);
m_chunks.reserve_capacity(required_chunks);
for (isize_t i = 0; i < additional_chunks; ++i)
{
// @Todo(C++26) _unsafe shouldn't be needed with trivial unions
auto* chunk = alloc_uninit_unsafe<Chunk>(m_chunks.allocator());
m_chunks.push(chunk);
}
}
void resize(isize_t new_size)
requires default_constructible<T>
{
if constexpr (trivially_default_constructible<T>)
{
resize_zero(new_size);
}
else
{
resize_construct(new_size);
}
}
void resize(isize_t new_size, const T& value)
requires copy_constructible<T>
{
resize_construct(new_size, value);
}
void resize_zero(isize_t new_size)
requires trivially_default_constructible<T>
{
const isize_t old_size = m_size;
resize_uninit_inner(new_size);
if (new_size > old_size)
{
for (PerChunkIterator it = make_index_iterator(old_size, new_size - 1);
it.has_more();
it.advance())
{
auto span = it.make_span(*m_chunks[it.chunk()]);
asl::memzero(span.data(), span.size_bytes());
}
}
}
void resize_uninit(isize_t new_size)
requires trivially_default_constructible<T>
{
resize_uninit_inner(new_size);
}
void pop()
{
ASL_ASSERT(size() > 0);
resize_uninit_inner(size() - 1);
}
template<typename Chunk>
class generic_iterator
{
isize_t m_index;
span<Chunk> m_chunks;
public:
constexpr generic_iterator(isize_t index, span<Chunk> chunks)
: m_index{index}
, m_chunks{chunks}
{}
constexpr generic_iterator& operator++()
{
m_index += 1;
return *this;
}
constexpr generic_iterator operator++(int)
{
auto tmp = *this;
m_index += 1;
return tmp;
}
constexpr bool operator==(this generic_iterator self, generic_iterator other)
{
ASL_ASSERT(self.m_chunks.data() == other.m_chunks.data());
return self.m_index == other.m_index;
}
constexpr auto& operator*(this generic_iterator self)
{
ASL_ASSERT(self.m_index >= 0);
return (*self.m_chunks[chunk_index(self.m_index)])[index_in_chunk(self.m_index)].as_init_unsafe();
}
constexpr auto* operator->(this generic_iterator self)
{
return &*self;
}
};
using iterator = generic_iterator<Chunk*>;
using const_iterator = generic_iterator<const Chunk* const>;
constexpr iterator begin() { return iterator{0, m_chunks}; }
constexpr iterator end() { return iterator{m_size, m_chunks}; }
constexpr const_iterator begin() const
{
return const_iterator{0, {m_chunks.data(), m_chunks.size()}};
}
constexpr const_iterator end() const
{
return const_iterator{m_size, {m_chunks.data(), m_chunks.size()}};
}
};
} // namespace asl

View File

@ -0,0 +1,377 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
#include "asl/tests/counting_allocator.hpp"
#include "asl/containers/chunked_buffer.hpp"
static_assert(asl::moveable<asl::chunked_buffer<int, 8>>);
static_assert(asl::moveable<asl::chunked_buffer<Copyable, 8>>);
static_assert(asl::moveable<asl::chunked_buffer<MoveableOnly, 8>>);
static_assert(asl::moveable<asl::chunked_buffer<Pinned, 8>>);
static_assert(asl::copyable<asl::chunked_buffer<int, 8>>);
static_assert(asl::copyable<asl::chunked_buffer<Copyable, 8>>);
static_assert(!asl::copyable<asl::chunked_buffer<MoveableOnly, 8>>);
static_assert(!asl::copyable<asl::chunked_buffer<Pinned, 8>>);
ASL_TEST(reserve)
{
asl::chunked_buffer<int, 16> b;
ASL_TEST_EXPECT(b.capacity() == 0);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(1);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(5);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(16);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(35);
ASL_TEST_EXPECT(b.capacity() == 48);
ASL_TEST_EXPECT(b.size() == 0);
b.reserve_capacity(12);
ASL_TEST_EXPECT(b.capacity() == 48);
ASL_TEST_EXPECT(b.size() == 0);
}
ASL_TEST(resize_uninit)
{
asl::chunked_buffer<int, 16> b;
ASL_TEST_EXPECT(b.capacity() == 0);
ASL_TEST_EXPECT(b.size() == 0);
b.resize_uninit(1);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 1);
b.resize_uninit(5);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 5);
b.resize_uninit(16);
ASL_TEST_EXPECT(b.capacity() == 16);
ASL_TEST_EXPECT(b.size() == 16);
b.resize_uninit(35);
ASL_TEST_EXPECT(b.capacity() == 48);
ASL_TEST_EXPECT(b.size() == 35);
b.resize_uninit(12);
ASL_TEST_EXPECT(b.capacity() == 48);
ASL_TEST_EXPECT(b.size() == 12);
}
ASL_TEST(resize_zero)
{
asl::chunked_buffer<int, 4> b;
ASL_TEST_EXPECT(b.capacity() == 0);
ASL_TEST_EXPECT(b.size() == 0);
b.resize_zero(2);
for (isize_t i = 0; i < 2; ++i)
{
ASL_TEST_EXPECT(b[i] == 0);
}
b.resize_zero(18);
for (isize_t i = 0; i < 18; ++i)
{
ASL_TEST_EXPECT(b[i] == 0);
}
}
ASL_TEST(resize)
{
asl::chunked_buffer<int, 4> b;
ASL_TEST_EXPECT(b.capacity() == 0);
ASL_TEST_EXPECT(b.size() == 0);
b.resize(10);
for (isize_t i = 0; i < 10; ++i)
{
ASL_TEST_EXPECT(b[i] == 0);
}
b.resize(20, 8);
for (isize_t i = 0; i < 10; ++i)
{
ASL_TEST_EXPECT(b[i] == 0);
}
for (isize_t i = 10; i < 20; ++i)
{
ASL_TEST_EXPECT(b[i] == 8);
}
}
ASL_TEST(push)
{
asl::chunked_buffer<int, 4> b;
for (int i = 0; i < 100; ++i)
{
b.push(i);
}
for (int i = 0; i < 100; ++i)
{
ASL_TEST_EXPECT(b[i] == i);
}
b.resize(1000);
for (int i = 0; i < 100; ++i)
{
ASL_TEST_EXPECT(b[i] == i);
}
}
ASL_TEST(pop)
{
asl::chunked_buffer<int, 2> b;
for (int i = 0; i < 8; ++i)
{
b.push(i);
}
ASL_TEST_EXPECT(b.size() == 8);
b.pop();
ASL_TEST_EXPECT(b.size() == 7);
for (int i = 0; i < 7; ++i)
{
ASL_TEST_EXPECT(b[i] == i);
}
}
ASL_TEST(pop_destruct)
{
bool d[3]{};
asl::chunked_buffer<DestructorObserver, 16> b;
b.push(&d[0]);
b.push(&d[1]);
b.push(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
b.pop();
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(d[2]);
b.pop();
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
ASL_TEST(clear_destroy)
{
bool destroyed[5]{};
asl::chunked_buffer<DestructorObserver, 2> buf;
for (bool& d: destroyed)
{
buf.push(&d); // NOLINT
}
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(!d);
}
buf.clear();
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(d);
}
}
ASL_TEST(alloc_count) // NOLINT
{
CountingAllocator::Stats stats;
asl::chunked_buffer<int, 4, CountingAllocator> buf{CountingAllocator{&stats}};
ASL_TEST_EXPECT(stats.alive_bytes == 0);
ASL_TEST_EXPECT(stats.alloc_count == 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 0);
buf.push(1);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf.push(2);
buf.push(3);
buf.push(4);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 1);
buf.push(5);
buf.push(6);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 2);
buf.resize(8, 8);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 2);
buf.resize(32, 8);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 9);
buf.resize(16, 0);
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 9);
buf.clear();
ASL_TEST_EXPECT(stats.alive_bytes > 0);
ASL_TEST_EXPECT(stats.dealloc_count == 0);
ASL_TEST_EXPECT(stats.any_alloc_count() == 9);
buf.destroy();
ASL_TEST_EXPECT(stats.alive_bytes == 0);
ASL_TEST_EXPECT(stats.dealloc_count == 9);
ASL_TEST_EXPECT(stats.any_alloc_count() == 9);
}
ASL_TEST(move)
{
bool destroyed[5]{};
{
asl::chunked_buffer<DestructorObserver, 2> buf;
for (bool& d: destroyed)
{
buf.push(&d); // NOLINT
}
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(!d);
}
asl::chunked_buffer<DestructorObserver, 2> buf2 = std::move(buf);
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(!d);
}
buf = std::move(buf2);
buf2.destroy();
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(!d);
}
}
for (const bool d: destroyed)
{
ASL_TEST_EXPECT(d);
}
}
ASL_TEST(copy) // NOLINT
{
asl::chunked_buffer<int, 4> buf;
for (int i = 0; i < 10; ++i) { buf.push(i); }
asl::chunked_buffer<int, 4> buf2 = buf;
ASL_TEST_EXPECT(buf.size() == 10);
ASL_TEST_EXPECT(buf2.size() == 10);
for (int i = 0; i < 10; ++i)
{
ASL_TEST_EXPECT(buf[i] == i);
ASL_TEST_EXPECT(buf2[i] == i);
}
buf2.resize(5);
buf = buf2;
ASL_TEST_EXPECT(buf.size() == 5);
ASL_TEST_EXPECT(buf2.size() == 5);
for (int i = 0; i < 5; ++i)
{
ASL_TEST_EXPECT(buf[i] == i);
ASL_TEST_EXPECT(buf2[i] == i);
}
buf.clear();
buf.resize(80, 12);
buf2 = buf;
ASL_TEST_EXPECT(buf.size() == 80);
ASL_TEST_EXPECT(buf2.size() == 80);
for (int i = 0; i < 80; ++i)
{
ASL_TEST_EXPECT(buf[i] == 12);
ASL_TEST_EXPECT(buf2[i] == 12);
}
}
ASL_TEST(iterator)
{
asl::chunked_buffer<int, 4> buf;
for (int i = 0; i < 30; ++i) { buf.push(100 + i); }
auto it = buf.begin();
auto end = buf.end();
for (int i = 0; i < 30; ++i)
{
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 100 + i);
it++;
}
ASL_TEST_EXPECT(it == end);
static_assert(asl::same_as<decltype(*it), int&>);
asl::chunked_buffer<int, 8> buf2;
ASL_TEST_EXPECT(buf2.begin() == buf2.end());
}
ASL_TEST(const_iterator)
{
asl::chunked_buffer<int, 4> buf_value;
for (int i = 0; i < 30; ++i) { buf_value.push(100 + i); }
const auto& buf = buf_value;
auto it = buf.begin();
auto end = buf.end();
for (int i = 0; i < 30; ++i)
{
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 100 + i);
++it;
}
ASL_TEST_EXPECT(it == end);
static_assert(asl::same_as<decltype(*it), const int&>);
asl::chunked_buffer<int, 8> buf2;
ASL_TEST_EXPECT(buf2.begin() == buf2.end());
}

View File

@ -147,7 +147,7 @@ public:
auto get(this auto&& self, const U& value)
requires key_hasher<KeyHasher, U> && key_comparator<KeyComparator, K, U>
{
using return_type = un_ref_t<copy_cref_t<decltype(self), V>>*;
using return_type = copy_const_t<un_ref_t<decltype(self)>, V>*;
isize_t index = self.find_slot_lookup(value);
if (index >= 0)
{

View File

@ -82,13 +82,13 @@ public:
constexpr auto front(this auto&& self)
{
using return_type = un_ref_t<copy_cref_t<decltype(self), T>>*;
using return_type = copy_const_t<un_ref_t<decltype(self)>, T>*;
return return_type{ self.m_head };
}
constexpr auto back(this auto&& self)
{
using return_type = un_ref_t<copy_cref_t<decltype(self), T>>*;
using return_type = copy_const_t<un_ref_t<decltype(self)>, T>*;
return return_type{ self.m_head != nullptr ? self.m_head->m_prev : nullptr };
}

View File

@ -5,6 +5,7 @@
#pragma once
#include "asl/base/integers.hpp"
#include "asl/base/float.hpp"
#include "asl/base/meta.hpp"
#include "asl/io/writer.hpp"
#include "asl/types/span.hpp"
@ -93,8 +94,8 @@ inline void AslFormat(Formatter& f, string_view sv)
f.write(sv);
}
void AslFormat(Formatter& f, float);
void AslFormat(Formatter& f, double);
void AslFormat(Formatter& f, float32_t);
void AslFormat(Formatter& f, float64_t);
void AslFormat(Formatter& f, bool);

View File

@ -6,9 +6,12 @@
#include "asl/base/float.hpp"
#include "asl/base/numeric.hpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#define JKJ_STD_REPLACEMENT_NAMESPACE_DEFINED 0
#define JKJ_STATIC_DATA_SECTION_DEFINED 0
#include <dragonbox.h>
#pragma clang diagnostic pop
static constexpr isize_t kZeroCount = 100;
static constexpr char kZeros[kZeroCount] = {
@ -24,12 +27,12 @@ static constexpr char kZeros[kZeroCount] = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
};
static constexpr bool is_zero(float x)
static constexpr bool is_zero(float32_t x)
{
return (asl::bit_cast<uint32_t>(x) & 0x7fff'ffffU) == 0;
}
static constexpr bool is_zero(double x)
static constexpr bool is_zero(float64_t x)
{
return (asl::bit_cast<uint64_t>(x) & 0x7fff'ffff'ffff'ffffULL) == 0;
}
@ -102,12 +105,12 @@ static void format_float(asl::Formatter& f, T value)
}
}
void asl::AslFormat(Formatter& f, float value)
void asl::AslFormat(Formatter& f, float32_t value)
{
format_float(f, value);
}
void asl::AslFormat(Formatter& f, double value)
void asl::AslFormat(Formatter& f, float64_t value)
{
format_float(f, value);
}

View File

@ -79,7 +79,7 @@ ASL_TEST(format_floats)
s = asl::format_to_string("{}", 123e-8);
ASL_TEST_EXPECT(s == "0.00000123"_sv);
s = asl::format_to_string("{} {}", asl::infinity<float>(), -asl::infinity<double>());
s = asl::format_to_string("{} {}", asl::infinity<float32_t>(), -asl::infinity<float64_t>());
ASL_TEST_EXPECT(s == "Infinity -Infinity"_sv);
s = asl::format_to_string("{}", asl::nan<float>());

View File

@ -0,0 +1,81 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
package(
default_applicable_licenses = ["//:license"],
)
cc_library(
name = "index_pool",
hdrs = [
"index_pool.hpp",
],
deps = [
"//asl/memory",
"//asl/memory:allocator",
"//asl/base",
"//asl/containers:chunked_buffer",
"//asl/types:option",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "dense_handle_pool",
hdrs = [
"dense_handle_pool.hpp",
],
deps = [
":index_pool",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "sparse_handle_pool",
hdrs = [
"sparse_handle_pool.hpp",
],
deps = [
":index_pool",
],
visibility = ["//visibility:public"],
)
cc_test(
name = "index_pool_tests",
srcs = [
"index_pool_tests.cpp",
],
deps = [
":index_pool",
"//asl/hashing",
"//asl/tests:utils",
"//asl/testing",
],
)
cc_test(
name = "dense_handle_pool_tests",
srcs = [
"dense_handle_pool_tests.cpp",
],
deps = [
":dense_handle_pool",
"//asl/tests:utils",
"//asl/testing",
],
)
cc_test(
name = "sparse_handle_pool_tests",
srcs = [
"sparse_handle_pool_tests.cpp",
],
deps = [
":sparse_handle_pool",
"//asl/tests:utils",
"//asl/testing",
],
)

View File

@ -0,0 +1,162 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/handle_pool/index_pool.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/containers/chunked_buffer.hpp"
namespace asl
{
// @Todo If we want the allocator to be non-copyable, we could
// introduce a reference allocator type that is copyable, and store
// the "main" allocator in the pool.
template<
is_object T,
int kIndexBits,
int kGenBits,
typename UserType = empty,
int kUserBits = 0,
isize_t kChunkSize = 32,
allocator Allocator = DefaultAllocator>
requires moveable<T> && copyable<Allocator>
class DenseHandlePool
{
using ThisIndexPool = IndexPool<kIndexBits, kGenBits, UserType, kUserBits, isize_t, Allocator>;
struct Slot
{
ThisIndexPool::handle h;
T obj;
template<typename... Args>
explicit Slot(ThisIndexPool::handle h_, Args&&... args)
: h{h_}
, obj(std::forward<Args>(args)...)
{}
};
using Buffer = chunked_buffer<Slot, kChunkSize, Allocator>;
ThisIndexPool m_index_pool{};
Buffer m_buffer{};
using config = ThisIndexPool::handle::config;
template<typename... Args>
isize_t push(Args&&... args)
requires constructible_from<T, Args&&...>
{
m_buffer.push(typename ThisIndexPool::handle{}, std::forward<Args>(args)...);
return m_buffer.size() - 1;
}
public:
using handle = ThisIndexPool::handle;
DenseHandlePool() requires default_constructible<Allocator> = default;
explicit DenseHandlePool(const Allocator& allocator)
: m_index_pool(allocator)
, m_buffer(allocator)
{}
ASL_DELETE_COPY(DenseHandlePool);
ASL_DEFAULT_MOVE(DenseHandlePool);
~DenseHandlePool() = default;
[[nodiscard]] bool is_full() const
{
return m_index_pool.is_full();
}
bool is_valid(handle h) const
{
return m_index_pool.is_valid(h);
}
template<typename... Args>
option<handle> acquire(config::UserType user, Args&&... args)
requires config::kHasUser && constructible_from<T, Args&&...>
{
if (is_full()) { return nullopt; }
const isize_t obj_index = push(std::forward<Args>(args)...);
const auto handle = m_index_pool.acquire_ensure(user, obj_index);
m_buffer[obj_index].h = handle;
return handle;
}
template<typename... Args>
option<handle> acquire(Args&&... args)
requires (!config::kHasUser) && constructible_from<T, Args&&...>
{
if (is_full()) { return nullopt; }
const isize_t obj_index = push(std::forward<Args>(args)...);
const auto handle = m_index_pool.acquire_ensure(obj_index);
m_buffer[obj_index].h = handle;
return handle;
}
template<typename... Args>
handle acquire_ensure(config::UserType user, Args&&... args)
requires config::kHasUser && constructible_from<T, Args&&...>
{
ASL_ASSERT_RELEASE(!is_full());
const isize_t obj_index = push(std::forward<Args>(args)...);
const auto handle = m_index_pool.acquire_ensure(user, obj_index);
m_buffer[obj_index].h = handle;
return handle;
}
template<typename... Args>
handle acquire_ensure(Args&&... args)
requires (!config::kHasUser) && constructible_from<T, Args&&...>
{
ASL_ASSERT_RELEASE(!is_full());
const isize_t obj_index = push(std::forward<Args>(args)...);
const auto handle = m_index_pool.acquire_ensure(obj_index);
m_buffer[obj_index].h = handle;
return handle;
}
void release(handle to_release_handle)
{
if (!is_valid(to_release_handle)) { return; }
const auto to_release_index = *m_index_pool.get_payload(to_release_handle);
if (to_release_index < m_buffer.size() - 1)
{
const auto to_swap_index = m_buffer.size() - 1;
const auto to_swap_handle = m_buffer[to_swap_index].h;
m_buffer[to_release_index] = std::move(m_buffer[to_swap_index]);
m_index_pool.exchange_payload(to_swap_handle, to_release_index);
}
m_buffer.pop();
m_index_pool.release(to_release_handle);
}
auto get(this auto&& self, handle h)
-> copy_const_t<un_ref_t<decltype(self)>, T>*
{
if (!self.is_valid(h)) { return nullptr; }
const auto index = *self.m_index_pool.get_payload(h);
return &self.m_buffer[index].obj;
}
auto get_ensure(this auto&& self, handle h)
-> copy_cref_t<decltype(self), T>
{
ASL_ASSERT_RELEASE(self.is_valid(h));
const auto index = *self.m_index_pool.get_payload(h);
return std::forward<decltype(self)>(self).m_buffer[index].obj;
}
};
} // namespace asl

View File

@ -0,0 +1,117 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
#include "asl/handle_pool/dense_handle_pool.hpp"
ASL_TEST(acquire_release) // NOLINT
{
asl::DenseHandlePool<int, 1, 1> pool;
ASL_TEST_EXPECT(!pool.is_full());
const auto a = pool.acquire_ensure(6);
const auto b = pool.acquire_ensure(7);
ASL_TEST_EXPECT(pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(pool.get_ensure(a) == 6);
ASL_TEST_EXPECT(pool.get_ensure(b) == 7);
pool.release(a);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(pool.get(a) == nullptr);
ASL_TEST_EXPECT(*pool.get(b) == 7);
const auto c = pool.acquire_ensure(8);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_valid(c));
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(*pool.get(b) == 7);
ASL_TEST_EXPECT(*pool.get(c) == 8);
ASL_TEST_EXPECT(pool.get(a) == nullptr);
}
ASL_TEST(element_destructor)
{
asl::DenseHandlePool<DestructorObserver, 8, 8> pool;
bool d[3]{};
const auto d0 = pool.acquire_ensure(&d[0]);
const auto d1 = pool.acquire_ensure(&d[1]);
const auto d2 = pool.acquire_ensure(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d1);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d0);
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d2);
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
ASL_TEST(destructor)
{
bool d[3]{};
{
asl::DenseHandlePool<DestructorObserver, 8, 8> pool;
pool.acquire_ensure(&d[0]);
const auto d1 = pool.acquire_ensure(&d[1]);
pool.acquire_ensure(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d1);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
}
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
enum Flags : uint8_t
{
kFlag1 = 1,
kFlag2 = 2,
};
ASL_TEST(user_type)
{
asl::DenseHandlePool<int, 8, 8, Flags> pool;
auto a = pool.acquire_ensure(kFlag2, 22);
auto b = pool.acquire_ensure(kFlag1, 11);
ASL_TEST_EXPECT(pool.get_ensure(a) == 22);
ASL_TEST_EXPECT(pool.get_ensure(b) == 11);
ASL_TEST_EXPECT(a.user() == kFlag2);
ASL_TEST_EXPECT(b.user() == kFlag1);
}

View File

@ -0,0 +1,378 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/base/integers.hpp"
#include "asl/base/meta.hpp"
#include "asl/containers/chunked_buffer.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/types/option.hpp"
namespace asl
{
template<
int kIndexBits_,
int kGenBits_,
typename UserType_ = empty,
int kUserBits_ = 0
>
requires (kIndexBits_ > 0 && kGenBits_ > 0 && kUserBits_ >= 0)
struct index_pool_config
{
static constexpr bool kHasUser = !same_as<UserType_, empty>;
using UserType = UserType_;
using PrimitiveUserType = smallest_unsigned_integer_type_for_width<size_of<UserType> * 8>;
static_assert(trivially_copy_constructible<UserType>);
static_assert(trivially_destructible<UserType>);
static_assert(size_of<UserType> == size_of<PrimitiveUserType>, "UserType should be of size 1, 2 or 4");
static constexpr int kUserBits = []() static -> int {
if constexpr (!kHasUser) { return 0; };
return kUserBits_ == 0 ? size_of<UserType> * 8 : kUserBits_;
}();
static_assert(kUserBits <= size_of<UserType> * 8);
static constexpr int kIndexBits = kIndexBits_;
static constexpr int kGenBits = kGenBits_;
static_assert(kIndexBits + kGenBits + kUserBits <= 63);
using HandleType = smallest_unsigned_integer_type_for_width<kIndexBits + kGenBits + kUserBits + 1>;
static constexpr int kGenShift = kIndexBits;
static constexpr int kUserShift = kIndexBits + kGenBits;
static constexpr HandleType kValidMask = HandleType{1} << (size_of<HandleType> * 8 - 1);
static constexpr HandleType kIndexMask = (HandleType{1} << kIndexBits) - 1;
static constexpr HandleType kGenMask = ((HandleType{1} << kGenBits) - 1) << kGenShift;
static constexpr HandleType kUserMask = ((HandleType{1} << kUserBits) - 1) << kUserShift;
static constexpr HandleType kNiche = static_cast<HandleType>(~uint64_t{kValidMask});
static constexpr uint64_t kMaxGen = (uint64_t{1} << kGenBits) - 1;
static constexpr uint64_t kMaxIndex = (uint64_t{1} << kIndexBits) - 1;
};
template<
int kIndexBits_,
int kGenBits_,
typename UserType_ = empty,
int kUserBits_ = 0
>
class index_pool_handle
{
public:
using config = index_pool_config<kIndexBits_, kGenBits_, UserType_, kUserBits_>;
private:
config::HandleType m_handle{};
public:
constexpr index_pool_handle() = default;
constexpr explicit index_pool_handle(niche_t)
: m_handle{config::kNiche}
{}
constexpr index_pool_handle(uint64_t index, uint64_t gen)
requires (!config::kHasUser)
: m_handle{static_cast<config::HandleType>(
config::kValidMask |
(index & config::kIndexMask) |
((gen << config::kGenShift) & config::kGenMask))}
{
ASL_ASSERT((index & uint64_t{config::kIndexMask}) == index);
ASL_ASSERT((gen & (uint64_t{config::kGenMask} >> config::kGenShift)) == gen);
}
constexpr index_pool_handle(uint64_t index, uint64_t gen, config::UserType user)
requires config::kHasUser
: m_handle{static_cast<config::HandleType>(
config::kValidMask |
(index & config::kIndexMask) |
((gen << config::kGenShift) & config::kGenMask) |
((static_cast<config::HandleType>(bit_cast<typename config::PrimitiveUserType>(user)) << config::kUserShift) & config::kUserMask))}
{
ASL_ASSERT((index & uint64_t{config::kIndexMask}) == index);
ASL_ASSERT((gen & (uint64_t{config::kGenMask} >> config::kGenShift)) == gen);
ASL_ASSERT((bit_cast<typename config::PrimitiveUserType>(user) & (uint64_t{config::kUserMask} >> config::kUserShift)) == bit_cast<typename config::PrimitiveUserType>(user));
}
constexpr bool is_null(this index_pool_handle self)
{
return !(self.m_handle & config::kValidMask);
}
constexpr uint64_t index(this index_pool_handle self)
{
return self.m_handle & config::kIndexMask;
}
constexpr uint64_t gen(this index_pool_handle self)
{
return (self.m_handle & config::kGenMask) >> config::kGenShift;
}
constexpr config::UserType user(this index_pool_handle self)
{
return bit_cast<typename config::UserType>(static_cast<config::PrimitiveUserType>(
((self.m_handle & config::kUserMask) >> config::kUserShift)));
}
constexpr bool operator==(this index_pool_handle self, index_pool_handle other) = default;
constexpr bool operator==(this index_pool_handle self, niche_t)
{
return self.m_handle == config::kNiche;
}
};
template<
int kIndexBits_,
int kGenBits_,
typename UserType_,
int kUserBits_
>
struct is_uniquely_represented<index_pool_handle<kIndexBits_, kGenBits_, UserType_, kUserBits_>> : true_type {};
template<
int kIndexBits_,
int kGenBits_,
typename UserType_ = empty,
int kUserBits_ = 0,
typename Payload = empty,
allocator Allocator = DefaultAllocator
>
class IndexPool
{
public:
using handle = index_pool_handle<kIndexBits_, kGenBits_, UserType_, kUserBits_>;
private:
using config = handle::config;
using internal_handle = index_pool_handle<
kIndexBits_,
kGenBits_,
typename config::PrimitiveUserType,
kUserBits_>;
static constexpr bool kHasPayload = !same_as<Payload, empty>;
// @Todo Remove need for default constructible & trivially destructible for payload
// Use maybe_uninit for it
static_assert(default_constructible<Payload>);
static_assert(copy_constructible<Payload>);
static_assert(trivially_destructible<Payload>);
struct Slot
{
bool is_end_of_list : 1;
bool is_active : 1;
internal_handle handle;
ASL_NO_UNIQUE_ADDRESS Payload payload;
};
chunked_buffer<Slot, 256, Allocator> m_slots;
// We only use the index, this is essentially the head of the linked
// list to the first available slot.
// Then the index of each slot points to the next available one.
internal_handle m_first_available;
void allocate_new_slot()
{
const auto new_index = static_cast<uint64_t>(m_slots.size());
if (new_index > config::kMaxIndex) { return; }
const internal_handle new_handle = internal_handle(new_index, 0, 0);
m_slots.push(Slot{
.is_end_of_list = true,
.is_active = false,
.handle = new_handle,
.payload = Payload{}
});
m_first_available = new_handle;
}
option<internal_handle> acquire_handle(const Payload& payload)
{
if (m_first_available.is_null())
{
allocate_new_slot();
}
if (m_first_available.is_null())
{
return nullopt;
}
auto index = m_first_available.index();
Slot& slot = m_slots[static_cast<isize_t>(index)];
ASL_ASSERT(!slot.is_active);
m_first_available = slot.is_end_of_list ? internal_handle{} : slot.handle;
slot.is_active = true;
slot.payload = payload;
return internal_handle(index, slot.handle.gen(), 0);
}
auto get_slot_if_valid(this auto&& self, handle h)
-> copy_const_t<decltype(self), Slot>*
{
if (h.is_null()) { return nullptr; }
auto index = static_cast<isize_t>(h.index());
if (index < 0 || index >= self.m_slots.size()) { return nullptr; }
auto& slot = self.m_slots[index];
if (!slot.is_active || slot.handle.gen() != h.gen())
{
return nullptr;
}
return &slot;
}
public:
IndexPool() requires default_constructible<Allocator> = default;
explicit IndexPool(Allocator allocator) : m_slots{std::move(allocator)} {}
[[nodiscard]] bool is_full() const
{
return m_first_available.is_null()
&& static_cast<uint64_t>(m_slots.size()) > config::kMaxIndex;
}
option<handle> acquire()
requires (!kHasPayload && !config::kHasUser)
{
return acquire_handle({}).transform([](internal_handle h)
{
return handle(h.index(), h.gen());
});
}
handle acquire_ensure()
requires (!kHasPayload && !config::kHasUser)
{
auto opt = acquire();
ASL_ASSERT_RELEASE(opt.has_value());
return opt.value();
}
option<handle> acquire(const Payload& payload)
requires (kHasPayload && !config::kHasUser)
{
return acquire_handle(payload).transform([](internal_handle h)
{
return handle(h.index(), h.gen());
});
}
handle acquire_ensure(const Payload& payload)
requires (kHasPayload && !config::kHasUser)
{
auto opt = acquire(payload);
ASL_ASSERT_RELEASE(opt.has_value());
return opt.value();
}
option<handle> acquire(config::UserType user)
requires (!kHasPayload && config::kHasUser)
{
return acquire_handle({}).transform([user](internal_handle h)
{
return handle(h.index(), h.gen(), user);
});
}
handle acquire_ensure(config::UserType user)
requires (!kHasPayload && config::kHasUser)
{
auto opt = acquire(user);
ASL_ASSERT_RELEASE(opt.has_value());
return opt.value();
}
option<handle> acquire(config::UserType user, const Payload& payload)
requires (kHasPayload && config::kHasUser)
{
return acquire_handle(payload).transform([user](internal_handle h)
{
return handle(h.index(), h.gen(), user);
});
}
handle acquire_ensure(config::UserType user, const Payload& payload)
requires (kHasPayload && config::kHasUser)
{
auto opt = acquire(user, payload);
ASL_ASSERT_RELEASE(opt.has_value());
return opt.value();
}
// @Todo Add a policy to abandon slots that reached max generation
void release(handle h)
{
if (Slot* slot = get_slot_if_valid(h); slot != nullptr)
{
const uint64_t next_gen = h.gen() == config::kMaxGen ? 0 : h.gen() + 1;
slot->is_active = false;
if (m_first_available.is_null())
{
slot->is_end_of_list = true;
slot->handle = internal_handle(h.index(), next_gen, 0);
}
else
{
slot->is_end_of_list = false;
slot->handle = internal_handle(m_first_available.index(), next_gen, 0);
}
m_first_available = internal_handle(h.index(), 0, 0);
}
}
bool is_valid(handle h) const
{
return get_slot_if_valid(h) != nullptr;
}
const Payload* get_payload(handle h) const
requires kHasPayload
{
if (const Slot* slot = get_slot_if_valid(h); slot != nullptr)
{
return &slot->payload;
}
return nullptr;
}
option<Payload> exchange_payload(handle h, Payload new_payload)
requires kHasPayload
{
if (Slot* slot = get_slot_if_valid(h); slot != nullptr)
{
return asl::exchange(slot->payload, new_payload);
}
return nullopt;
}
};
} // namespace asl

View File

@ -0,0 +1,348 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/handle_pool/index_pool.hpp"
#include "asl/hashing/hash.hpp"
enum Flags: uint8_t {
kFlag0 = 0,
kFlag1 = 1,
kFlag2 = 2,
};
using Cfg1 = asl::index_pool_config<4, 3>;
static_assert(!Cfg1::kHasUser);
static_assert(Cfg1::kUserBits == 0);
static_assert(asl::same_as<Cfg1::HandleType, uint8_t>);
static_assert(Cfg1::kValidMask == uint8_t{0x80});
static_assert(Cfg1::kIndexMask == uint8_t{0x0f});
static_assert(Cfg1::kGenMask == uint8_t{0x70});
static_assert(Cfg1::kGenShift == 4);
static_assert(Cfg1::kMaxGen == 7);
static_assert(Cfg1::kMaxIndex == 15);
using Cfg2 = asl::index_pool_config<5, 5, Flags>;
static_assert(Cfg2::kHasUser);
static_assert(Cfg2::kUserBits == 8);
static_assert(asl::same_as<Cfg2::PrimitiveUserType, uint8_t>);
static_assert(asl::same_as<Cfg2::HandleType, uint32_t>);
static_assert(Cfg2::kValidMask == uint32_t{0x8000'0000});
static_assert(Cfg2::kIndexMask == uint32_t{0x0000'001f});
static_assert(Cfg2::kGenMask == uint32_t{0x0000'03e0});
static_assert(Cfg2::kUserMask == uint32_t{0x0003'fc00});
static_assert(Cfg2::kGenShift == 5);
static_assert(Cfg2::kUserShift == 10);
static_assert(Cfg2::kMaxGen == 31);
static_assert(Cfg2::kMaxIndex == 31);
using Cfg3 = asl::index_pool_config<5, 6, Flags, 4>;
static_assert(Cfg3::kHasUser);
static_assert(Cfg3::kUserBits == 4);
static_assert(asl::same_as<Cfg3::PrimitiveUserType, uint8_t>);
static_assert(asl::same_as<Cfg3::HandleType, uint16_t>);
static_assert(Cfg3::kValidMask == uint16_t{0x8000});
static_assert(Cfg3::kIndexMask == uint16_t{0x001f});
static_assert(Cfg3::kGenMask == uint16_t{0x07e0});
static_assert(Cfg3::kUserMask == uint16_t{0x7800});
static_assert(Cfg3::kGenShift == 5);
static_assert(Cfg3::kUserShift == 11);
static_assert(Cfg3::kMaxGen == 63);
static_assert(Cfg3::kMaxIndex == 31);
static_assert(asl::default_constructible<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_copy_constructible<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_move_constructible<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_copy_assignable<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_move_assignable<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::trivially_destructible<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::hashable<asl::index_pool_handle<5, 5, uint8_t>>);
static_assert(asl::has_niche<asl::index_pool_handle<5, 5, uint8_t>>);
ASL_TEST(default_is_invalid)
{
const asl::index_pool_handle<5, 5, uint8_t> idx;
ASL_TEST_EXPECT(idx.is_null());
}
ASL_TEST(niche_is_invalid)
{
const asl::index_pool_handle<5, 5, uint8_t> idx{asl::niche_t{}};
ASL_TEST_EXPECT(idx.is_null());
}
ASL_TEST(construct)
{
const asl::index_pool_handle<5, 5> idx(9, 11);
ASL_TEST_EXPECT(!idx.is_null());
ASL_TEST_EXPECT(idx.index() == 9);
ASL_TEST_EXPECT(idx.gen() == 11);
}
ASL_TEST(construct_user)
{
const asl::index_pool_handle<5, 5, Flags, 4> idx(9, 11, kFlag2);
ASL_TEST_EXPECT(!idx.is_null());
ASL_TEST_EXPECT(idx.index() == 9);
ASL_TEST_EXPECT(idx.gen() == 11);
ASL_TEST_EXPECT(idx.user() == kFlag2);
static_assert(asl::same_as<Flags, decltype(idx.user())>);
}
ASL_TEST(compare) // NOLINT
{
const asl::index_pool_handle<5, 5, Flags, 4> idx_default;
const asl::index_pool_handle<5, 5, Flags, 4> idx0;
const asl::index_pool_handle<5, 5, Flags, 4> idx1(9, 11, kFlag2);
const asl::index_pool_handle<5, 5, Flags, 4> idx2(9, 11, kFlag1);
const asl::index_pool_handle<5, 5, Flags, 4> idx3(9, 11, kFlag1);
const asl::index_pool_handle<5, 5, Flags, 4> idx4(9, 10, kFlag2);
const asl::index_pool_handle<5, 5, Flags, 4> idx5(8, 11, kFlag2);
ASL_TEST_EXPECT(idx0 == idx_default);
ASL_TEST_EXPECT(idx0 != idx1);
ASL_TEST_EXPECT(idx0 != idx2);
ASL_TEST_EXPECT(idx0 != idx3);
ASL_TEST_EXPECT(idx0 != idx4);
ASL_TEST_EXPECT(idx0 != idx5);
ASL_TEST_EXPECT(idx1 != idx2);
ASL_TEST_EXPECT(idx1 != idx3);
ASL_TEST_EXPECT(idx1 != idx4);
ASL_TEST_EXPECT(idx1 != idx5);
ASL_TEST_EXPECT(idx2 == idx3);
ASL_TEST_EXPECT(idx2 != idx4);
ASL_TEST_EXPECT(idx2 != idx5);
ASL_TEST_EXPECT(idx3 != idx4);
ASL_TEST_EXPECT(idx3 != idx5);
ASL_TEST_EXPECT(idx4 != idx5);
}
ASL_TEST(hashing) // NOLINT
{
const asl::index_pool_handle<4, 4> idx0(asl::niche_t{});
const asl::index_pool_handle<4, 4> idx1{};
const asl::index_pool_handle<4, 4> idx2(1, 1);
const asl::index_pool_handle<4, 4> idx3(1, 1);
const asl::index_pool_handle<4, 4> idx4(2, 1);
const asl::index_pool_handle<4, 4> idx5(1, 2);
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx1));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx2));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx2));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx2) == asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx4) != asl::hash_value(idx5));
}
ASL_TEST(hashing_in_option) // NOLINT
{
const asl::option<asl::index_pool_handle<4, 4>> idx0;
const asl::option<asl::index_pool_handle<4, 4>> idx1{asl::index_pool_handle<4, 4>()};
const asl::option<asl::index_pool_handle<4, 4>> idx2{asl::index_pool_handle<4, 4>(1, 1)};
const asl::option<asl::index_pool_handle<4, 4>> idx3{asl::index_pool_handle<4, 4>(1, 1)};
const asl::option<asl::index_pool_handle<4, 4>> idx4{asl::index_pool_handle<4, 4>(2, 1)};
const asl::option<asl::index_pool_handle<4, 4>> idx5{asl::index_pool_handle<4, 4>(1, 2)};
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx1));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx2));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx0) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx2));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx1) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx2) == asl::hash_value(idx3));
ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx2) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx4));
ASL_TEST_EXPECT(asl::hash_value(idx3) != asl::hash_value(idx5));
ASL_TEST_EXPECT(asl::hash_value(idx4) != asl::hash_value(idx5));
}
ASL_TEST(simple_pool) // NOLINT
{
using Pool = asl::IndexPool<8, 8>;
Pool pool;
auto a = pool.acquire_ensure();
auto b = pool.acquire_ensure();
ASL_TEST_EXPECT(!a.is_null());
ASL_TEST_EXPECT(!b.is_null());
ASL_TEST_EXPECT(a.index() == 0);
ASL_TEST_EXPECT(b.index() == 1);
ASL_TEST_EXPECT(a.gen() == 0);
ASL_TEST_EXPECT(b.gen() == 0);
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(a != b);
ASL_TEST_EXPECT(!pool.is_valid({}));
ASL_TEST_EXPECT(pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
pool.release(a);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
auto c = pool.acquire_ensure();
ASL_TEST_EXPECT(!c.is_null());
ASL_TEST_EXPECT(c.index() == 0);
ASL_TEST_EXPECT(c.gen() == 1);
ASL_TEST_EXPECT(a != c);
ASL_TEST_EXPECT(b != c);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_valid(c));
pool.release(b);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(!pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_valid(c));
pool.release(c);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(!pool.is_valid(b));
ASL_TEST_EXPECT(!pool.is_valid(c));
}
ASL_TEST(pool_acquire_release_a_lot)
{
using Pool = asl::IndexPool<3, 3>;
Pool pool;
for (int i = 0; i < 80; ++i)
{
pool.release(pool.acquire_ensure());
ASL_TEST_EXPECT(!pool.is_full());
}
}
ASL_TEST(pool_acquire_past_capacity)
{
using Pool = asl::IndexPool<3, 3>;
Pool pool;
auto idx = pool.acquire_ensure();
for (int i = 0; i < 7; ++i)
{
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(pool.acquire().has_value());
}
for (int i = 0; i < 8; ++i)
{
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(!pool.acquire().has_value());
}
pool.release(idx);
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(pool.acquire().has_value());
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(!pool.acquire().has_value());
}
ASL_TEST(pool_with_user_type)
{
using Pool = asl::IndexPool<8, 8, Flags, 4>;
Pool pool;
const auto a = pool.acquire_ensure(kFlag0);
const auto b = pool.acquire_ensure(kFlag2);
ASL_TEST_EXPECT(a.user() == kFlag0);
ASL_TEST_EXPECT(b.user() == kFlag2);
}
ASL_TEST(pool_with_payload)
{
using Pool = asl::IndexPool<8, 8, asl::empty, 0, int>;
Pool pool;
const auto a = pool.acquire_ensure(37);
const auto b = pool.acquire_ensure(28);
ASL_TEST_EXPECT(*pool.get_payload(a) == 37);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
ASL_TEST_EXPECT(pool.get_payload({}) == nullptr);
pool.release(a);
ASL_TEST_EXPECT(pool.get_payload(a) == nullptr);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
}
ASL_TEST(pool_with_payload_and_user)
{
using Pool = asl::IndexPool<8, 8, Flags, 8, int>;
Pool pool;
const auto a = pool.acquire_ensure(kFlag1, 37);
const auto b = pool.acquire_ensure(kFlag0, 28);
ASL_TEST_EXPECT(a.user() == kFlag1);
ASL_TEST_EXPECT(b.user() == kFlag0);
ASL_TEST_EXPECT(*pool.get_payload(a) == 37);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
ASL_TEST_EXPECT(pool.get_payload({}) == nullptr);
pool.release(a);
ASL_TEST_EXPECT(pool.get_payload(a) == nullptr);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
}
ASL_TEST(pool_exchange_payload)
{
using Pool = asl::IndexPool<8, 8, asl::empty, 0, int>;
Pool pool;
const auto a = pool.acquire_ensure(37);
const auto b = pool.acquire_ensure(28);
ASL_TEST_EXPECT(*pool.get_payload(a) == 37);
ASL_TEST_EXPECT(*pool.get_payload(b) == 28);
ASL_TEST_EXPECT(!pool.exchange_payload({}, 0).has_value());
ASL_TEST_EXPECT(pool.exchange_payload(a, 101).value() == 37);
ASL_TEST_EXPECT(pool.exchange_payload(b, 102).value() == 28);
ASL_TEST_EXPECT(*pool.get_payload(a) == 101);
ASL_TEST_EXPECT(*pool.get_payload(b) == 102);
}

View File

@ -0,0 +1,158 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/handle_pool/index_pool.hpp"
#include "asl/memory/allocator.hpp"
#include "asl/containers/chunked_buffer.hpp"
namespace asl
{
// @Todo If we want the allocator to be non-copyable, we could
// introduce a reference allocator type that is copyable, and store
// the "main" allocator in the pool.
template<
is_object T,
int kIndexBits,
int kGenBits,
typename UserType = empty,
int kUserBits = 0,
isize_t kChunkSize = 32,
allocator Allocator = DefaultAllocator>
requires copyable<Allocator>
class SparseHandlePool
{
using ThisIndexPool = IndexPool<kIndexBits, kGenBits, UserType, kUserBits, empty, Allocator>;
struct Slot
{
ThisIndexPool::handle h;
maybe_uninit<T> obj;
Slot() = default;
ASL_DELETE_COPY_MOVE(Slot);
~Slot()
{
if (!h.is_null())
{
obj.destroy_unsafe();
}
}
};
using Buffer = chunked_buffer<Slot, kChunkSize, Allocator>;
ThisIndexPool m_index_pool{};
Buffer m_buffer{};
using config = ThisIndexPool::handle::config;
template<typename... Args>
void set_object(ThisIndexPool::handle h, Args&&... args)
{
const auto index = static_cast<isize_t>(h.index());
if (m_buffer.size() <= index)
{
m_buffer.resize(index + 1);
}
m_buffer[index].h = h;
m_buffer[index].obj.construct_unsafe(std::forward<Args>(args)...);
}
public:
using handle = ThisIndexPool::handle;
SparseHandlePool() requires default_constructible<Allocator> = default;
explicit SparseHandlePool(const Allocator& allocator)
: m_index_pool(allocator)
, m_buffer(allocator)
{}
ASL_DELETE_COPY(SparseHandlePool);
ASL_DEFAULT_MOVE(SparseHandlePool);
~SparseHandlePool() = default;
[[nodiscard]] bool is_full() const
{
return m_index_pool.is_full();
}
bool is_valid(handle h) const
{
return m_index_pool.is_valid(h);
}
template<typename... Args>
option<handle> acquire(config::UserType user, Args&&... args)
requires config::kHasUser && constructible_from<T, Args&&...>
{
if (is_full()) { return nullopt; }
const auto handle = m_index_pool.acquire_ensure(user);
set_object(handle, std::forward<Args>(args)...);
return handle;
}
template<typename... Args>
option<handle> acquire(Args&&... args)
requires (!config::kHasUser) && constructible_from<T, Args&&...>
{
if (is_full()) { return nullopt; }
const auto handle = m_index_pool.acquire_ensure();
set_object(handle, std::forward<Args>(args)...);
return handle;
}
template<typename... Args>
handle acquire_ensure(config::UserType user, Args&&... args)
requires config::kHasUser && constructible_from<T, Args&&...>
{
ASL_ASSERT_RELEASE(!is_full());
const auto handle = m_index_pool.acquire_ensure(user);
set_object(handle, std::forward<Args>(args)...);
return handle;
}
template<typename... Args>
handle acquire_ensure(Args&&... args)
requires (!config::kHasUser) && constructible_from<T, Args&&...>
{
ASL_ASSERT_RELEASE(!is_full());
const auto handle = m_index_pool.acquire_ensure();
set_object(handle, std::forward<Args>(args)...);
return handle;
}
void release(handle h)
{
if (!is_valid(h)) { return; }
auto& slot = m_buffer[static_cast<isize_t>(h.index())];
slot.h = {};
slot.obj.destroy_unsafe();
m_index_pool.release(h);
}
auto get(this auto&& self, handle h)
-> copy_const_t<un_ref_t<decltype(self)>, T>*
{
if (!self.is_valid(h)) { return nullptr; }
return &std::forward<decltype(self)>(self).m_buffer[static_cast<isize_t>(h.index())]
.obj.as_init_unsafe();
}
auto get_ensure(this auto&& self, handle h)
-> copy_cref_t<decltype(self), T>
{
ASL_ASSERT_RELEASE(self.is_valid(h));
return std::forward<decltype(self)>(self).m_buffer[static_cast<isize_t>(h.index())]
.obj.as_init_unsafe();
}
};
} // namespace asl

View File

@ -0,0 +1,117 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
#include "asl/handle_pool/sparse_handle_pool.hpp"
ASL_TEST(acquire_release) // NOLINT
{
asl::SparseHandlePool<int, 1, 1> pool;
ASL_TEST_EXPECT(!pool.is_full());
const auto a = pool.acquire_ensure(6);
const auto b = pool.acquire_ensure(7);
ASL_TEST_EXPECT(pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(pool.get_ensure(a) == 6);
ASL_TEST_EXPECT(pool.get_ensure(b) == 7);
pool.release(a);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(!pool.is_full());
ASL_TEST_EXPECT(pool.get(a) == nullptr);
ASL_TEST_EXPECT(*pool.get(b) == 7);
const auto c = pool.acquire_ensure(8);
ASL_TEST_EXPECT(!pool.is_valid(a));
ASL_TEST_EXPECT(pool.is_valid(b));
ASL_TEST_EXPECT(pool.is_valid(c));
ASL_TEST_EXPECT(pool.is_full());
ASL_TEST_EXPECT(*pool.get(b) == 7);
ASL_TEST_EXPECT(*pool.get(c) == 8);
ASL_TEST_EXPECT(pool.get(a) == nullptr);
}
ASL_TEST(element_destructor)
{
asl::SparseHandlePool<DestructorObserver, 8, 8> pool;
bool d[3]{};
const auto d0 = pool.acquire_ensure(&d[0]);
const auto d1 = pool.acquire_ensure(&d[1]);
const auto d2 = pool.acquire_ensure(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d1);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d0);
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d2);
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
ASL_TEST(destructor)
{
bool d[3]{};
{
asl::SparseHandlePool<DestructorObserver, 8, 8> pool;
pool.acquire_ensure(&d[0]);
const auto d1 = pool.acquire_ensure(&d[1]);
pool.acquire_ensure(&d[2]);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(!d[1]);
ASL_TEST_EXPECT(!d[2]);
pool.release(d1);
ASL_TEST_EXPECT(!d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(!d[2]);
}
ASL_TEST_EXPECT(d[0]);
ASL_TEST_EXPECT(d[1]);
ASL_TEST_EXPECT(d[2]);
}
enum Flags : uint8_t
{
kFlag1 = 1,
kFlag2 = 2,
};
ASL_TEST(user_type)
{
asl::SparseHandlePool<int, 8, 8, Flags> pool;
auto a = pool.acquire_ensure(kFlag2, 22);
auto b = pool.acquire_ensure(kFlag1, 11);
ASL_TEST_EXPECT(pool.get_ensure(a) == 22);
ASL_TEST_EXPECT(pool.get_ensure(b) == 11);
ASL_TEST_EXPECT(a.user() == kFlag2);
ASL_TEST_EXPECT(b.user() == kFlag1);
}

View File

@ -87,9 +87,26 @@ void log(level l, const source_location& sl, string_view fmt, const Args&... arg
} // namespace asl::log
// @Todo Compile-time configuration of logging
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 4
#define ASL_LOG_DEBUG(...) ::asl::log::log(::asl::log::kDebug, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif
#define ASL_LOG_DEBUG(...) ::asl::log::log(::asl::log::kDebug, ::asl::source_location{}, __VA_ARGS__)
#define ASL_LOG_INFO(...) ::asl::log::log(::asl::log::kInfo, ::asl::source_location{}, __VA_ARGS__)
#define ASL_LOG_WARNING(...) ::asl::log::log(::asl::log::kWarning, ::asl::source_location{}, __VA_ARGS__)
#define ASL_LOG_ERROR(...) ::asl::log::log(::asl::log::kError, ::asl::source_location{}, __VA_ARGS__)
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 3
#define ASL_LOG_INFO(...) ::asl::log::log(::asl::log::kInfo, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 2
#define ASL_LOG_WARNING(...) ::asl::log::log(::asl::log::kWarning, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif
#if !defined(ASL_LOG_LEVEL) || ASL_LOG_LEVEL >= 1
#define ASL_LOG_ERROR(...) ::asl::log::log(::asl::log::kError, ::asl::source_location{}, __VA_ARGS__)
#else
#define ASL_LOG_DEBUG(...)
#endif

View File

@ -4,6 +4,7 @@
#include "asl/memory/allocator.hpp"
#include "asl/base/assert.hpp"
#include "asl/base/numeric.hpp"
#include "asl/memory/layout.hpp"
#include <cstdlib>
@ -14,11 +15,11 @@
void* asl::GlobalHeap::alloc(const layout& layout)
{
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
void* ptr = ::_aligned_malloc(
static_cast<size_t>(layout.size),
static_cast<size_t>(layout.align));
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
void* ptr = ::aligned_alloc(
static_cast<size_t>(layout.align),
static_cast<size_t>(layout.size));
@ -29,11 +30,11 @@ void* asl::GlobalHeap::alloc(const layout& layout)
void* asl::GlobalHeap::realloc(void* old_ptr, [[maybe_unused]] const layout& old_layout, const layout& new_layout)
{
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
return ::_aligned_realloc(old_ptr,
static_cast<size_t>(new_layout.size),
static_cast<size_t>(new_layout.align));
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
if (new_layout.align <= old_layout.align)
{
void* new_ptr = ::realloc(old_ptr, static_cast<size_t>(new_layout.size));
@ -50,9 +51,9 @@ void* asl::GlobalHeap::realloc(void* old_ptr, [[maybe_unused]] const layout& old
void asl::GlobalHeap::dealloc(void* ptr, const layout&)
{
#if ASL_OS_WINDOWS
#if defined(ASL_OS_WINDOWS)
::_aligned_free(ptr);
#elif ASL_OS_LINUX
#elif defined(ASL_OS_LINUX)
::free(ptr);
#endif
}

View File

@ -40,6 +40,21 @@ T* alloc_new(allocator auto& a, auto&&... args)
return construct_at<T>(ptr, std::forward<decltype(args)>(args)...);
}
template<typename T>
T* alloc_uninit(allocator auto& a)
requires trivially_default_constructible<T>
{
void* ptr = a.alloc(layout::of<T>());
return reinterpret_cast<T*>(ptr); // NOLINT(*-reinterpret-cast)
}
template<typename T>
T* alloc_uninit_unsafe(allocator auto& a)
{
void* ptr = a.alloc(layout::of<T>());
return reinterpret_cast<T*>(ptr); // NOLINT(*-reinterpret-cast)
}
template<typename T>
void alloc_delete(allocator auto& a, T* ptr)
{

View File

@ -45,6 +45,24 @@ cc_library(
visibility = ["//visibility:public"],
)
cc_library(
name = "parse_number",
hdrs = [
"parse_number.hpp",
],
srcs = [
"parse_number_float.cpp",
"parse_number.cpp",
],
deps = [
"//asl/base",
"//asl/types:status",
":string_view",
"//vendor/fast_float",
],
visibility = ["//visibility:public"],
)
[cc_test(
name = "%s_tests" % name,
srcs = [
@ -54,6 +72,7 @@ cc_library(
":string",
":string_builder",
":string_view",
":parse_number",
"//asl/tests:utils",
"//asl/testing",
],
@ -61,4 +80,5 @@ cc_library(
"string",
"string_view",
"string_builder",
"parse_number",
]]

View File

@ -0,0 +1,162 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/strings/parse_number.hpp"
namespace asl
{
bool parse_float_impl(const char** begin, const char* end, float*);
bool parse_double_impl(const char** begin, const char* end, double*);
} // namespace asl
asl::status_or<asl::parse_number_result<float32_t>> asl::parse_float32(asl::string_view sv)
{
const auto* begin = sv.data();
// NOLINTNEXTLINE(*-pointer-arithmetic)
const auto* end = begin + sv.size();
if (float32_t value{}; parse_float_impl(&begin, end, &value))
{
return parse_number_result<float32_t>{
.value = value,
.remaining = string_view{begin, end},
};
}
return invalid_argument_error();
}
asl::status_or<asl::parse_number_result<float64_t>> asl::parse_float64(asl::string_view sv)
{
const auto* begin = sv.data();
// NOLINTNEXTLINE(*-pointer-arithmetic)
const auto* end = begin + sv.size();
if (float64_t value{}; parse_double_impl(&begin, end, &value))
{
return parse_number_result<float64_t>{
.value = value,
.remaining = string_view{begin, end},
};
}
return invalid_argument_error();
}
namespace
{
constexpr int8_t kBase16Table[256] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
// @Todo Maybe monomorph this for common bases (2, 16, 10)?
template<typename T>
asl::status_or<asl::parse_number_result<T>> parse_integer(asl::string_view sv, int base)
{
ASL_ASSERT(base >= 2 && base <= 16);
if (sv.is_empty()) { return asl::invalid_argument_error(); }
T value = 0;
bool is_negative = false;
if (asl::is_signed_integer<T> && sv[0] == '-')
{
is_negative = true;
sv = sv.substr(1);
}
isize_t cursor = 0;
while (cursor < sv.size())
{
// NOLINTNEXTLINE(*-array-index)
int8_t digit = kBase16Table[static_cast<uint8_t>(sv[cursor])];
if (digit < 0 || digit >= base) { break; }
if (__builtin_mul_overflow(value, static_cast<T>(base), &value))
{
return asl::invalid_argument_error("overflow");
}
if (asl::is_signed_integer<T> && is_negative)
{
digit = static_cast<int8_t>(-digit);
}
if (__builtin_add_overflow(value, static_cast<T>(digit), &value))
{
return asl::invalid_argument_error("overflow");
}
cursor += 1;
}
if (cursor == 0)
{
return asl::invalid_argument_error();
}
return asl::parse_number_result<T>{
.value = value,
.remaining = sv.substr(cursor),
};
}
} // anonymous namespace
asl::status_or<asl::parse_number_result<uint8_t>> asl::parse_uint8(string_view sv, int base)
{
return parse_integer<uint8_t>(sv, base);
}
asl::status_or<asl::parse_number_result<uint16_t>> asl::parse_uint16(string_view sv, int base)
{
return parse_integer<uint16_t>(sv, base);
}
asl::status_or<asl::parse_number_result<uint32_t>> asl::parse_uint32(string_view sv, int base)
{
return parse_integer<uint32_t>(sv, base);
}
asl::status_or<asl::parse_number_result<uint64_t>> asl::parse_uint64(string_view sv, int base)
{
return parse_integer<uint64_t>(sv, base);
}
asl::status_or<asl::parse_number_result<int8_t>> asl::parse_int8(string_view sv, int base)
{
return parse_integer<int8_t>(sv, base);
}
asl::status_or<asl::parse_number_result<int16_t>> asl::parse_int16(string_view sv, int base)
{
return parse_integer<int16_t>(sv, base);
}
asl::status_or<asl::parse_number_result<int32_t>> asl::parse_int32(string_view sv, int base)
{
return parse_integer<int32_t>(sv, base);
}
asl::status_or<asl::parse_number_result<int64_t>> asl::parse_int64(string_view sv, int base)
{
return parse_integer<int64_t>(sv, base);
}

View File

@ -0,0 +1,35 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/types/status_or.hpp"
#include "asl/strings/string_view.hpp"
#include "asl/base/float.hpp"
namespace asl
{
template<typename T>
struct parse_number_result
{
T value;
string_view remaining;
};
status_or<parse_number_result<float32_t>> parse_float32(string_view);
status_or<parse_number_result<float64_t>> parse_float64(string_view);
status_or<parse_number_result<uint8_t>> parse_uint8(string_view, int base = 10);
status_or<parse_number_result<uint16_t>> parse_uint16(string_view, int base = 10);
status_or<parse_number_result<uint32_t>> parse_uint32(string_view, int base = 10);
status_or<parse_number_result<uint64_t>> parse_uint64(string_view, int base = 10);
status_or<parse_number_result<int8_t>> parse_int8(string_view, int base = 10);
status_or<parse_number_result<int16_t>> parse_int16(string_view, int base = 10);
status_or<parse_number_result<int32_t>> parse_int32(string_view, int base = 10);
status_or<parse_number_result<int64_t>> parse_int64(string_view, int base = 10);
} // namespace asl

View File

@ -0,0 +1,35 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#include <fast_float.h>
#pragma clang diagnostic pop
// We need to isolate fast_float.h completely from asl
// because it conflicts with our redefinitions of things
// from the STL. In this case it's operator new, but there
// might be other conflicts.
#pragma clang diagnostic ignored "-Wmissing-prototypes"
namespace asl
{
extern bool parse_float_impl(const char** begin, const char* end, float* value)
{
auto res = fast_float::from_chars(*begin, end, *value);
*begin = res.ptr;
return res.ec == std::errc{};
}
extern bool parse_double_impl(const char** begin, const char* end, double* value)
{
auto res = fast_float::from_chars(*begin, end, *value);
*begin = res.ptr;
return res.ec == std::errc{};
}
} // namespace asl

View File

@ -0,0 +1,24 @@
a = ord('a')
f = ord('f')
A = ord('A')
F = ord('F')
n0 = ord('0')
n9 = ord('9')
output = ""
for i in range(0, 16):
for j in range(0, 16):
v = i * 16 + j
n = -1
if v >= a and v <= f:
n = v - a + 10
elif v >= A and v <= F:
n = v - A + 10
elif v >= n0 and v <= n9:
n = v - n0
output += f"{n:>2}, "
output += "\n"
print(output)

View File

@ -0,0 +1,201 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/strings/parse_number.hpp"
#include "asl/base/numeric.hpp"
#include "asl/testing/testing.hpp"
ASL_TEST(parse_float_error)
{
const asl::string_view sv = "this is not a number lmao";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_float_empty)
{
const asl::string_view sv = "";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_float_simple)
{
const asl::string_view sv = "3.1415";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F));
ASL_TEST_EXPECT(res.value().remaining.size() == 0);
}
ASL_TEST(parse_float_integer)
{
const asl::string_view sv = "31415";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 31415.0F);
ASL_TEST_EXPECT(res.value().remaining.size() == 0);
}
ASL_TEST(parse_float_scientific)
{
const asl::string_view sv = "314.15e-2";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F));
ASL_TEST_EXPECT(res.value().remaining.size() == 0);
}
ASL_TEST(parse_float_suffix)
{
const asl::string_view sv = "3.1415 yoyoyo";
auto res = asl::parse_float32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(asl::are_nearly_equal(res.value().value, 3.1415F));
ASL_TEST_EXPECT(res.value().remaining == " yoyoyo");
}
ASL_TEST(parse_int)
{
const asl::string_view sv = "926473";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 926473);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_int_negative)
{
const asl::string_view sv = "-926473";
auto res = asl::parse_int32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == -926473);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_int_suffix)
{
const asl::string_view sv = "926473 what's this then";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 926473);
ASL_TEST_EXPECT(res.value().remaining == " what's this then");
}
ASL_TEST(parse_uint_with_minus)
{
const asl::string_view sv = "-926473";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_int_with_only_minus)
{
const asl::string_view sv = "-@";
auto res = asl::parse_int32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_uint_invalid)
{
const asl::string_view sv = "abcd";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_uint_empty)
{
const asl::string_view sv = "";
auto res = asl::parse_uint32(sv);
ASL_TEST_EXPECT(!res.ok());
}
ASL_TEST(parse_uint_overflow)
{
ASL_TEST_EXPECT(!asl::parse_uint16("80000").ok());
ASL_TEST_EXPECT(!asl::parse_uint16("65536").ok());
}
ASL_TEST(parse_uint16_max)
{
const asl::string_view sv = "65535";
auto res = asl::parse_uint16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 65535);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_uint16_zero)
{
const asl::string_view sv = "0";
auto res = asl::parse_uint16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 0);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_uint16_zeros)
{
const asl::string_view sv = "00000";
auto res = asl::parse_uint16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 0);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_int_overflow)
{
ASL_TEST_EXPECT(!asl::parse_int16("80000").ok());
ASL_TEST_EXPECT(!asl::parse_int16("40000").ok());
ASL_TEST_EXPECT(!asl::parse_int16("32768").ok());
ASL_TEST_EXPECT(!asl::parse_int16("-80000").ok());
ASL_TEST_EXPECT(!asl::parse_int16("-40000").ok());
ASL_TEST_EXPECT(!asl::parse_int16("-32769").ok());
}
ASL_TEST(parse_int16_max)
{
const asl::string_view sv = "32767";
auto res = asl::parse_int16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 32767);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_int16_min)
{
const asl::string_view sv = "-32768";
auto res = asl::parse_int16(sv);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == -32768);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_hex)
{
const asl::string_view sv = "1000a";
auto res = asl::parse_uint32(sv, 16);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 65546);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_bin)
{
const asl::string_view sv = "101010";
auto res = asl::parse_uint32(sv, 2);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 42);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}
ASL_TEST(parse_oct)
{
const asl::string_view sv = "644";
auto res = asl::parse_uint32(sv, 8);
ASL_TEST_EXPECT(res.ok());
ASL_TEST_EXPECT(res.value().value == 6 * 64 + 4 * 8 + 4);
ASL_TEST_EXPECT(res.value().remaining.is_empty());
}

View File

@ -28,6 +28,13 @@ public:
, m_size{size}
{}
constexpr string_view(const char* begin, const char* end)
: m_data{begin}
, m_size{end - begin}
{
ASL_ASSERT(begin <= end);
}
template<isize_t kSize>
constexpr string_view(const char (&str)[kSize]) // NOLINT(*explicit*)
requires (kSize >= 1)

View File

@ -69,7 +69,7 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
asl::testing::Test* failed_head = nullptr;
for (auto* it = g_state.head; it != nullptr; it = it->m_next)
for (auto* it = g_state.head; it != nullptr;)
{
asl::eprint(GREEN("[ RUN ]") " {}\n", it->m_case_name);
@ -80,13 +80,17 @@ int main([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
asl::eprint(GREEN("[ OK ]") " {}\n", it->m_case_name);
pass += 1;
it = it->m_next;
}
else
{
asl::eprint(RED("[ FAILED ]") " {}\n", it->m_case_name);
fail += 1;
it->m_next = asl::exchange(failed_head, it);
auto* this_test = it;
it = it->m_next;
this_test->m_next = asl::exchange(failed_head, this_test);
}
}

View File

@ -9,10 +9,12 @@ package(
cc_library(
name = "utils",
hdrs = [
"counting_allocator.hpp",
"types.hpp",
],
deps = [
"//asl/base",
"//asl/memory:allocator",
],
visibility = ["//asl:__subpackages__"],
)

View File

@ -0,0 +1,48 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/memory/allocator.hpp"
struct CountingAllocator
{
struct Stats
{
isize_t alloc_count{};
isize_t realloc_count{};
isize_t dealloc_count{};
isize_t alive_bytes{};
[[nodiscard]] constexpr isize_t any_alloc_count() const
{
return alloc_count + realloc_count;
}
};
Stats* stats;
[[nodiscard]]
void* alloc(const asl::layout& layout) const
{
stats->alloc_count += 1;
stats->alive_bytes += layout.size;
return asl::GlobalHeap::alloc(layout);
}
void* realloc(void* ptr, const asl::layout& old, const asl::layout& new_layout) const
{
stats->realloc_count += 1;
stats->alive_bytes += new_layout.size - old.size;
return asl::GlobalHeap::realloc(ptr, old, new_layout);
}
void dealloc(void* ptr, const asl::layout& layout) const
{
stats->dealloc_count += 1;
stats->alive_bytes -= layout.size;
asl::GlobalHeap::dealloc(ptr, layout);
}
constexpr bool operator==(const CountingAllocator&) const { return true; }
};
static_assert(asl::allocator<CountingAllocator>);

View File

@ -77,7 +77,7 @@ struct DestructorObserver
{
if (this != &other)
{
destroyed = asl::exchange(other.destroyed, nullptr);
asl::swap(destroyed, other.destroyed);
}
return *this;
}

View File

@ -6,6 +6,18 @@ package(
default_applicable_licenses = ["//:license"],
)
cc_library(
name = "array",
hdrs = [
"array.hpp",
],
deps = [
"//asl/base",
"//asl/types:span",
],
visibility = ["//visibility:public"],
)
cc_library(
name = "box",
hdrs = [
@ -124,6 +136,16 @@ cc_test(
],
)
cc_test(
name = "array_tests",
srcs = ["array_tests.cpp"],
deps = [
"//asl/tests:utils",
"//asl/testing",
"//asl/types:array",
],
)
cc_test(
name = "box_tests",
srcs = ["box_tests.cpp"],

70
asl/types/array.hpp Normal file
View File

@ -0,0 +1,70 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#pragma once
#include "asl/base/assert.hpp"
#include "asl/base/meta.hpp"
#include "asl/base/utility.hpp"
#include "asl/types/span.hpp"
namespace asl
{
template<is_object T, isize_t kSize>
requires (kSize > 0)
struct array
{
T m_data[kSize];
[[nodiscard]] constexpr bool is_empty() const { return false; }
[[nodiscard]] constexpr isize_t size() const { return kSize; }
constexpr auto data(this auto&& self)
{
using return_type = copy_const_t<un_ref_t<decltype(self)>, T>*;
return static_cast<return_type>(self.m_data);
}
constexpr auto begin(this auto&& self)
{
return contiguous_iterator{self.data()};
}
constexpr auto end(this auto&& self)
{
return contiguous_iterator{self.data() + kSize};
}
template<isize_t kSpanSize>
requires (kSpanSize == kSize || kSpanSize == dynamic_size)
constexpr operator span<const T, kSpanSize>() const // NOLINT(*explicit*)
{
return as_span();
}
template<isize_t kSpanSize>
requires (kSpanSize == kSize || kSpanSize == dynamic_size)
constexpr operator span<T, kSpanSize>() // NOLINT(*explicit*)
{
return as_span();
}
constexpr auto as_span(this auto&& self)
{
using type = copy_const_t<un_ref_t<decltype(self)>, T>;
return span<type, kSize>{self.data(), self.size()};
}
constexpr auto&& operator[](this auto&& self, isize_t i)
{
ASL_ASSERT(i >= 0 && i <= self.size());
return std::forward_like<decltype(self)>(std::forward<decltype(self)>(self).data()[i]);
}
};
} // namespace asl

122
asl/types/array_tests.cpp Normal file
View File

@ -0,0 +1,122 @@
// Copyright 2025 Steven Le Rouzic
//
// SPDX-License-Identifier: BSD-3-Clause
#include "asl/types/array.hpp"
#include "asl/testing/testing.hpp"
#include "asl/tests/types.hpp"
static_assert(sizeof(asl::array<int32_t, 8>) == 32);
static_assert(asl::default_constructible<asl::array<int, 6>>);
static_assert(asl::trivially_default_constructible<asl::array<int, 6>>);
static_assert(asl::default_constructible<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_default_constructible<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_copy_constructible<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_copy_assignable<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_move_constructible<asl::array<TrivialType, 6>>);
static_assert(asl::trivially_move_assignable<asl::array<TrivialType, 6>>);
static_assert(asl::default_constructible<asl::array<TrivialTypeDefaultValue, 6>>);
static_assert(!asl::trivially_default_constructible<asl::array<TrivialTypeDefaultValue, 6>>);
static_assert(asl::trivially_destructible<asl::array<int, 6>>);
static_assert(asl::trivially_destructible<asl::array<TrivialType, 6>>);
static_assert(!asl::trivially_destructible<asl::array<WithDestructor, 6>>);
static_assert(asl::copyable<asl::array<Copyable, 6>>);
static_assert(!asl::copyable<asl::array<MoveableOnly, 6>>);
static_assert(!asl::copyable<asl::array<Pinned, 6>>);
static_assert(asl::moveable<asl::array<Copyable, 6>>);
static_assert(asl::moveable<asl::array<MoveableOnly, 6>>);
static_assert(!asl::moveable<asl::array<Pinned, 6>>);
ASL_TEST(construct_default)
{
asl::array<int, 4> arr{};
ASL_TEST_EXPECT(static_cast<void*>(&arr) == static_cast<void*>(arr.data()));
ASL_TEST_EXPECT(arr[0] == 0);
ASL_TEST_EXPECT(arr[1] == 0);
ASL_TEST_EXPECT(arr[2] == 0);
ASL_TEST_EXPECT(arr[3] == 0);
ASL_TEST_EXPECT(arr.data()[0] == 0); // NOLINT
ASL_TEST_EXPECT(arr.data()[1] == 0); // NOLINT
ASL_TEST_EXPECT(arr.data()[2] == 0); // NOLINT
ASL_TEST_EXPECT(arr.data()[3] == 0); // NOLINT
}
ASL_TEST(construct)
{
asl::array<int, 4> arr{10, 11, 12, 13};
ASL_TEST_EXPECT(arr[0] == 10);
ASL_TEST_EXPECT(arr[1] == 11);
ASL_TEST_EXPECT(arr[2] == 12);
ASL_TEST_EXPECT(arr[3] == 13);
ASL_TEST_EXPECT(arr.data()[0] == 10); // NOLINT
ASL_TEST_EXPECT(arr.data()[1] == 11); // NOLINT
ASL_TEST_EXPECT(arr.data()[2] == 12); // NOLINT
ASL_TEST_EXPECT(arr.data()[3] == 13); // NOLINT
}
static_assert(asl::convertible_to<asl::array<int, 4>, asl::span<int, 4>>);
static_assert(asl::convertible_to<asl::array<int, 4>, asl::span<int>>);
ASL_TEST(sized_span)
{
asl::array<int, 4> arr{10, 11, 12, 13};
const asl::span<int, 4> s1 = arr;
ASL_TEST_EXPECT(s1.size() == 4);
ASL_TEST_EXPECT(s1.data() == arr.data());
ASL_TEST_EXPECT(s1[0] == 10);
ASL_TEST_EXPECT(s1[1] == 11);
ASL_TEST_EXPECT(s1[2] == 12);
ASL_TEST_EXPECT(s1[3] == 13);
}
ASL_TEST(unsized_span)
{
asl::array<int, 4> arr{10, 11, 12, 13};
const asl::span<int> s2 = arr;
ASL_TEST_EXPECT(s2.size() == 4);
ASL_TEST_EXPECT(s2[0] == 10);
ASL_TEST_EXPECT(s2[1] == 11);
ASL_TEST_EXPECT(s2[2] == 12);
ASL_TEST_EXPECT(s2[3] == 13);
}
ASL_TEST(iterator)
{
const asl::array<int, 4> arr{10, 11, 12, 13};
auto it = arr.begin();
auto end = arr.end();
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 10);
it++;
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 11);
it++;
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 12);
it++;
ASL_TEST_ASSERT(it != end);
ASL_TEST_EXPECT(*it == 13);
it++;
ASL_TEST_ASSERT(it == end);
}

View File

@ -10,6 +10,8 @@ static_assert(asl::size_of<int> == asl::size_of<asl::maybe_uninit<int>>);
static_assert(asl::align_of<int> == asl::align_of<asl::maybe_uninit<int>>);
#define TEST_TYPE_PROPERTIES(PRP) \
static_assert(asl::PRP<asl::maybe_uninit<int>> == asl::PRP<int>); \
static_assert(asl::PRP<asl::maybe_uninit<void*>> == asl::PRP<void*>); \
static_assert(asl::PRP<asl::maybe_uninit<TrivialType>> == asl::PRP<TrivialType>); \
static_assert(asl::PRP<asl::maybe_uninit<TrivialTypeDefaultValue>> == asl::PRP<TrivialTypeDefaultValue>); \
static_assert(asl::PRP<asl::maybe_uninit<WithDestructor>> == asl::PRP<WithDestructor>); \
@ -17,6 +19,8 @@ static_assert(asl::align_of<int> == asl::align_of<asl::maybe_uninit<int>>);
static_assert(asl::PRP<asl::maybe_uninit<MoveableOnly>> == asl::PRP<MoveableOnly>); \
static_assert(asl::PRP<asl::maybe_uninit<Pinned>> == asl::PRP<Pinned>);
// @Todo(C++26) We expect this to break once trivial unions land.
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3074r7.html#just-make-it-work
TEST_TYPE_PROPERTIES(trivially_default_constructible);
TEST_TYPE_PROPERTIES(trivially_copy_constructible);
TEST_TYPE_PROPERTIES(trivially_move_constructible);

View File

@ -68,7 +68,7 @@ public:
, m_size{size}
{}
constexpr explicit span(T* data, isize_t size)
constexpr explicit span(T* data, [[maybe_unused]] isize_t size)
requires (!kIsDynamic)
: m_data{data}
{

View File

@ -0,0 +1,4 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause

View File

@ -0,0 +1,8 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
CLANG_BINDIR = "{CLANG_BINDIR}"
CLANG_LIBDIR = "{CLANG_LIBDIR}"
CLANG_INCLUDE_DIRS = {CLANG_INCLUDE_DIRS}

View File

@ -0,0 +1,19 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
load(":toolchain_config.bzl", "declare_toolchain")
filegroup(name = "empty")
PAIRS = [
("windows", "x86_64"),
("linux", "x86_64"),
("linux", "aarch64"),
]
[
declare_toolchain(name = "%s_%s_toolchain" % (os, arch), os = os, arch = arch)
for (os, arch) in PAIRS
]

View File

@ -0,0 +1,113 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
def _copy_file(ctx, from_path, to_path):
content = ctx.read(from_path)
ctx.file(to_path, content = content)
def _get_clang_info(ctx, clang_path, line_prefix):
result = ctx.execute([clang_path, "--version"])
if result.return_code != 0:
return None
for l in result.stdout.splitlines():
if l.startswith(line_prefix):
l = l[len(line_prefix):]
return l.split(" ")[0]
return None
def _get_clang_version(ctx, clang_path):
return _get_clang_info(ctx, clang_path, "clang version ")
def _get_clang_lib_dir(ctx, clang_path):
result = ctx.execute([clang_path, "-no-canonical-prefixes", "--print-resource-dir"])
if result.return_code != 0:
return None
return result.stdout
def _get_include_dirs(ctx, clang_path):
ctx.file("detect.cpp", content = "")
detect_path = ctx.path("detect.cpp")
res = ctx.execute([
clang_path,
"-v",
"-no-canonical-prefixes",
"-fsyntax-only",
"-x", "c++",
detect_path,
])
lines = res.stderr.splitlines()
listing_include_dirs = False
include_dirs = []
for l in lines:
if l.startswith("#include <...> search starts here:"):
listing_include_dirs = True
elif l.startswith("End of search list."):
listing_include_dirs = False
elif listing_include_dirs:
include_dirs.append(l.strip())
return include_dirs
def _impl(ctx):
_copy_file(ctx, ctx.attr._build_bazel, "BUILD.bazel")
_copy_file(ctx, ctx.attr._toolchain_config_bzl, "toolchain_config.bzl")
clang_path = ctx.which("clang")
if not clang_path:
fail("Could not find clang")
clang_version = _get_clang_version(ctx, clang_path)
if not clang_version:
fail("Could not detect clang version")
clang_lib_dir = _get_clang_lib_dir(ctx, clang_path)
if not clang_lib_dir:
fail("Could not detect clang lib dir")
include_dirs = _get_include_dirs(ctx, clang_path)
if not include_dirs:
fail("Could not detect include dirs")
ctx.template(
"clang_config.bzl",
ctx.attr._clang_config_bzl_tpl,
executable = False,
substitutions = {
"{CLANG_BINDIR}": str(clang_path.dirname),
"{CLANG_LIBDIR}": str(clang_lib_dir).strip().replace("\\", "/"),
"{CLANG_INCLUDE_DIRS}": str(include_dirs),
},
)
_config_detection = repository_rule(
implementation = _impl,
local = True,
configure = True,
attrs = {
"_build_bazel": attr.label(
default = Label("//bazel/clang_toolchain:clang_toolchain.BUILD.bazel"),
allow_single_file = True,
),
"_toolchain_config_bzl": attr.label(
default = Label("//bazel/clang_toolchain:toolchain_config.bzl"),
allow_single_file = True,
),
"_clang_config_bzl_tpl": attr.label(
default = Label("//bazel/clang_toolchain:clang_config.bzl.tpl"),
allow_single_file = True,
),
},
)
clang_toolchain = module_extension(
implementation = lambda ctx: _config_detection(
name = "clang_toolchain",
),
)

View File

@ -0,0 +1,596 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
load(
"//:clang_config.bzl",
"CLANG_BINDIR",
"CLANG_LIBDIR",
"CLANG_INCLUDE_DIRS",
)
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
load(
"@rules_cc//cc:cc_toolchain_config_lib.bzl",
"action_config",
"artifact_name_pattern",
"feature",
"feature_set",
"flag_group",
"flag_set",
"tool",
"tool_path",
"variable_with_value",
)
CPP_COMPILE_ACTIONS = [
ACTION_NAMES.cpp_compile,
ACTION_NAMES.cpp_header_parsing
]
C_COMPILE_ACTIONS = [
ACTION_NAMES.c_compile,
]
COMPILE_ACTIONS = C_COMPILE_ACTIONS + CPP_COMPILE_ACTIONS
LINK_ACTIONS = [
ACTION_NAMES.cpp_link_dynamic_library,
ACTION_NAMES.cpp_link_nodeps_dynamic_library,
ACTION_NAMES.cpp_link_executable,
]
ARCHIVE_ACTIONS = [
ACTION_NAMES.cpp_link_static_library,
]
def _impl(ctx):
os = ctx.attr.os
is_windows = os == "windows"
exe_suffix = ".exe" if is_windows else ""
tool_paths = [
tool_path(name = "ar", path = CLANG_BINDIR + "/llvm-ar" + exe_suffix),
tool_path(name = "ld", path = CLANG_BINDIR + "/ld.lld" + exe_suffix),
tool_path(name = "nm", path = CLANG_BINDIR + "/llvm-nm" + exe_suffix),
tool_path(name = "objdump", path = CLANG_BINDIR + "/llvm-objdump" + exe_suffix),
tool_path(name = "strip", path = CLANG_BINDIR + "/llvm-strip" + exe_suffix),
tool_path(name = "gcc", path = CLANG_BINDIR + "/clang" + exe_suffix),
tool_path(name = "cpp", path = CLANG_BINDIR + "/clang++" + exe_suffix),
]
action_configs = [
action_config(action_name = name, enabled = True, tools = [tool(path = CLANG_BINDIR + "/clang" + exe_suffix)])
for name in C_COMPILE_ACTIONS
] + [
action_config(action_name = name, enabled = True, tools = [tool(path = CLANG_BINDIR + "/clang++" + exe_suffix)])
for name in CPP_COMPILE_ACTIONS
] + [
action_config(action_name = name, enabled = True, tools = [tool(path = CLANG_BINDIR + "/clang++" + exe_suffix)])
for name in LINK_ACTIONS
] + [
action_config(action_name = name, enabled = True, tools = [tool(path = CLANG_BINDIR + "/llvm-ar" + exe_suffix)])
for name in ARCHIVE_ACTIONS
] + [
action_config(ACTION_NAMES.strip, enabled = True, tools = [tool(path = CLANG_BINDIR + "/llvm-strip" + exe_suffix)]),
]
artifact_name_patterns = [
artifact_name_pattern(
category_name = "object_file",
prefix = "",
extension = ".obj" if is_windows else ".o",
),
artifact_name_pattern(
category_name = "static_library",
prefix = "" if is_windows else "lib",
extension = ".lib" if is_windows else ".a",
),
artifact_name_pattern(
category_name = "dynamic_library",
prefix = "" if is_windows else "lib",
extension = ".dll" if is_windows else ".so",
),
artifact_name_pattern(
category_name = "executable",
prefix = "",
extension = exe_suffix,
),
]
asan_link_flag_sets = []
ubsan_link_flag_sets = []
if is_windows:
asan_link_flag_sets.append(flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-l\"" + CLANG_LIBDIR + "/lib/windows/clang_rt.asan_dynamic-x86_64.lib\"",
],
),
],
))
ubsan_link_flag_sets.append(flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-l\"" + CLANG_LIBDIR + "/lib/windows/clang_rt.ubsan_standalone-x86_64.lib\"",
],
),
],
))
features = [
feature(name = "no_legacy_features"),
feature(name = "opt"),
feature(name = "fastbuild"),
feature(name = "dbg"),
feature(
name = "default_flags",
enabled = True,
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-no-canonical-prefixes",
"-fcolor-diagnostics",
],
),
],
),
flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-fuse-ld=lld",
],
),
],
),
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-Wno-builtin-macro-redefined",
"-D__DATE__=\"redacted\"",
"-D__TIMESTAMP__=\"redacted\"",
"-D__TIME__=\"redacted\"",
"-c",
],
),
flag_group(
flags = ["-frandom-seed=%{output_file}"],
expand_if_available = "output_file",
),
flag_group(
flags = ["-D%{preprocessor_defines}"],
iterate_over = "preprocessor_defines",
),
flag_group(
flags = ["-I%{include_paths}"],
iterate_over = "include_paths",
),
flag_group(
flags = ["-iquote", "%{quote_include_paths}"],
iterate_over = "quote_include_paths",
),
flag_group(
flags = ["-isystem", "%{system_include_paths}"],
iterate_over = "system_include_paths",
),
flag_group(
flags = ["-MD", "-MF", "%{dependency_file}"],
expand_if_available = "dependency_file",
),
],
),
flag_set(
actions = ARCHIVE_ACTIONS,
flag_groups = [
flag_group(
flags = ["rcsD"],
),
flag_group(
flags = ["%{output_execpath}"],
expand_if_available = "output_execpath",
),
],
),
flag_set(
actions = LINK_ACTIONS + ARCHIVE_ACTIONS,
flag_groups = [
flag_group(
iterate_over = "libraries_to_link",
expand_if_available = "libraries_to_link",
flag_groups = [
flag_group(
flags = ["-Wl,--start-lib"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file_group",
),
),
flag_group(
iterate_over = "libraries_to_link.object_files",
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file_group",
),
flag_groups = [
flag_group(
flags = ["%{libraries_to_link.object_files}"],
expand_if_false = "libraries_to_link.is_whole_archive",
),
flag_group(
flags = ["-Wl,-force_load,%{libraries_to_link.object_files}"],
expand_if_true = "libraries_to_link.is_whole_archive",
),
],
),
flag_group(
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file",
),
flag_groups = [
flag_group(
flags = ["%{libraries_to_link.name}"],
expand_if_false = "libraries_to_link.is_whole_archive",
),
flag_group(
flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
expand_if_true = "libraries_to_link.is_whole_archive",
),
],
),
flag_group(
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "interface_library",
),
flag_groups = [
flag_group(
flags = ["%{libraries_to_link.name}"],
expand_if_false = "libraries_to_link.is_whole_archive",
),
flag_group(
flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
expand_if_true = "libraries_to_link.is_whole_archive",
),
],
),
flag_group(
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "static_library",
),
flag_groups = [
flag_group(
flags = ["%{libraries_to_link.name}"],
expand_if_false = "libraries_to_link.is_whole_archive",
),
flag_group(
flags = ["-Wl,-force_load,%{libraries_to_link.name}"],
expand_if_true = "libraries_to_link.is_whole_archive",
),
],
),
flag_group(
flags = ["-l%{libraries_to_link.name}"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "dynamic_library",
),
),
flag_group(
flags = ["-l:%{libraries_to_link.name}"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "versioned_dynamic_library",
),
),
flag_group(
expand_if_true = "libraries_to_link.is_whole_archive",
flag_groups = [
flag_group(
expand_if_false = "macos_flags",
flags = ["-Wl,-no-whole-archive"],
),
],
),
flag_group(
flags = ["-Wl,--end-lib"],
expand_if_equal = variable_with_value(
name = "libraries_to_link.type",
value = "object_file_group",
),
),
],
),
],
),
],
),
feature(
name = "windows_linker_flags",
enabled = is_windows,
flag_sets = [
flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-Xlinker",
"/subsystem:console",
],
),
],
),
],
),
feature(
name = "dbg_flags",
enabled = True,
requires = [feature_set(["dbg"])],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-DDEBUG=1",
],
),
],
),
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-g3",
],
),
],
),
],
),
feature(
name = "fastbuild_flags",
enabled = True,
requires = [feature_set(["fastbuild"])],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-O1",
"-DDEBUG=1",
],
),
],
),
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-g1",
],
),
],
),
],
),
feature(
name = "opt_flags",
enabled = True,
requires = [feature_set(["opt"])],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-O3",
"-DNDEBUG=1",
],
),
],
),
],
),
feature(
name = "c++20",
provides = ["c++_version"],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = ["-std=c++20"],
),
],
),
],
),
feature(
name = "c++23",
provides = ["c++_version"],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = ["-std=c++23"],
),
],
),
],
),
feature(
name = "asan",
implies = ["sanitize"],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-fsanitize=address",
],
),
],
),
] + asan_link_flag_sets,
),
feature(
name = "ubsan",
implies = ["sanitize"],
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS + LINK_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-fsanitize=undefined",
],
),
],
),
] + ubsan_link_flag_sets,
),
feature(
name = "sanitize",
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = [
"-fno-omit-frame-pointer",
"-fno-sanitize-ignorelist",
"-fno-sanitize-recover=all",
],
),
],
),
],
),
feature(
name = "final_flags",
enabled = True,
flag_sets = [
flag_set(
actions = COMPILE_ACTIONS,
flag_groups = [
flag_group(
flags = ["%{user_compile_flags}"],
expand_if_available = "user_compile_flags",
iterate_over = "user_compile_flags",
),
flag_group(
flags = ["-o", "%{output_file}"],
expand_if_available = "output_file",
),
flag_group(
flags = ["%{source_file}"],
expand_if_available = "source_file",
),
],
),
flag_set(
actions = LINK_ACTIONS,
flag_groups = [
flag_group(
flags = ["%{user_link_flags}"],
expand_if_available = "user_link_flags",
iterate_over = "user_link_flags",
),
flag_group(
flags = ["-o", "%{output_execpath}"],
expand_if_available = "output_execpath",
),
],
),
flag_set(
actions = LINK_ACTIONS + ARCHIVE_ACTIONS,
flag_groups = [
flag_group(
flags = ["@%{linker_param_file}"],
expand_if_available = "linker_param_file",
),
],
),
],
),
]
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
toolchain_identifier = "%s-toolchain" % os,
compiler = "clang",
artifact_name_patterns = artifact_name_patterns,
cxx_builtin_include_directories = CLANG_INCLUDE_DIRS,
tool_paths = tool_paths,
action_configs = action_configs,
features = features,
)
toolchain_config = rule(
implementation = _impl,
attrs = {
"os": attr.string(mandatory = True),
},
provides = [CcToolchainConfigInfo],
)
def _declare_toolchain_impl(name, os, arch, visibility):
toolchain_config(
name = name + "_config",
os = os,
)
native.cc_toolchain(
name = name + "_cc",
toolchain_config = name + "_config",
all_files = ":empty",
compiler_files = ":empty",
linker_files = ":empty",
objcopy_files = ":empty",
strip_files = ":empty",
dwp_files = ":empty",
)
native.toolchain(
name = name + "_def",
toolchain = name + "_cc",
toolchain_type = "@rules_cc//cc:toolchain_type",
exec_compatible_with = [
"@platforms//cpu:%s" % arch,
"@platforms//os:%s" % os,
],
target_compatible_with = [
"@platforms//cpu:%s" % arch,
"@platforms//os:%s" % os,
],
visibility = ["//visibility:public"],
)
declare_toolchain = macro(
implementation = _declare_toolchain_impl,
attrs = {
"os": attr.string(mandatory = True, configurable = False),
"arch": attr.string(mandatory = True, configurable = False),
}
)

View File

@ -1 +1 @@
bazel run //tools:refresh_clangd --config=linux -- --config=linux
bazel run //tools:refresh_clangd

View File

@ -1 +1,6 @@
constexpr cast from void* +cpp26 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2738r1.pdf
add sanitizers features +bazel_clang_toolchain
linux x64 support +bazel_clang_toolchain
linux arm64 support +bazel_clang_toolchain

View File

@ -19,6 +19,7 @@ TO_FIX = [
"**/*.cpp",
"**/*.py",
"**/*.bzl",
"**/*.bzl.tpl",
"**/*.bazel",
"**/*.txt",
"**/*.bat",

View File

@ -18,7 +18,7 @@ cc_library(
name = "dragonbox",
hdrs = ["dragonbox.h"],
includes = ["."],
visibility = ["//:__subpackages__"],
visibility = ["//asl:__subpackages__"],
applicable_licenses = [
":license",
],

25
vendor/fast_float/BUILD.bazel vendored Normal file
View File

@ -0,0 +1,25 @@
# Copyright 2025 Steven Le Rouzic
#
# SPDX-License-Identifier: BSD-3-Clause
load("@rules_license//rules:license.bzl", "license")
license(
name = "license",
license_kinds = [
"@rules_license//licenses/spdx:MIT",
],
license_text = "LICENSE.txt",
package_name = "fast_float",
package_url = "https://github.com/fastfloat/fast_float",
)
cc_library(
name = "fast_float",
hdrs = ["fast_float.h"],
includes = ["."],
visibility = ["//asl:__subpackages__"],
applicable_licenses = [
":license",
],
)

27
vendor/fast_float/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,27 @@
MIT License
Copyright (c) 2021 The fast_float authors
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE 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 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, 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 IN THE SOFTWARE.

4443
vendor/fast_float/fast_float.h vendored Normal file

File diff suppressed because it is too large Load Diff