Compare commits
29 Commits
v0.2.0
...
bazel_clan
Author | SHA1 | Date | |
---|---|---|---|
9f792e536b | |||
00ea14788f | |||
bcdad5b876 | |||
cca2e26724 | |||
195f20ff17 | |||
a78e24e9eb | |||
43ab95880d | |||
47c6677405 | |||
92f908ee1b | |||
30237bb78f | |||
8f59f113e8 | |||
afb237c513 | |||
19e2164441 | |||
b8a87223bb | |||
a1db1cd9e2 | |||
54b95b1662 | |||
e7e7023340 | |||
f7a2699ac0 | |||
088e03708a | |||
5bca42b049 | |||
e1ba7dd7a9 | |||
837f696971 | |||
afbfd0e781 | |||
0776012d09 | |||
4f8cbd442a | |||
2d309a2cff | |||
f19d93a69a | |||
e034efe8bd | |||
2c457c4275 |
33
.bazelrc
33
.bazelrc
@ -1,19 +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
|
||||
# @Todo(bazel) We should be able to use @local_config_cc...
|
||||
build:windows --extra_toolchains=@@rules_cc++cc_configure_extension+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
|
||||
@ -26,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
|
||||
|
@ -21,3 +21,4 @@ Checks:
|
||||
- "-cppcoreguidelines-pro-type-union-access"
|
||||
- "-*-copy-assignment-signature"
|
||||
- "-*-unconventional-assign-operator"
|
||||
- "-readability-math-missing-parentheses"
|
||||
|
11
BUILD.bazel
11
BUILD.bazel
@ -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",
|
||||
],
|
||||
)
|
||||
|
25
MODULE.bazel
25
MODULE.bazel
@ -2,19 +2,24 @@
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
module(name = "asl")
|
||||
module(name = "asl", version = "0.4.0")
|
||||
|
||||
bazel_dep(name = "platforms", version = "0.0.10")
|
||||
bazel_dep(name = "rules_cc", version = "0.0.17")
|
||||
bazel_dep(name = "platforms", version = "0.0.11")
|
||||
bazel_dep(name = "rules_license", version = "1.0.0")
|
||||
|
||||
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 = "rules_cc", version = "0.1.1")
|
||||
|
||||
bazel_dep(name = "rules_python", version = "1.1.0")
|
||||
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",
|
||||
)
|
||||
|
||||
|
96
MODULE.bazel.lock
generated
96
MODULE.bazel.lock
generated
@ -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,11 +43,13 @@
|
||||
"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",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.10/source.json": "f22828ff4cf021a6b577f1bf6341cb9dcd7965092a439f64fc1bb3b7a5ae4bd5",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
|
||||
@ -69,12 +75,12 @@
|
||||
"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.17/source.json": "4db99b3f55c90ab28d14552aa0632533e3e8e5e9aea0f5c24ac0014282c2a7c5",
|
||||
"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",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
|
||||
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
|
||||
@ -88,10 +94,10 @@
|
||||
"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.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_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.6.1/source.json": "f18d9ad3c4c54945bf422ad584fa6c5ca5b3116ff55a5b1bc77e5c1210be5960",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
|
||||
@ -121,8 +127,8 @@
|
||||
"https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.1.0/MODULE.bazel": "57e01abae22956eb96d891572490d20e07d983e0c065de0b2170cafe5053e788",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.1.0/source.json": "29f1fdfd23a40808c622f813bc93e29c3aae277333f03293f667e76159750a0f",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13",
|
||||
"https://bcr.bazel.build/modules/rules_python/1.3.0/source.json": "25932f917cd279c7baefa6cb1d3fa8750a7a29de522024449b19af6eab51f4a0",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
|
||||
@ -134,53 +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": {
|
||||
"@@platforms//host:extension.bzl%host_platform": {
|
||||
"//bazel/clang_toolchain:config_detection.bzl%clang_toolchain": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=",
|
||||
"usagesDigest": "SeQiIN/f8/Qt9vYQk7qcXp4I4wJeEC0RnQDiaaJ4tb8=",
|
||||
"bzlTransitiveDigest": "xXTcNvgp9bMnw/1ZKi1vOOoz139EHr8Hw3Ugd9+eos0=",
|
||||
"usagesDigest": "CRr/eSLZQiKHqCq8MWfzhslrj4Mqar1310aEoa4BzJw=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"host_platform": {
|
||||
"repoRuleId": "@@platforms//host:extension.bzl%host_platform_repo",
|
||||
"clang_toolchain": {
|
||||
"repoRuleId": "@@//bazel/clang_toolchain:config_detection.bzl%_config_detection",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": []
|
||||
}
|
||||
},
|
||||
"@@rules_java+//java:rules_java_deps.bzl%compatibility_proxy": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "84xJEZ1jnXXwo8BXMprvBm++rRt4jsTu9liBxz0ivps=",
|
||||
"usagesDigest": "jTQDdLDxsS43zuRmg1faAjIEPWdLAbDAowI1pInQSoo=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"compatibility_proxy": {
|
||||
"repoRuleId": "@@rules_java+//java:rules_java_deps.bzl%_compatibility_proxy_repo_rule",
|
||||
"attributes": {}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_java+",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@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": {},
|
||||
@ -241,6 +225,42 @@
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"@@rules_python+//python/uv:uv.bzl%uv": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=",
|
||||
"usagesDigest": "vJ5RHUxAnV24M5swNGiAnkdxMx3Hp/iOLmNANTC5Xc8=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"uv": {
|
||||
"repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo",
|
||||
"attributes": {
|
||||
"toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'",
|
||||
"toolchain_names": [
|
||||
"none"
|
||||
],
|
||||
"toolchain_implementations": {
|
||||
"none": "'@@rules_python+//python:none'"
|
||||
},
|
||||
"toolchain_compatible_with": {
|
||||
"none": [
|
||||
"@platforms//:incompatible"
|
||||
]
|
||||
},
|
||||
"toolchain_target_settings": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_python+",
|
||||
"platforms",
|
||||
"platforms"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
asl/BUILD.bazel
Normal file
17
asl/BUILD.bazel
Normal 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",
|
||||
},
|
||||
)
|
@ -11,17 +11,26 @@ cc_library(
|
||||
hdrs = [
|
||||
"annotations.hpp",
|
||||
"assert.hpp",
|
||||
"bit.hpp",
|
||||
"config.hpp",
|
||||
"defer.hpp",
|
||||
"float.hpp",
|
||||
"functional.hpp",
|
||||
"integers.hpp",
|
||||
"meta.hpp",
|
||||
"numeric.hpp",
|
||||
"utility.hpp",
|
||||
],
|
||||
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"],
|
||||
)
|
||||
|
||||
@ -37,10 +46,12 @@ cc_library(
|
||||
"//asl/types:box",
|
||||
],
|
||||
) for name in [
|
||||
"bit",
|
||||
"defer",
|
||||
"float",
|
||||
"functional",
|
||||
"integers",
|
||||
"meta",
|
||||
"numeric",
|
||||
"utility",
|
||||
]]
|
||||
|
@ -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
|
||||
|
@ -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__) {} \
|
||||
|
166
asl/base/bit.hpp
Normal file
166
asl/base/bit.hpp
Normal file
@ -0,0 +1,166 @@
|
||||
// Copyright 2025 Steven Le Rouzic
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "asl/base/integers.hpp"
|
||||
#include "asl/base/meta.hpp"
|
||||
|
||||
namespace asl
|
||||
{
|
||||
|
||||
constexpr bool has_single_bit(is_unsigned_integer auto x)
|
||||
{
|
||||
return x != 0 && ((x - 1) & x) == 0;
|
||||
}
|
||||
|
||||
constexpr int popcount(uint8_t v)
|
||||
{
|
||||
v = v - ((v >> 1) & 0x55);
|
||||
v = (v & 0x33) + ((v >> 2) & 0x33);
|
||||
return (v + (v >> 4)) & 0x0F;
|
||||
}
|
||||
|
||||
constexpr int popcount(uint16_t v)
|
||||
{
|
||||
v = v - ((v >> 1) & 0x5555);
|
||||
v = (v & 0x3333) + ((v >> 2) & 0x3333);
|
||||
return static_cast<uint16_t>((v + (v >> 4) & 0x0F0F) * uint16_t{0x0101}) >> 8;
|
||||
}
|
||||
|
||||
constexpr int popcount(uint32_t v)
|
||||
{
|
||||
v = v - ((v >> 1) & 0x5555'5555);
|
||||
v = (v & 0x3333'3333) + ((v >> 2) & 0x3333'3333);
|
||||
return static_cast<int>(((v + (v >> 4) & 0x0F0F'0F0F) * 0x0101'0101) >> 24);
|
||||
}
|
||||
|
||||
constexpr int popcount(uint64_t v)
|
||||
{
|
||||
v = v - ((v >> 1) & 0x5555'5555'5555'5555);
|
||||
v = (v & 0x3333'3333'3333'3333) + ((v >> 2) & 0x3333'3333'3333'3333);
|
||||
return static_cast<int>(((v + (v >> 4) & 0x0F0F'0F0F'0F0F'0F0F) * 0x0101'0101'0101'0101) >> 56);
|
||||
}
|
||||
|
||||
constexpr uint8_t propagate_right_one(uint8_t v)
|
||||
{
|
||||
v = v | (v >> 1);
|
||||
v = v | (v >> 2);
|
||||
v = v | (v >> 4);
|
||||
return v;
|
||||
}
|
||||
|
||||
constexpr uint16_t propagate_right_one(uint16_t v)
|
||||
{
|
||||
v = v | (v >> 1);
|
||||
v = v | (v >> 2);
|
||||
v = v | (v >> 4);
|
||||
v = v | (v >> 8);
|
||||
return v;
|
||||
}
|
||||
|
||||
constexpr uint32_t propagate_right_one(uint32_t v)
|
||||
{
|
||||
v = v | (v >> 1);
|
||||
v = v | (v >> 2);
|
||||
v = v | (v >> 4);
|
||||
v = v | (v >> 8);
|
||||
v = v | (v >> 16);
|
||||
return v;
|
||||
}
|
||||
|
||||
constexpr uint64_t propagate_right_one(uint64_t v)
|
||||
{
|
||||
v = v | (v >> 1);
|
||||
v = v | (v >> 2);
|
||||
v = v | (v >> 4);
|
||||
v = v | (v >> 8);
|
||||
v = v | (v >> 16);
|
||||
v = v | (v >> 32);
|
||||
return v;
|
||||
}
|
||||
|
||||
constexpr int countr_zero(is_unsigned_integer auto v)
|
||||
{
|
||||
v = ~v & (v - 1);
|
||||
return popcount(v);
|
||||
}
|
||||
|
||||
constexpr int countr_one(is_unsigned_integer auto v)
|
||||
{
|
||||
v = v & (~v - 1);
|
||||
return popcount(v);
|
||||
}
|
||||
|
||||
constexpr int countl_zero(is_unsigned_integer auto v)
|
||||
{
|
||||
v = ~propagate_right_one(v);
|
||||
return popcount(v);
|
||||
}
|
||||
|
||||
constexpr int countl_one(is_unsigned_integer auto v)
|
||||
{
|
||||
v = ~v;
|
||||
v = ~propagate_right_one(v);
|
||||
return popcount(v);
|
||||
}
|
||||
|
||||
template<is_unsigned_integer T>
|
||||
constexpr T rotr(T v, int s);
|
||||
|
||||
template<is_unsigned_integer T>
|
||||
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) % N)) : rotr(v, -s);
|
||||
}
|
||||
|
||||
template<is_unsigned_integer T>
|
||||
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) % N)) : rotl(v, -s);
|
||||
}
|
||||
|
||||
constexpr uint16_t byteswap(uint16_t v)
|
||||
{
|
||||
return static_cast<uint16_t>((v << 8U) | (v >> 8U));
|
||||
}
|
||||
|
||||
constexpr uint32_t byteswap(uint32_t v)
|
||||
{
|
||||
return rotr(v & 0x00ff'00ff, 8) | rotl(v & 0xff00'ff00, 8);
|
||||
}
|
||||
|
||||
constexpr uint64_t byteswap(uint64_t v)
|
||||
{
|
||||
return (uint64_t{byteswap(static_cast<uint32_t>(v))} << 32)
|
||||
| uint64_t{byteswap(static_cast<uint32_t>(v >> 32))};
|
||||
}
|
||||
|
||||
constexpr auto bit_ceil(is_unsigned_integer auto v)
|
||||
{
|
||||
v -= 1;
|
||||
v = propagate_right_one(v);
|
||||
v += 1;
|
||||
return v;
|
||||
}
|
||||
|
||||
constexpr auto bit_floor(is_unsigned_integer auto v)
|
||||
{
|
||||
v = propagate_right_one(v);
|
||||
v = v - (v >> 1);
|
||||
return v;
|
||||
}
|
||||
|
||||
constexpr int bit_width(is_unsigned_integer auto v)
|
||||
{
|
||||
static constexpr int N = sizeof(decltype(v)) * 8;
|
||||
return N - countl_zero(v);
|
||||
}
|
||||
|
||||
} // namespace asl
|
||||
|
203
asl/base/bit_tests.cpp
Normal file
203
asl/base/bit_tests.cpp
Normal file
@ -0,0 +1,203 @@
|
||||
// Copyright 2025 Steven Le Rouzic
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#include "asl/base/bit.hpp"
|
||||
|
||||
#include "asl/testing/testing.hpp"
|
||||
|
||||
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(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)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::popcount(uint8_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint8_t{255}) == 8);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint8_t{46}) == 4);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint16_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint16_t{255}) == 8);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint16_t{65535}) == 16);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint16_t{46}) == 4);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint32_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint32_t{255}) == 8);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint32_t{65535}) == 16);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint32_t{65536}) == 1);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint32_t{46}) == 4);
|
||||
ASL_TEST_EXPECT(asl::popcount(uint32_t{0xffff'ffff}) == 32);
|
||||
ASL_TEST_EXPECT(asl::popcount(0x8421'1248'8421'1248) == 16);
|
||||
ASL_TEST_EXPECT(asl::popcount(0xffff'ffff'ffff'ffff) == 64);
|
||||
}
|
||||
|
||||
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);
|
||||
ASL_TEST_EXPECT(asl::countr_zero(uint8_t{0b10101001}) == 0);
|
||||
}
|
||||
|
||||
ASL_TEST(countr_one)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::countr_one(uint8_t{255}) == 8);
|
||||
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b00011100}) == 0);
|
||||
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b10101011}) == 2);
|
||||
ASL_TEST_EXPECT(asl::countr_one(uint8_t{0b10101001}) == 1);
|
||||
}
|
||||
|
||||
ASL_TEST(countl_zero)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0}) == 8);
|
||||
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{255}) == 0);
|
||||
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b00011100}) == 3);
|
||||
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b10101010}) == 0);
|
||||
ASL_TEST_EXPECT(asl::countl_zero(uint8_t{0b00001001}) == 4);
|
||||
}
|
||||
|
||||
ASL_TEST(countl_one)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::countl_one(uint8_t{255}) == 8);
|
||||
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b00011100}) == 0);
|
||||
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b10101010}) == 1);
|
||||
ASL_TEST_EXPECT(asl::countl_one(uint8_t{0b11101001}) == 3);
|
||||
}
|
||||
|
||||
ASL_TEST(rotl)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 1) == 0x8000'0200);
|
||||
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 3) == 0x0000'0802);
|
||||
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, 0) == 0x4000'0100);
|
||||
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, -1) == 0x2000'0080);
|
||||
ASL_TEST_EXPECT(asl::rotl(uint32_t{0x4000'0100}, -12) == 0x1004'0000);
|
||||
}
|
||||
|
||||
ASL_TEST(rotr)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, -1) == 0x8000'0200);
|
||||
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, -3) == 0x0000'0802);
|
||||
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 0) == 0x4000'0100);
|
||||
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 1) == 0x2000'0080);
|
||||
ASL_TEST_EXPECT(asl::rotr(uint32_t{0x4000'0100}, 12) == 0x1004'0000);
|
||||
}
|
||||
|
||||
ASL_TEST(byteswap)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::byteswap(uint32_t{0x1234'5678}) == 0x7856'3412);
|
||||
ASL_TEST_EXPECT(asl::byteswap(uint16_t{0x1234}) == 0x3412);
|
||||
ASL_TEST_EXPECT(asl::byteswap(uint64_t{0x0123'4567'89ab'cdef}) == 0xefcd'ab89'6745'2301);
|
||||
}
|
||||
|
||||
ASL_TEST(bit_ceil) // NOLINT(*-cognitive-complexity)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{1}) == 1);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{2}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{3}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{4}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{5}) == 8);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{6}) == 8);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{127}) == 128);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{128}) == 128);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{254}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint8_t{255}) == 0);
|
||||
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{1}) == 1);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{2}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{3}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{4}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{5}) == 8);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{6}) == 8);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{32000}) == 32768);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{32768}) == 32768);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{35000}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_ceil(uint16_t{65535}) == 0);
|
||||
}
|
||||
|
||||
ASL_TEST(bit_floor) // NOLINT(*-cognitive-complexity)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{1}) == 1);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{2}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{3}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{4}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{5}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{6}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{127}) == 64);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{128}) == 128);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{254}) == 128);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint8_t{255}) == 128);
|
||||
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{1}) == 1);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{2}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{3}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{4}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{5}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{6}) == 4);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{32000}) == 16384);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{32768}) == 32768);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{35000}) == 32768);
|
||||
ASL_TEST_EXPECT(asl::bit_floor(uint16_t{65535}) == 32768);
|
||||
}
|
||||
|
||||
ASL_TEST(bit_width) // NOLINT(*-cognitive-complexity)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint8_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint8_t{1}) == 1);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint8_t{2}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint8_t{3}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint8_t{4}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint8_t{5}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint8_t{6}) == 3);
|
||||
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint16_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint16_t{1}) == 1);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint16_t{2}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint16_t{3}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint16_t{4}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint16_t{5}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint16_t{6}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint16_t{65535}) == 16);
|
||||
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{1}) == 1);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{2}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{3}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{4}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{5}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{6}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{65535}) == 16);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{65536}) == 17);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint32_t{0xffff'ffff}) == 32);
|
||||
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{0}) == 0);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{1}) == 1);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{2}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{3}) == 2);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{4}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{5}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{6}) == 3);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{65535}) == 16);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{65536}) == 17);
|
||||
ASL_TEST_EXPECT(asl::bit_width(uint64_t{0xffff'ffff}) == 32);
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -6,12 +6,42 @@
|
||||
|
||||
#include "asl/base/meta.hpp"
|
||||
|
||||
using float32_t = float;
|
||||
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); }
|
||||
|
||||
|
@ -9,56 +9,62 @@
|
||||
|
||||
namespace asl {
|
||||
|
||||
template<typename... Args, typename C>
|
||||
constexpr auto invoke(is_func auto C::* f, auto&& self, Args&&... args)
|
||||
-> decltype((self.*f)(std::forward<Args>(args)...))
|
||||
requires requires {
|
||||
(self.*f)(std::forward<Args>(args)...);
|
||||
template<typename F, typename... Args>
|
||||
constexpr auto invoke(F&& f, Args&&... args)
|
||||
-> decltype(std::forward<F>(f)(std::forward<Args>(args)...))
|
||||
requires (!is_member_ptr<un_cvref_t<F>>) && requires {
|
||||
f(std::forward<Args>(args)...);
|
||||
}
|
||||
{
|
||||
return std::forward<F>(f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename C>
|
||||
constexpr auto&& invoke(auto C::* f, same_or_derived_from<C> auto&& arg)
|
||||
{
|
||||
return std::forward<decltype(arg)>(arg).*f;
|
||||
}
|
||||
|
||||
template<typename C>
|
||||
constexpr auto&& invoke(auto C::* f, auto&& arg)
|
||||
requires (
|
||||
!same_or_derived_from<decltype(arg), C>
|
||||
&& requires { (*arg).*f; }
|
||||
)
|
||||
{
|
||||
return (*std::forward<decltype(arg)>(arg)).*f;
|
||||
}
|
||||
|
||||
template<typename C, typename... Args>
|
||||
constexpr auto invoke(is_func auto C::* f, same_or_derived_from<C> auto&& self, Args&&... args)
|
||||
-> decltype((std::forward<decltype(self)>(self).*f)(std::forward<Args>(args)...))
|
||||
requires requires { (self.*f)(std::forward<Args>(args)...); }
|
||||
{
|
||||
return (std::forward<decltype(self)>(self).*f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args, typename C>
|
||||
constexpr auto invoke(is_func auto C::* f, auto* self, Args&&... args)
|
||||
-> decltype((self->*f)(std::forward<Args>(args)...))
|
||||
requires requires {
|
||||
(self->*f)(std::forward<Args>(args)...);
|
||||
}
|
||||
{
|
||||
return (self->*f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args, typename C>
|
||||
constexpr auto invoke(is_object auto C::* m, auto&& self, Args&&...)
|
||||
-> decltype(self.*m)
|
||||
template<typename C, typename... Args>
|
||||
constexpr auto invoke(is_func auto C::* f, auto&& self, Args&&... args)
|
||||
-> decltype(((*std::forward<decltype(self)>(self)).*f)(std::forward<Args>(args)...))
|
||||
requires (
|
||||
sizeof...(Args) == 0 &&
|
||||
requires { self.*m; }
|
||||
!same_or_derived_from<decltype(self), C>
|
||||
&& requires { ((*self).*f)(std::forward<Args>(args)...); }
|
||||
)
|
||||
{
|
||||
return std::forward<decltype(self)>(self).*m;
|
||||
return ((*std::forward<decltype(self)>(self)).*f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename... Args, typename C>
|
||||
constexpr auto invoke(is_object auto C::* m, auto* self, Args&&...)
|
||||
-> decltype(self->*m)
|
||||
requires (
|
||||
sizeof...(Args) == 0 &&
|
||||
requires { self->*m; }
|
||||
)
|
||||
template<typename R, typename F, typename... Args>
|
||||
constexpr R invoke_r(F&& f, Args&&... args)
|
||||
{
|
||||
return self->*m;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
constexpr auto invoke(auto&& f, Args&&... args)
|
||||
-> decltype(f(std::forward<Args>(args)...))
|
||||
requires requires {
|
||||
f(std::forward<Args>(args)...);
|
||||
if constexpr (is_void<R>)
|
||||
{
|
||||
static_cast<void>(invoke(std::forward<F>(f), std::forward<Args>(args)...));
|
||||
}
|
||||
else
|
||||
{
|
||||
return invoke(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
}
|
||||
{
|
||||
return std::forward<decltype(f)>(f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<typename Void, typename F, typename... Args>
|
||||
@ -79,4 +85,8 @@ concept invocable = requires (F&& f, Args&&... args)
|
||||
invoke(std::forward<F>(f), std::forward<Args>(args)...);
|
||||
};
|
||||
|
||||
template<typename R, typename F, typename... Args>
|
||||
concept invocable_r = invocable<F, Args...>
|
||||
&& (is_void<R> || convertible_to<invoke_result_t<F, Args...>, R>);
|
||||
|
||||
} // namespace asl
|
||||
|
@ -7,9 +7,12 @@
|
||||
|
||||
struct HasFunction
|
||||
{
|
||||
void do_something(int, float) {}
|
||||
void do_something(int, float) const {}
|
||||
int& do_something2(int, float) &;
|
||||
};
|
||||
|
||||
struct HasFunction2 : public HasFunction {};
|
||||
|
||||
struct HasMember
|
||||
{
|
||||
int member{};
|
||||
@ -17,6 +20,8 @@ struct HasMember
|
||||
void (*member_fn)(){};
|
||||
};
|
||||
|
||||
struct HasMember2 : public HasMember {};
|
||||
|
||||
struct Functor
|
||||
{
|
||||
int64_t operator()() { return 35; }
|
||||
@ -34,7 +39,13 @@ static_assert(asl::same_as<asl::invoke_result_t<Functor>, int64_t>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<Functor, int>, int>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(static_cast<float(*)(float)>(some_func1)), float>, float>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something), HasFunction, int, float>, void>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasMember::member), const HasMember>, const int&>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something), const HasFunction2&, int, float>, void>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something), HasFunction*, int, float>, void>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something2), HasFunction2&, int, float>, int&>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasFunction::do_something2), HasFunction*, int, float>, int&>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasMember::member), HasMember>, int&&>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasMember::member), const HasMember&>, const int&>);
|
||||
static_assert(asl::same_as<asl::invoke_result_t<decltype(&HasMember::member), const HasMember2*>, const int&>);
|
||||
|
||||
static_assert(asl::invocable<int()>);
|
||||
static_assert(!asl::invocable<int(), int>);
|
||||
@ -45,8 +56,20 @@ static_assert(asl::invocable<Functor, int>);
|
||||
static_assert(!asl::invocable<Functor, void*>);
|
||||
static_assert(asl::invocable<decltype(static_cast<float(*)(float)>(some_func1)), float>);
|
||||
static_assert(asl::invocable<decltype(&HasFunction::do_something), HasFunction, int, float>);
|
||||
static_assert(asl::invocable<decltype(&HasFunction::do_something), const HasFunction2&, int, float>);
|
||||
static_assert(asl::invocable<decltype(&HasFunction::do_something), HasFunction*, int, float>);
|
||||
static_assert(!asl::invocable<decltype(&HasFunction::do_something), HasFunction, int, int*>);
|
||||
static_assert(asl::invocable<decltype(&HasMember::member), const HasMember>);
|
||||
static_assert(!asl::invocable<decltype(&HasFunction::do_something2), HasFunction, int, float>);
|
||||
static_assert(!asl::invocable<decltype(&HasFunction::do_something2), const HasFunction2&, int, float>);
|
||||
static_assert(asl::invocable<decltype(&HasFunction::do_something2), HasFunction2&, int, float>);
|
||||
static_assert(asl::invocable<decltype(&HasFunction::do_something2), HasFunction*, int, float>);
|
||||
static_assert(!asl::invocable<decltype(&HasFunction::do_something2), HasFunction, int, int*>);
|
||||
static_assert(asl::invocable<decltype(&HasMember::member), const HasMember2>);
|
||||
static_assert(asl::invocable<decltype(&HasMember::member), const HasMember&>);
|
||||
static_assert(asl::invocable<decltype(&HasMember::member), const HasMember2*>);
|
||||
|
||||
static_assert(asl::invocable_r<void*, int*()>);
|
||||
static_assert(!asl::invocable_r<int*, void*()>);
|
||||
|
||||
ASL_TEST(invoke_member_function)
|
||||
{
|
||||
@ -89,3 +112,9 @@ ASL_TEST(invoke_lambda)
|
||||
ASL_TEST_EXPECT(asl::invoke([](){ return 35; }) == 35);
|
||||
ASL_TEST_EXPECT(asl::invoke([](int x){ return x + 2; }, 6) == 8);
|
||||
}
|
||||
|
||||
ASL_TEST(invoke_r)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::invoke_r<int>([]() { return 1ULL; }) == 1);
|
||||
asl::invoke_r<void>([]() { return 1ULL; });
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -25,6 +25,9 @@ struct empty {};
|
||||
|
||||
template<typename T> struct id { using type = T; };
|
||||
|
||||
struct in_place_t {};
|
||||
static constexpr in_place_t in_place{};
|
||||
|
||||
template<typename... Args> static constexpr isize_t types_count = sizeof...(Args);
|
||||
|
||||
template<typename T, T kValue> struct integral_constant { static constexpr T value = kValue; };
|
||||
@ -92,12 +95,6 @@ template<typename T> concept trivially_destructible = __is_trivially_destructibl
|
||||
template<typename T> concept copyable = copy_constructible<T> && copy_assignable<T>;
|
||||
template<typename T> concept moveable = move_constructible<T> && move_assignable<T>;
|
||||
|
||||
template<typename From, typename To>
|
||||
concept convertible_to = __is_convertible(From, To);
|
||||
|
||||
template<typename Derived, class Base>
|
||||
concept derived_from = __is_class(Derived) && __is_class(Base) && convertible_to<const volatile Derived*, const volatile Base*>;
|
||||
|
||||
using nullptr_t = decltype(nullptr);
|
||||
|
||||
template<typename T> struct _un_const_helper { using type = T; };
|
||||
@ -149,11 +146,22 @@ 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 {};
|
||||
|
||||
template<typename T> concept is_ptr = _is_ptr_helper<un_cv_t<T>>::value;
|
||||
|
||||
template<typename From, typename To>
|
||||
concept convertible_to = __is_convertible(From, To);
|
||||
|
||||
template<typename Derived, typename Base>
|
||||
concept derived_from = __is_class(Derived) && __is_class(Base) && convertible_to<const volatile Derived*, const volatile Base*>;
|
||||
|
||||
template<typename Derived, typename Base>
|
||||
concept same_or_derived_from = same_as<un_cvref_t<Derived>, Base> || derived_from<un_cvref_t<Derived>, Base>;
|
||||
|
||||
template<typename T> struct _tame_helper { using type = T; };
|
||||
|
||||
#define TAME_HELPER_IMPL(TRAILING) \
|
||||
@ -194,6 +202,17 @@ template<typename R, typename... Args> struct _is_func_helper<R(Args...)> : true
|
||||
|
||||
template<typename T> concept is_func = _is_func_helper<tame_t<T>>::value;
|
||||
|
||||
template<typename T> struct _is_member_ptr_helper : false_type {};
|
||||
template<typename C, typename T> struct _is_member_ptr_helper<T C::*> : true_type
|
||||
{
|
||||
static constexpr bool kIsFunc = is_func<T>;
|
||||
};
|
||||
|
||||
template<typename T> concept is_member_ptr = _is_member_ptr_helper<un_cv_t<T>>::value;
|
||||
|
||||
template<typename T> concept is_member_func_ptr = is_member_ptr<T> && _is_member_ptr_helper<un_cv_t<T>>::kIsFunc;
|
||||
template<typename T> concept is_member_data_ptr = is_member_ptr<T> && !_is_member_ptr_helper<un_cv_t<T>>::kIsFunc;
|
||||
|
||||
template<typename T> concept is_object = !is_void<T> && !is_ref<T> && !is_func<T>;
|
||||
|
||||
template<typename T> struct _array_helper : false_type { using type = T; };
|
||||
@ -220,25 +239,111 @@ template<> struct _is_floating_point_helper<double> : true_type {};
|
||||
|
||||
template<typename T> concept is_floating_point = _is_floating_point_helper<un_cv_t<T>>::value;
|
||||
|
||||
template<typename T> struct _is_integer_helper : false_type {};
|
||||
template<> struct _is_integer_helper<int8_t> : true_type {};
|
||||
template<> struct _is_integer_helper<int16_t> : true_type {};
|
||||
template<> struct _is_integer_helper<int32_t> : true_type {};
|
||||
template<> struct _is_integer_helper<int64_t> : true_type {};
|
||||
template<> struct _is_integer_helper<uint8_t> : true_type {};
|
||||
template<> struct _is_integer_helper<uint16_t> : true_type {};
|
||||
template<> struct _is_integer_helper<uint32_t> : true_type {};
|
||||
template<> struct _is_integer_helper<uint64_t> : true_type {};
|
||||
template<typename T> struct _integer_traits
|
||||
{
|
||||
static constexpr bool kSigned = false;
|
||||
static constexpr bool kUnsigned = false;
|
||||
};
|
||||
|
||||
template<typename T> concept is_integer = _is_integer_helper<un_cv_t<T>>::value;
|
||||
template<> struct _integer_traits<uint8_t>
|
||||
{
|
||||
static constexpr bool kSigned = false;
|
||||
static constexpr bool kUnsigned = true;
|
||||
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_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_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_unsigned = uint64_t;
|
||||
};
|
||||
|
||||
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>
|
||||
{
|
||||
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>
|
||||
{
|
||||
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>
|
||||
{
|
||||
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;
|
||||
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_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<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<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<> 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;
|
||||
|
||||
|
@ -135,6 +135,12 @@ static_assert(!asl::is_ref<void>);
|
||||
static_assert(!asl::is_ref<void()>);
|
||||
static_assert(!asl::is_ref<void() const &&>);
|
||||
|
||||
struct MyClass
|
||||
{
|
||||
int data;
|
||||
int fn(int x) { return x; } // NOLINT
|
||||
};
|
||||
|
||||
static_assert(asl::is_ptr<int*>);
|
||||
static_assert(asl::is_ptr<const int* const>);
|
||||
static_assert(asl::is_ptr<const volatile int*>);
|
||||
@ -142,6 +148,28 @@ static_assert(!asl::is_ptr<int>);
|
||||
static_assert(!asl::is_ptr<void>);
|
||||
static_assert(!asl::is_ptr<void()>);
|
||||
static_assert(!asl::is_ptr<void() const &&>);
|
||||
static_assert(!asl::is_ptr<int MyClass::*>);
|
||||
static_assert(!asl::is_ptr<int (MyClass::*)(int)>);
|
||||
|
||||
static_assert(!asl::is_member_ptr<int*>);
|
||||
static_assert(!asl::is_member_ptr<void()>);
|
||||
static_assert(!asl::is_member_ptr<void() const &&>);
|
||||
static_assert(asl::is_member_ptr<int MyClass::*>);
|
||||
static_assert(asl::is_member_ptr<int (MyClass::*)(int)>);
|
||||
|
||||
static_assert(!asl::is_member_data_ptr<int*>);
|
||||
static_assert(!asl::is_member_data_ptr<void()>);
|
||||
static_assert(!asl::is_member_data_ptr<void() const &&>);
|
||||
static_assert(asl::is_member_data_ptr<int MyClass::*>);
|
||||
static_assert(!asl::is_member_data_ptr<int (MyClass::*)(int)>);
|
||||
|
||||
static_assert(!asl::is_member_func_ptr<int*>);
|
||||
static_assert(!asl::is_member_func_ptr<void()>);
|
||||
static_assert(!asl::is_member_func_ptr<void() const &&>);
|
||||
static_assert(!asl::is_member_func_ptr<int MyClass::*>);
|
||||
static_assert(asl::is_member_func_ptr<int (MyClass::*)(int)>);
|
||||
static_assert(asl::is_member_func_ptr<int (MyClass::*)(int) const>);
|
||||
static_assert(asl::is_member_func_ptr<int (MyClass::*)(int) volatile &&>);
|
||||
|
||||
static_assert(asl::same_as<int, asl::tame_t<int>>);
|
||||
static_assert(asl::same_as<int(), asl::tame_t<int()>>);
|
||||
@ -224,6 +252,8 @@ static_assert(!asl::convertible_to<const int16_t(&)[], int16_t(&)[]>);
|
||||
static_assert(!asl::convertible_to<D(&)[], C(&)[]>);
|
||||
|
||||
static_assert(asl::derived_from<Derived, Base>);
|
||||
static_assert(asl::derived_from<Derived, Derived>);
|
||||
static_assert(asl::derived_from<Base, Base>);
|
||||
static_assert(!asl::derived_from<Base, Derived>);
|
||||
static_assert(!asl::derived_from<D, C>);
|
||||
static_assert(!asl::derived_from<C, D>);
|
||||
@ -231,9 +261,20 @@ static_assert(!asl::derived_from<uint8_t, uint16_t>);
|
||||
static_assert(!asl::derived_from<uint16_t, uint8_t>);
|
||||
static_assert(!asl::derived_from<int, int>);
|
||||
|
||||
static_assert(asl::same_or_derived_from<Derived, Base>);
|
||||
static_assert(asl::same_or_derived_from<Derived, Derived>);
|
||||
static_assert(asl::same_or_derived_from<Base, Base>);
|
||||
static_assert(!asl::same_or_derived_from<Base, Derived>);
|
||||
static_assert(!asl::same_or_derived_from<D, C>);
|
||||
static_assert(!asl::same_or_derived_from<C, D>);
|
||||
static_assert(!asl::same_or_derived_from<uint8_t, uint16_t>);
|
||||
static_assert(!asl::same_or_derived_from<uint16_t, uint8_t>);
|
||||
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>);
|
||||
@ -322,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>);
|
||||
@ -332,3 +382,46 @@ static_assert(!asl::same_as<asl::decay_t<int[4][2]>, int*>);
|
||||
static_assert(!asl::same_as<asl::decay_t<int[4][2]>, int**>);
|
||||
static_assert(asl::same_as<asl::decay_t<int[4][2]>, int(*)[2]>);
|
||||
static_assert(asl::same_as<asl::decay_t<int(int)>, int(*)(int)>);
|
||||
|
||||
enum EnumU8 : uint8_t {};
|
||||
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>);
|
||||
|
101
asl/base/numeric.hpp
Normal file
101
asl/base/numeric.hpp
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 2025 Steven Le Rouzic
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#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
|
||||
{
|
||||
|
||||
template<is_integer T>
|
||||
constexpr bool is_pow2(T x)
|
||||
{
|
||||
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>;
|
||||
|
||||
template<is_numeric T>
|
||||
constexpr T min(T a, T b)
|
||||
{
|
||||
return (a <= b) ? a : b;
|
||||
}
|
||||
|
||||
template<is_numeric T>
|
||||
constexpr T max(T a, T b)
|
||||
{
|
||||
return (a >= b) ? a : b;
|
||||
}
|
||||
|
||||
template<is_numeric T>
|
||||
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
|
||||
|
68
asl/base/numeric_tests.cpp
Normal file
68
asl/base/numeric_tests.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2025 Steven Le Rouzic
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
#include "asl/base/numeric.hpp"
|
||||
|
||||
#include "asl/testing/testing.hpp"
|
||||
|
||||
ASL_TEST(is_pow2)
|
||||
{
|
||||
ASL_TEST_EXPECT(asl::is_pow2(4));
|
||||
ASL_TEST_EXPECT(asl::is_pow2(65536));
|
||||
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);
|
||||
}
|
@ -47,9 +47,6 @@ template<typename T>
|
||||
namespace asl
|
||||
{
|
||||
|
||||
struct in_place_t {};
|
||||
static constexpr in_place_t in_place{};
|
||||
|
||||
template<moveable T>
|
||||
constexpr void swap(T& a, T& b)
|
||||
{
|
||||
@ -72,37 +69,10 @@ constexpr U bit_cast(T value) requires (sizeof(T) == sizeof(U))
|
||||
return __builtin_bit_cast(U, value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr T min(T a, T b)
|
||||
template<is_enum T>
|
||||
constexpr auto to_underlying(T value)
|
||||
{
|
||||
return (a <= b) ? a : b;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr T max(T a, T b)
|
||||
{
|
||||
return (a >= b) ? a : b;
|
||||
}
|
||||
|
||||
constexpr uint64_t round_up_pow2(uint64_t v)
|
||||
{
|
||||
ASL_ASSERT(v <= 0x8000'0000'0000'0000);
|
||||
|
||||
v -= 1;
|
||||
|
||||
v |= v >> 1U;
|
||||
v |= v >> 2U;
|
||||
v |= v >> 4U;
|
||||
v |= v >> 8U;
|
||||
v |= v >> 16U;
|
||||
v |= v >> 32U;
|
||||
|
||||
return v + 1;
|
||||
}
|
||||
|
||||
constexpr bool is_pow2(isize_t v)
|
||||
{
|
||||
return v > 0 && ((v - 1) & v) == 0; // NOLINT
|
||||
return static_cast<underlying_t<T>>(value);
|
||||
}
|
||||
|
||||
// NOLINTBEGIN(*-macro-parentheses)
|
||||
|
@ -81,3 +81,20 @@ ASL_TEST(forward_like)
|
||||
ASL_TEST_EXPECT(test_fwd_like<int&&>(9) == 4);
|
||||
}
|
||||
|
||||
enum class Enum : int // NOLINT
|
||||
{
|
||||
kOne = 1,
|
||||
kTwo = 2,
|
||||
};
|
||||
|
||||
enum Enum2 : int // NOLINT
|
||||
{
|
||||
kOne = 1,
|
||||
kTwo = 2,
|
||||
};
|
||||
|
||||
static_assert(asl::to_underlying(Enum::kOne) == 1);
|
||||
static_assert(asl::to_underlying(Enum::kTwo) == 2);
|
||||
static_assert(asl::to_underlying(kOne) == 1);
|
||||
static_assert(asl::to_underlying(kTwo) == 2);
|
||||
|
||||
|
@ -13,6 +13,7 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
"//asl/memory",
|
||||
"//asl/memory:allocator",
|
||||
"//asl/base",
|
||||
"//asl/types:span",
|
||||
"//asl/hashing",
|
||||
@ -20,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 = [
|
||||
@ -28,6 +45,7 @@ cc_library(
|
||||
deps = [
|
||||
"//asl/base",
|
||||
"//asl/memory",
|
||||
"//asl/memory:allocator",
|
||||
"//asl/types:maybe_uninit",
|
||||
"//asl/hashing",
|
||||
],
|
||||
@ -42,6 +60,7 @@ cc_library(
|
||||
deps = [
|
||||
"//asl/base",
|
||||
"//asl/memory",
|
||||
"//asl/memory:allocator",
|
||||
"//asl/hashing",
|
||||
":hash_set",
|
||||
],
|
||||
@ -72,6 +91,7 @@ cc_library(
|
||||
],
|
||||
) for name in [
|
||||
"buffer",
|
||||
"chunked_buffer",
|
||||
"hash_map",
|
||||
"hash_set",
|
||||
"intrusive_list",
|
||||
|
@ -9,13 +9,15 @@
|
||||
#include "asl/memory/memory.hpp"
|
||||
#include "asl/base/annotations.hpp"
|
||||
#include "asl/base/assert.hpp"
|
||||
#include "asl/base/bit.hpp"
|
||||
#include "asl/types/span.hpp"
|
||||
#include "asl/hashing/hash.hpp"
|
||||
|
||||
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{};
|
||||
@ -89,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);
|
||||
@ -98,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);
|
||||
@ -134,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);
|
||||
@ -161,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);
|
||||
@ -243,7 +273,6 @@ public:
|
||||
}
|
||||
|
||||
constexpr buffer(buffer&& other)
|
||||
requires moveable<T>
|
||||
: buffer(std::move(other.m_allocator))
|
||||
{
|
||||
move_from_other(std::move(other), false);
|
||||
@ -258,7 +287,6 @@ public:
|
||||
}
|
||||
|
||||
constexpr buffer& operator=(buffer&& other)
|
||||
requires moveable<T>
|
||||
{
|
||||
if (&other == this) { return *this; }
|
||||
move_from_other(std::move(other), true);
|
||||
@ -270,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());
|
||||
@ -315,12 +351,11 @@ 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);
|
||||
|
||||
new_capacity = static_cast<isize_t>(round_up_pow2(static_cast<uint64_t>(new_capacity)));
|
||||
new_capacity = static_cast<isize_t>(bit_ceil(static_cast<uint64_t>(new_capacity)));
|
||||
|
||||
T* old_data = data();
|
||||
const isize_t old_capacity = capacity();
|
||||
@ -352,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)
|
||||
{
|
||||
@ -373,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)
|
||||
@ -384,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)
|
||||
@ -409,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()};
|
||||
}
|
||||
|
||||
@ -431,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]);
|
||||
}
|
||||
|
||||
|
@ -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*>);
|
||||
|
||||
|
405
asl/containers/chunked_buffer.hpp
Normal file
405
asl/containers/chunked_buffer.hpp
Normal 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
|
||||
|
377
asl/containers/chunked_buffer_tests.cpp
Normal file
377
asl/containers/chunked_buffer_tests.cpp
Normal 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());
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -7,6 +7,8 @@
|
||||
#include "asl/base/annotations.hpp"
|
||||
#include "asl/base/utility.hpp"
|
||||
#include "asl/base/meta.hpp"
|
||||
#include "asl/base/bit.hpp"
|
||||
#include "asl/base/numeric.hpp"
|
||||
#include "asl/memory/allocator.hpp"
|
||||
#include "asl/memory/memory.hpp"
|
||||
#include "asl/types/maybe_uninit.hpp"
|
||||
@ -83,7 +85,7 @@ protected:
|
||||
ASL_ASSERT(size > 0);
|
||||
return max<isize_t>(
|
||||
kMinCapacity,
|
||||
static_cast<isize_t>(round_up_pow2((static_cast<uint64_t>(size) * 4 + 2) / 3)));
|
||||
static_cast<isize_t>(bit_ceil((static_cast<uint64_t>(size) * 4 + 2) / 3)));
|
||||
}
|
||||
|
||||
static void insert_inner(
|
||||
|
@ -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 };
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -4,10 +4,14 @@
|
||||
|
||||
#include "asl/formatting/format.hpp"
|
||||
#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] = {
|
||||
@ -23,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;
|
||||
}
|
||||
@ -101,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);
|
||||
}
|
||||
|
@ -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>());
|
||||
|
81
asl/handle_pool/BUILD.bazel
Normal file
81
asl/handle_pool/BUILD.bazel
Normal 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",
|
||||
],
|
||||
)
|
162
asl/handle_pool/dense_handle_pool.hpp
Normal file
162
asl/handle_pool/dense_handle_pool.hpp
Normal 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
|
117
asl/handle_pool/dense_handle_pool_tests.cpp
Normal file
117
asl/handle_pool/dense_handle_pool_tests.cpp
Normal 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);
|
||||
}
|
378
asl/handle_pool/index_pool.hpp
Normal file
378
asl/handle_pool/index_pool.hpp
Normal 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
|
||||
|
348
asl/handle_pool/index_pool_tests.cpp
Normal file
348
asl/handle_pool/index_pool_tests.cpp
Normal 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);
|
||||
}
|
||||
|
158
asl/handle_pool/sparse_handle_pool.hpp
Normal file
158
asl/handle_pool/sparse_handle_pool.hpp
Normal 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
|
117
asl/handle_pool/sparse_handle_pool_tests.cpp
Normal file
117
asl/handle_pool/sparse_handle_pool_tests.cpp
Normal 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);
|
||||
}
|
@ -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
|
||||
|
@ -9,16 +9,27 @@ package(
|
||||
cc_library(
|
||||
name = "memory",
|
||||
hdrs = [
|
||||
"allocator.hpp",
|
||||
"layout.hpp",
|
||||
"memory.hpp",
|
||||
],
|
||||
srcs = [
|
||||
"allocator.cpp",
|
||||
],
|
||||
deps = [
|
||||
"//asl/base",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "allocator",
|
||||
hdrs = [
|
||||
"allocator.hpp",
|
||||
],
|
||||
srcs = [
|
||||
"allocator.cpp",
|
||||
],
|
||||
deps = [
|
||||
"//asl/base",
|
||||
"//asl/memory",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -17,6 +17,17 @@ constexpr void* operator new(size_t, void* ptr) noexcept
|
||||
namespace asl
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]]
|
||||
constexpr T* address_of(T& obj)
|
||||
{
|
||||
return __builtin_addressof(obj);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void address_of(const T&& obj) = delete;
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr isize_t memcmp(const void* a, const void* b, isize_t size)
|
||||
{
|
||||
return __builtin_memcmp(a, b, static_cast<size_t>(size));
|
||||
|
@ -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",
|
||||
]]
|
||||
|
162
asl/strings/parse_number.cpp
Normal file
162
asl/strings/parse_number.cpp
Normal 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);
|
||||
}
|
||||
|
35
asl/strings/parse_number.hpp
Normal file
35
asl/strings/parse_number.hpp
Normal 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
|
||||
|
35
asl/strings/parse_number_float.cpp
Normal file
35
asl/strings/parse_number_float.cpp
Normal 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
|
||||
|
24
asl/strings/parse_number_integer_table.py
Normal file
24
asl/strings/parse_number_integer_table.py
Normal 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)
|
||||
|
201
asl/strings/parse_number_tests.cpp
Normal file
201
asl/strings/parse_number_tests.cpp
Normal 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());
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,10 +9,12 @@ package(
|
||||
cc_library(
|
||||
name = "utils",
|
||||
hdrs = [
|
||||
"counting_allocator.hpp",
|
||||
"types.hpp",
|
||||
],
|
||||
deps = [
|
||||
"//asl/base",
|
||||
"//asl/memory:allocator",
|
||||
],
|
||||
visibility = ["//asl:__subpackages__"],
|
||||
)
|
||||
|
48
asl/tests/counting_allocator.hpp
Normal file
48
asl/tests/counting_allocator.hpp
Normal 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>);
|
@ -77,7 +77,7 @@ struct DestructorObserver
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
destroyed = asl::exchange(other.destroyed, nullptr);
|
||||
asl::swap(destroyed, other.destroyed);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
@ -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 = [
|
||||
@ -14,6 +26,7 @@ cc_library(
|
||||
deps = [
|
||||
"//asl/base",
|
||||
"//asl/memory",
|
||||
"//asl/memory:allocator",
|
||||
"//asl/hashing",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
@ -86,6 +99,7 @@ cc_library(
|
||||
deps = [
|
||||
"//asl/base",
|
||||
"//asl/memory",
|
||||
"//asl/memory:allocator",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@ -97,6 +111,7 @@ cc_library(
|
||||
],
|
||||
deps = [
|
||||
"//asl/base",
|
||||
"//asl/memory",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@ -121,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
70
asl/types/array.hpp
Normal 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
122
asl/types/array_tests.cpp
Normal 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);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "asl/base/utility.hpp"
|
||||
#include "asl/base/meta.hpp"
|
||||
#include "asl/base/functional.hpp"
|
||||
#include "asl/memory/memory.hpp"
|
||||
|
||||
namespace asl
|
||||
{
|
||||
@ -43,7 +44,7 @@ public:
|
||||
&& same_as<invoke_result_t<T, Args...>, R>
|
||||
)
|
||||
// NOLINTNEXTLINE(*cast*)
|
||||
: m_obj{const_cast<void*>(reinterpret_cast<const void*>(&t))}
|
||||
: m_obj{const_cast<void*>(reinterpret_cast<const void*>(address_of(t)))}
|
||||
, m_invoke{invoke<un_ref_t<T>>}
|
||||
{}
|
||||
|
||||
@ -56,7 +57,7 @@ public:
|
||||
)
|
||||
{
|
||||
// NOLINTNEXTLINE(*cast*)
|
||||
m_obj = const_cast<void*>(reinterpret_cast<const void*>(&t));
|
||||
m_obj = const_cast<void*>(reinterpret_cast<const void*>(address_of(t)));
|
||||
m_invoke = invoke<un_ref_t<T>>;
|
||||
|
||||
return *this;
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
{
|
||||
|
4
bazel/clang_toolchain/BUILD.bazel
Normal file
4
bazel/clang_toolchain/BUILD.bazel
Normal file
@ -0,0 +1,4 @@
|
||||
# Copyright 2025 Steven Le Rouzic
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
8
bazel/clang_toolchain/clang_config.bzl.tpl
Normal file
8
bazel/clang_toolchain/clang_config.bzl.tpl
Normal 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}
|
||||
|
19
bazel/clang_toolchain/clang_toolchain.BUILD.bazel
Normal file
19
bazel/clang_toolchain/clang_toolchain.BUILD.bazel
Normal 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
|
||||
]
|
||||
|
113
bazel/clang_toolchain/config_detection.bzl
Normal file
113
bazel/clang_toolchain/config_detection.bzl
Normal 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",
|
||||
),
|
||||
)
|
596
bazel/clang_toolchain/toolchain_config.bzl
Normal file
596
bazel/clang_toolchain/toolchain_config.bzl
Normal 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),
|
||||
}
|
||||
)
|
@ -1 +1 @@
|
||||
bazel run //tools:refresh_clangd --config=linux -- --config=linux
|
||||
bazel run //tools:refresh_clangd
|
||||
|
5
todo.txt
5
todo.txt
@ -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
|
||||
|
||||
|
@ -19,6 +19,7 @@ TO_FIX = [
|
||||
"**/*.cpp",
|
||||
"**/*.py",
|
||||
"**/*.bzl",
|
||||
"**/*.bzl.tpl",
|
||||
"**/*.bazel",
|
||||
"**/*.txt",
|
||||
"**/*.bat",
|
||||
|
2
vendor/dragonbox/BUILD.bazel
vendored
2
vendor/dragonbox/BUILD.bazel
vendored
@ -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
25
vendor/fast_float/BUILD.bazel
vendored
Normal 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
27
vendor/fast_float/LICENSE.txt
vendored
Normal 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
4443
vendor/fast_float/fast_float.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user