├── .clang-format ├── .clang-tidy ├── .clangd ├── .dir-locals.el ├── .gdbinit ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.org ├── examples ├── CMakeLists.txt ├── cat.cpp ├── client.cpp ├── echo.cpp ├── hello.cpp ├── hello_libc.cpp ├── memory_copy_libc.cpp ├── server.cpp └── window.cpp ├── format.cmake ├── gdb_pretty_printers ├── cat_printers.py └── ruff.toml ├── src ├── CMakeLists.txt ├── global_includes.hpp └── libraries │ ├── algorithm │ └── cat │ │ └── algorithm │ ├── allocator │ └── cat │ │ ├── allocator │ │ ├── linear_allocator │ │ ├── null_allocator │ │ ├── page_allocator │ │ └── pool_allocator │ ├── arithmetic │ └── cat │ │ ├── arithmetic │ │ └── arithmetic_interface │ ├── array │ └── cat │ │ └── array │ ├── atomic │ └── cat │ │ └── atomic │ ├── bit │ ├── cat │ │ └── bit │ └── implementations │ │ ├── align_down.tpp │ │ ├── align_up.tpp │ │ └── is_aligned.tpp │ ├── bitset │ └── cat │ │ └── bitset │ ├── cast │ └── cat │ │ └── cast │ ├── collection │ └── cat │ │ └── collection │ ├── compare │ └── cat │ │ └── compare │ ├── cpuid │ └── cat │ │ └── cpuid │ ├── debug │ ├── cat │ │ └── debug │ └── implementations │ │ ├── assert.cpp │ │ ├── breakpoint.cpp │ │ ├── default_assert_handler.cpp │ │ └── verify.cpp │ ├── enum │ └── cat │ │ └── enum │ ├── file │ └── cat │ │ └── file │ ├── format │ ├── cat │ │ ├── detail │ │ │ ├── ftoa_dragonbox.hpp │ │ │ └── itoa_jeaiii.hpp │ │ └── format │ └── implementations │ │ ├── ftoa_dragonbox.cpp │ │ └── itoa_jeaiii.cpp │ ├── functional │ └── cat │ │ └── functional │ ├── initializer_list │ └── cat │ │ └── initializer_list │ ├── iterator │ └── cat │ │ ├── insert_iterators │ │ └── iterator │ ├── limits │ └── cat │ │ └── limits │ ├── linux │ ├── cat │ │ └── linux │ └── implementations │ │ ├── block_all_signals.cpp │ │ ├── clonearguments.cpp │ │ ├── create_socket_local.cpp │ │ ├── is_a_tty.cpp │ │ ├── process.cpp │ │ ├── process.tpp │ │ ├── raise.cpp │ │ ├── raise_here.cpp │ │ ├── read_char.cpp │ │ ├── sys_accept.cpp │ │ ├── sys_bind.cpp │ │ ├── sys_clone.cpp │ │ ├── sys_close.cpp │ │ ├── sys_connect.cpp │ │ ├── sys_creat.cpp │ │ ├── sys_fstat.cpp │ │ ├── sys_getpid.cpp │ │ ├── sys_ioctl.cpp │ │ ├── sys_listen.cpp │ │ ├── sys_mmap.cpp │ │ ├── sys_munmap.cpp │ │ ├── sys_open.cpp │ │ ├── sys_read.cpp │ │ ├── sys_readv.cpp │ │ ├── sys_recv.cpp │ │ ├── sys_rt_sigprocmask.cpp │ │ ├── sys_send.cpp │ │ ├── sys_sendto.cpp │ │ ├── sys_socket.cpp │ │ ├── sys_stat.cpp │ │ ├── sys_tkill.cpp │ │ ├── sys_unlink.cpp │ │ ├── sys_wait4.cpp │ │ ├── sys_waitid.cpp │ │ ├── sys_write.cpp │ │ ├── sys_writev.cpp │ │ ├── syscall.tpp │ │ ├── syscall0.cpp │ │ ├── syscall1.cpp │ │ ├── syscall2.cpp │ │ ├── syscall3.cpp │ │ ├── syscall4.cpp │ │ ├── syscall5.cpp │ │ ├── syscall6.cpp │ │ ├── tty_get_attributes.cpp │ │ ├── tty_set_attributes.cpp │ │ └── wait_pid.cpp │ ├── list │ └── cat │ │ └── list │ ├── match │ └── cat │ │ └── match │ ├── math │ └── cat │ │ └── math │ ├── maybe │ └── cat │ │ └── maybe │ ├── memory │ ├── cat │ │ └── memory │ └── implementations │ │ ├── copy_memory.cpp │ │ └── copy_memory_small.cpp │ ├── meta │ ├── cat │ │ └── meta │ └── implementations │ │ ├── common_reference.tpp │ │ └── constant_evaluate.tpp │ ├── new │ └── implementations │ │ └── new.cpp │ ├── notype │ └── cat │ │ └── notype │ ├── ring │ └── cat │ │ └── ring │ ├── runtime │ ├── cat │ │ └── runtime │ └── implementations │ │ ├── __cxa_atexit.cpp │ │ ├── __cxa_pure_virtual.cpp │ │ ├── __dso_handle.cpp │ │ ├── __stack_chk_fail.cpp │ │ ├── _start.cpp │ │ ├── exit.cpp │ │ ├── load_argc.cpp │ │ ├── load_base_stack_pointer.cpp │ │ ├── longjmp.cpp │ │ └── setjmp.cpp │ ├── sanitizer │ └── cat │ │ └── sanitizer │ ├── scaredy │ └── cat │ │ └── scaredy │ ├── simd │ ├── cat │ │ ├── detail │ │ │ ├── simd_avx2.hpp │ │ │ ├── simd_avx2_fwd.hpp │ │ │ ├── simd_impl.hpp │ │ │ ├── simd_sse42.hpp │ │ │ └── simd_sse42_fwd.hpp │ │ └── simd │ └── implementations │ │ ├── cmp_implicit_str_c.tpp │ │ ├── cmp_implicit_str_i.tpp │ │ ├── compare_implicit_length_strings.tpp │ │ ├── compare_implicit_length_strings_return_index.tpp │ │ ├── is_avx2_supported.cpp │ │ ├── is_avx512f_supported.cpp │ │ ├── is_avx512vl_supported.cpp │ │ ├── is_avx_supported.cpp │ │ ├── is_mmx_supported.cpp │ │ ├── is_sse1_supported.cpp │ │ ├── is_sse2_supported.cpp │ │ ├── is_sse3_supported.cpp │ │ ├── is_sse4_1_supported.cpp │ │ ├── is_sse4_2_supported.cpp │ │ ├── is_ssse3_supported.cpp │ │ ├── sfence.cpp │ │ ├── shuffle.tpp │ │ ├── stream_in.tpp │ │ ├── zero_avx_registers.cpp │ │ └── zero_upper_avx_registers.cpp │ ├── socket │ └── cat │ │ └── socket │ ├── span │ └── cat │ │ └── span │ ├── string │ ├── cat │ │ └── string │ └── implementations │ │ ├── compare_strings.cpp │ │ ├── eprint.cpp │ │ ├── eprintln.cpp │ │ ├── memcpy.cpp │ │ ├── memset.cpp │ │ ├── print.cpp │ │ ├── println.cpp │ │ ├── string_length.tpp │ │ └── strlen.cpp │ ├── thread │ └── cat │ │ └── thread │ ├── tui │ └── cat │ │ └── tui │ ├── tuple │ ├── cat │ │ └── tuple │ └── implementations │ │ └── tuple_cat.tpp │ ├── type_list │ └── cat │ │ └── type_list │ ├── unique │ └── cat │ │ └── unique │ ├── utility │ └── cat │ │ └── utility │ ├── variant │ └── cat │ │ └── variant │ ├── vec │ └── cat │ │ └── vec │ └── x11 │ └── cat │ └── x11 └── tests ├── CMakeLists.txt ├── src ├── test_alloc.cpp ├── test_arithmetic.cpp ├── test_arrays.cpp ├── test_bit.cpp ├── test_bitset.cpp ├── test_cast.cpp ├── test_compare_strings.cpp ├── test_copy_memory.cpp ├── test_cpuid.cpp ├── test_dummy.cpp ├── test_format_strings.cpp ├── test_invoke.cpp ├── test_linear_allocator.cpp ├── test_list.cpp ├── test_math.cpp ├── test_maybe.cpp ├── test_meta.cpp ├── test_paging_memory.cpp ├── test_pool_allocator.cpp ├── test_raii.cpp ├── test_ring.cpp ├── test_scaredy.cpp ├── test_set_memory.cpp ├── test_simd.cpp ├── test_string_length.cpp ├── test_stringify.cpp ├── test_thread.cpp ├── test_tuple.cpp ├── test_typelist.cpp ├── test_variant.cpp └── test_vec.cpp ├── unit_tests.cpp └── unit_tests.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | IndentWidth: 3 4 | ColumnLimit: 80 5 | MaxEmptyLinesToKeep: 1 6 | ContinuationIndentWidth: 3 7 | 8 | LambdaBodyIndentation: Signature 9 | ReferenceAlignment: Pointer 10 | QualifierAlignment: Custom 11 | QualifierOrder: ['friend', 'static', 'inline', 'constexpr', 'type', 'const', 'volatile' ] 12 | AlignArrayOfStructures: Left 13 | RequiresClausePosition: OwnLine 14 | InsertBraces: true 15 | IndentRequiresClause: true 16 | BreakAfterAttributes: Always 17 | RequiresExpressionIndentation: Keyword 18 | IntegerLiteralSeparator: 19 | Binary: 4 20 | Decimal: 3 21 | Hex: 8 22 | 23 | # TODO: This doesn't seem to work in clang-format 20: 24 | SeparateDefinitionBlocks: Always 25 | 26 | Cpp11BracedListStyle: true 27 | AlignAfterOpenBracket: Align 28 | AlignConsecutiveTableGenCondOperatorColons: None 29 | AlignConsecutiveBitFields: AcrossEmptyLinesAndComments 30 | AlignEscapedNewlines: Left 31 | BreakBeforeBinaryOperators: NonAssignment 32 | AlignOperands: Align 33 | AlignTrailingComments: true 34 | AllowAllArgumentsOnNextLine: true 35 | AllowAllConstructorInitializersOnNextLine: true 36 | AllowAllParametersOfDeclarationOnNextLine: true 37 | AllowShortEnumsOnASingleLine: false 38 | AllowShortBlocksOnASingleLine: Never 39 | AllowShortCaseLabelsOnASingleLine: false 40 | AllowShortFunctionsOnASingleLine: None 41 | AllowShortLambdasOnASingleLine: None 42 | AllowShortIfStatementsOnASingleLine: Never 43 | AllowShortCompoundRequirementOnASingleLine: true 44 | AllowShortLoopsOnASingleLine: false 45 | AlwaysBreakBeforeMultilineStrings: false 46 | AlwaysBreakTemplateDeclarations: Yes 47 | BinPackArguments: true 48 | BinPackParameters: BinPack 49 | BreakBeforeBraces: Attach 50 | BreakAfterReturnType: All 51 | BreakBeforeConceptDeclarations: Always 52 | BreakBeforeInheritanceComma: false 53 | BreakInheritanceList: AfterComma 54 | BreakBeforeTernaryOperators: true 55 | BreakConstructorInitializersBeforeComma: false 56 | BreakConstructorInitializers: BeforeColon 57 | BreakBeforeInlineASMColon: Always 58 | BreakStringLiterals: true 59 | CompactNamespaces: false 60 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 61 | DeriveLineEnding: false 62 | DerivePointerAlignment: false 63 | EmptyLineBeforeAccessModifier: Always 64 | ExperimentalAutoDetectBinPacking: false 65 | ShortNamespaceLines: 16 66 | FixNamespaceComments: true 67 | SortIncludes: true 68 | IncludeBlocks: Regroup 69 | IncludeCategories: 70 | - Regex: '^' 71 | Priority: 2 72 | SortPriority: 0 73 | CaseSensitive: true 74 | - Regex: '^<.*\.hpp>' 75 | Priority: 1 76 | SortPriority: 0 77 | CaseSensitive: true 78 | - Regex: '^<.*\.tpp>' 79 | Priority: 1 80 | SortPriority: 0 81 | CaseSensitive: true 82 | - Regex: '^<.*' 83 | Priority: 2 84 | SortPriority: 0 85 | CaseSensitive: true 86 | - Regex: '.*' 87 | Priority: 3 88 | SortPriority: 0 89 | CaseSensitive: true 90 | IndentCaseLabels: true 91 | IndentCaseBlocks: true 92 | IndentGotoLabels: false 93 | IndentPPDirectives: None 94 | IndentExternBlock: AfterExternBlock 95 | IndentWrappedFunctionNames: false 96 | KeepEmptyLinesAtTheStartOfBlocks: false 97 | NamespaceIndentation: None 98 | PenaltyBreakAssignment: 2 99 | PenaltyBreakBeforeFirstCallParameter: 1 100 | PenaltyBreakComment: 300 101 | PenaltyBreakFirstLessLess: 120 102 | PenaltyBreakString: 1000 103 | PenaltyBreakTemplateDeclaration: 10 104 | PenaltyExcessCharacter: 1000000 105 | PenaltyReturnTypeOnItsOwnLine: 0 106 | PenaltyIndentedWhitespace: 0 107 | PointerAlignment: Left 108 | ReflowComments: true 109 | SortUsingDeclarations: LexicographicNumeric 110 | SpaceAfterCStyleCast: false 111 | SpaceAfterLogicalNot: false 112 | SpaceAfterTemplateKeyword: true 113 | SpaceBeforeAssignmentOperators: true 114 | SpaceBeforeCaseColon: false 115 | SpaceBeforeCpp11BracedList: false 116 | SpaceBeforeCtorInitializerColon: true 117 | SpaceBeforeInheritanceColon: true 118 | SpaceBeforeParens: ControlStatements 119 | SpaceAroundPointerQualifiers: Default 120 | SpaceBeforeRangeBasedForLoopColon: true 121 | SpaceInEmptyBlock: false 122 | SpaceInEmptyParentheses: false 123 | SpacesBeforeTrailingComments: 2 124 | SpacesInAngles: false 125 | SpacesInConditionalStatement: false 126 | SpacesInContainerLiterals: true 127 | SpacesInCStyleCastParentheses: false 128 | SpacesInParentheses: false 129 | SpacesInSquareBrackets: false 130 | SpaceBeforeSquareBrackets: false 131 | BitFieldColonSpacing: Both 132 | Standard: Auto 133 | UseCRLF: false 134 | UseTab: Never 135 | ... 136 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: "bugprone-*, 3 | performance-*, 4 | modernize-*, 5 | readability-*, 6 | # clang-analyzer-*, 7 | # cppcoreguidelines-*, 8 | misc-*, 9 | # DISABLE SOME CHECKS: 10 | -bugprone-easily-swappable-parameters, 11 | -bugprone-reserved-identifier, 12 | -performance-noexcept-move-constructor, 13 | -modernize-avoid-c-arrays, 14 | -modernize-use-auto, 15 | -readability-magic-numbers, 16 | -readability-enum-initial-value, 17 | -readability-identifier-length, 18 | -readability-uppercase-literal-suffix, 19 | -readability-function-cognitive-complexity, 20 | -readability-convert-member-functions-to-static, 21 | -readability-static-accessed-through-instance, 22 | -readability-named-parameter, 23 | -readability-function-cognitive-complexity, 24 | -readability-else-after-return, 25 | -cppcoreguidelines-avoid-magic-numbers, 26 | -cppcoreguidelines-pro-type-union-access, 27 | -cppcoreguidelines-pro-bounds-pointer-arithmetic, 28 | -cppcoreguidelines-pro-type-reinterpret-cast, 29 | -cppcoreguidelines-macro-usage, 30 | -misc-non-private-member-variables-in-classes, 31 | -misc-unused-using-decls, 32 | -misc-unconventional-assign-operator, 33 | -misc-use-internal-linkage, 34 | " 35 | 36 | WarningsAsErrors: '' 37 | HeaderFilterRegex: ^src 38 | FormatStyle: file 39 | 40 | CheckOptions: 41 | - { key: readability-identifier-naming.VariableCase, value: lower_case } 42 | - { key: readability-identifier-naming.VariableIgnoredRegexp, value: '_[_(A-Z)].*' } 43 | - { key: readability-identifier-naming.MemberCase, value: lower_case } 44 | - { key: readability-identifier-naming.MemberIgnoredRegexp, value: '_[_(A-Z)].*' } 45 | - { key: readability-identifier-naming.FunctionCase, value: lower_case } 46 | - { key: readability-identifier-naming.FunctionIgnoredRegexp, value: '_[_(A-Z)].*' } 47 | - { key: readability-identifier-naming.NamespaceCase, value: lower_case } 48 | - { key: readability-identifier-naming.StructCase, value: lower_case } 49 | - { key: readability-identifier-naming.StructIgnoredRegexp, value: '_[_(A-Z)].*' } 50 | - { key: readability-identifier-naming.ClassCase, value: lower_case } 51 | - { key: readability-identifier-naming.ClassIgnoredRegexp, value: '_[_(A-Z)].*' } 52 | - { key: readability-identifier-naming.TypeAliasCase, value: lower_case } 53 | - { key: readability-identifier-naming.TypedefCase, value: lower_case } 54 | - { key: readability-identifier-naming.PointerParameterIgnoredRegexp, value: '(p+)_.*' } 55 | - { key: readability-identifier-naming.PointerParameterPrefix, value: p_ } 56 | - { key: readability-identifier-naming.LocalPointerIgnoredRegexp, value: '(_|((p+)_.*))' } 57 | - { key: readability-identifier-naming.LocalPointerPrefix, value: p_ } 58 | - { key: readability-identifier-naming.LocalConstantPointerIgnoredRegexp, value: '(_|((p+)_.*))' } 59 | - { key: readability-identifier-naming.LocalConstantPointerPrefix, value: p_ } 60 | - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } 61 | - { key: readability-identifier-naming.PrivateMemberIgnoredRegexp, value: '_' } 62 | - { key: readability-identifier-length.IgnoredParameterNames, value: '[tuxy]' } 63 | - { key: readability-identifier-naming.TemplateParameterIgnoredRegexp, value: '[A-Z]' } 64 | - { key: readability-identifier-naming.TypeAliasIgnoredRegexp, value: '[A-Z]' } 65 | - { key: readability-identifier-naming.TypedefIgnoredRegexp, value: '[A-Z]' } 66 | - { key: performance-unnecessary-value-param.AllowedTypes, value: string;span;io_vector } 67 | - { key: readability-operators-representation.BinaryOperators, value: '&&;&=;&;|;~;!;!=;||;|=;^;^=' } 68 | ... 69 | -------------------------------------------------------------------------------- /.clangd: -------------------------------------------------------------------------------- 1 | Index: 2 | StandardLibrary: No 3 | 4 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((indent-tabs-mode . nil) 2 | (tab-width . 3) 3 | (c-basic-offset . 3) 4 | (c-ts-mode-indent-offset . 3) 5 | (fill-column . 80)))) 6 | -------------------------------------------------------------------------------- /.gdbinit: -------------------------------------------------------------------------------- 1 | # This `.gdbinit` is to be symlinked in all build directories. 2 | # i.e. `ln build/examples/.gdbinit` and `build/tests/Debug/` 3 | break exit 4 | break cat::default_assert_handler 5 | break test_fail 6 | 7 | # TODO: Do not hard-code this path. 8 | source /home/conscat/src/libcat/gdb_pretty_printers/cat_printers.py 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *#* 3 | build*/ 4 | compile_commands.json 5 | .cache/ 6 | .cmake/ 7 | Testing/ 8 | CMakeFiles/ 9 | .gdb_history 10 | .dir-locals-*.el 11 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE: libCat 🐈‍⬛ 2 | #+AUTHOR: Conscat 3 | #+OPTIONS: ^:{} 4 | #+STARTUP: fold 5 | 6 | libCat is a non-POSIX compliant C++26 runtime. 7 | It has no pthreads nor =malloc()=, and by extension no exceptions. 8 | It has type-safe arithmetic, SIMD, fast syscalls, CRTP interfaces, 9 | 10 | * Building 11 | libCat requires a recent development version of Clang 20 from the =trunk= branch. 12 | Compiling libCat is only routinely tested using =ninja=. 13 | Configuring the build requires CMake 3.29. 14 | #+BEGIN_SRC 15 | $ export CXX=clang # Or any path to a Clang 20 executable 16 | $ cmake -B build/ -G 'Ninja Multi-Config' 17 | $ cmake --build build/ 18 | $ ./build/tests/Debug/unit_tests 19 | #+END_SRC 20 | 21 | The =.clang-format= and =.clang-tidy= configurations are only compatible with recent builds of clang-tools from the =main= branch. 22 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Link `.gdbinit` to build directories. 2 | 3 | # `Ninja Multi-Config` generator: 4 | if(CMAKE_CONFIGURATION_TYPES) 5 | add_custom_target( 6 | cat-gdb-examples ALL 7 | COMMAND ${CMAKE_COMMAND} -E create_symlink 8 | # This requires CMake 3.20: 9 | ${PROJECT_SOURCE_DIR}/.gdbinit ${CMAKE_CURRENT_BINARY_DIR}/$/.gdbinit 10 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/$ 11 | ) 12 | 13 | # `Ninja` generator: 14 | else() 15 | add_custom_target( 16 | cat-gdb-examples ALL 17 | COMMAND ${CMAKE_COMMAND} -E create_symlink 18 | ${PROJECT_SOURCE_DIR}/.gdbinit ${CMAKE_BINARY_DIR}/.gdbinit 19 | DEPENDS ${CMAKE_BINARY_DIR} 20 | ) 21 | endif() 22 | add_dependencies(cat cat-gdb-examples) 23 | 24 | add_library(cat-examples INTERFACE) 25 | 26 | target_compile_options(cat-examples INTERFACE 27 | $<$:-fmax-errors=4> 28 | $<$:-ferror-limit=4> 29 | $<$:-fconcepts-diagnostics-depth=2> 30 | ) 31 | 32 | target_link_libraries(cat-examples INTERFACE cat) 33 | 34 | option(CAT_BUILD_ALL_EXAMPLES "Compile all examples unconditionally." ON) 35 | 36 | option(CAT_BUILD_EXAMPLE_HELLO "Compile hello.cpp." OFF) 37 | if(CAT_BUILD_EXAMPLE_HELLO OR CAT_BUILD_ALL_EXAMPLES) 38 | add_executable(hello hello.cpp) 39 | target_compile_options(hello PRIVATE ${CAT_COMPILE_OPTIONS}) 40 | target_compile_definitions(hello PRIVATE "NO_ARGC_ARGV") 41 | target_link_libraries(hello PRIVATE cat-examples) 42 | target_link_options(hello PRIVATE ${CAT_LINK_OPTIONS}) 43 | endif() 44 | 45 | option(CAT_BUILD_EXAMPLE_ECHO "Compile echo.cpp." OFF) 46 | if(CAT_BUILD_EXAMPLE_ECHO OR CAT_BUILD_ALL_EXAMPLES) 47 | add_executable(echo echo.cpp) 48 | target_compile_options(echo PRIVATE ${CAT_COMPILE_OPTIONS}) 49 | target_link_libraries(echo PRIVATE cat-examples) 50 | target_link_options(echo PRIVATE ${CAT_LINK_OPTIONS}) 51 | endif() 52 | 53 | option(CAT_BUILD_EXAMPLE_CLIENT_SERVER "Compile client.cpp and server.cpp." OFF) 54 | if(CAT_BUILD_EXAMPLE_CLIENT_SERVER OR CAT_BUILD_ALL_EXAMPLES) 55 | add_executable(client client.cpp) 56 | target_compile_options(client PRIVATE ${CAT_COMPILE_OPTIONS}) 57 | target_link_libraries(client PRIVATE cat-examples) 58 | target_link_options(client PRIVATE ${CAT_LINK_OPTIONS}) 59 | 60 | add_executable(server server.cpp) 61 | target_compile_options(server PRIVATE ${CAT_COMPILE_OPTIONS}) 62 | target_link_libraries(server PRIVATE cat-examples) 63 | target_link_options(server PRIVATE ${CAT_LINK_OPTIONS}) 64 | endif() 65 | 66 | option(CAT_BUILD_EXAMPLE_WINDOW "Compile window.cpp." OFF) 67 | if(CAT_BUILD_EXAMPLE_WINDOW OR CAT_BUILD_ALL_EXAMPLES) 68 | add_executable(window window.cpp) 69 | target_compile_options(window PRIVATE ${CAT_COMPILE_OPTIONS}) 70 | target_link_libraries(window PRIVATE cat-examples) 71 | target_link_options(window PRIVATE ${CAT_LINK_OPTIONS}) 72 | endif() 73 | 74 | option(CAT_BUILD_EXAMPLE_CAT "Compile cat.cpp." OFF) 75 | if(CAT_BUILD_EXAMPLE_CAT OR CAT_BUILD_ALL_EXAMPLES) 76 | add_executable(unixcat cat.cpp) 77 | target_compile_options(unixcat PRIVATE ${CAT_COMPILE_OPTIONS}) 78 | target_link_libraries(unixcat PRIVATE cat-examples) 79 | target_link_options(unixcat PRIVATE ${CAT_LINK_OPTIONS}) 80 | set_target_properties(unixcat PROPERTIES OUTPUT_NAME cat) 81 | endif() 82 | 83 | # A dummy project is required to guarantee that the directories are generated. 84 | # The directories must be generated for symlinking `.gdbinit` to succeed. 85 | # This can be skipped if one or more other examples are built. 86 | if( 87 | NOT (CAT_BUILD_ALL_EXAMPLES 88 | OR CAT_BUILD_LIBC_EXAMPLES 89 | OR CAT_BUILD_EXAMPLE_CAT 90 | OR CAT_BUILD_EXAMPLE_CLIENT_SERVER 91 | OR CAT_BUILD_EXAMPLE_ECHO 92 | OR CAT_BUILD_EXAMPLE_HELLO 93 | OR CAT_BUILD_EXAMPLE_WINDOW) 94 | ) 95 | add_executable(dummy echo.cpp) 96 | target_link_libraries(dummy PRIVATE cat-examples) 97 | target_link_options(dummy PRIVATE ${CAT_LINK_OPTIONS}) 98 | endif() 99 | 100 | # libC executables are only configured for Release. 101 | list( 102 | APPEND 103 | LIBC_RELEASE_OPTIONS 104 | -fno-exceptions -fno-rtti -fno-unwind-tables 105 | -fno-asynchronous-unwind-tables 106 | -no-pie 107 | -ffunction-sections -fdata-sections 108 | -fvisibility=hidden -fvisibility-inlines-hidden 109 | -flto -fwhole-file -fno-plt 110 | -Wl,-z,noseparate-code,--gc-sections 111 | $<$:-O3> 112 | $<$:-Os> 113 | ) 114 | 115 | # Do not build libC examples by default. 116 | option(CAT_BUILD_LIBC_EXAMPLES "Enable libC examples to compare against libCat." OFF) 117 | 118 | if(CAT_BUILD_LIBC_EXAMPLES) 119 | add_executable(hello_libc hello_libc.cpp) 120 | target_compile_options( 121 | hello_libc PRIVATE 122 | ${LIBC_RELEASE_OPTIONS} 123 | ) 124 | target_link_options( 125 | hello_libc PRIVATE 126 | ${LIBC_RELEASE_OPTIONS} 127 | ) 128 | 129 | add_executable(memcpy_libc memory_copy_libc.cpp) 130 | target_compile_options( 131 | memcpy_libc PRIVATE 132 | ${LIBC_RELEASE_OPTIONS} 133 | ) 134 | target_link_options( 135 | memcpy_libc PRIVATE 136 | ${LIBC_RELEASE_OPTIONS} 137 | ) 138 | endif() 139 | -------------------------------------------------------------------------------- /examples/cat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace cat::literals; 7 | using namespace cat::integers; 8 | 9 | constexpr idx block_size = 4_uki; 10 | 11 | auto 12 | get_file_size(nix::file_descriptor file_descriptor) -> cat::maybe { 13 | nix::file_status status = nix::sys_fstat(file_descriptor).or_exit(); 14 | if (status.is_regular()) { 15 | return status.file_size; 16 | } 17 | if (status.is_block_device()) { 18 | return status.block_size; 19 | } 20 | return cat::nullopt; 21 | } 22 | 23 | void 24 | output_to_console(nix::io_vector io_vector) { 25 | // TODO: Create a mutable string type to prevent this undefined behavior. 26 | // TODO: Make this buffered output to reduce syscalls. 27 | cat::byte const* p_buffer = io_vector.data(); 28 | ++p_buffer; 29 | auto _ = nix::sys_write( 30 | nix::stdout, __builtin_bit_cast(char const*, p_buffer), io_vector.size()); 31 | } 32 | 33 | void 34 | read_and_print_file(char* p_file_name) { 35 | nix::file_descriptor file_descriptor = 36 | nix::sys_open(p_file_name, nix::open_mode::read_only) 37 | .or_exit("No such file or directory!", 2); 38 | idx const file_size = idx(get_file_size(file_descriptor).value()); 39 | idx bytes_remaining = file_size; 40 | idx blocks = file_size / block_size; 41 | idx current_block; 42 | if (file_size % block_size > 0) { 43 | blocks++; 44 | } 45 | 46 | cat::page_allocator pager; 47 | 48 | cat::span io_vectors = 49 | pager.alloc_multi(blocks).or_exit( 50 | "Failed to allocate memory!", 3); 51 | defer { 52 | pager.free_multi(io_vectors.data(), io_vectors.size()); 53 | }; 54 | 55 | while (bytes_remaining > 0) { 56 | idx current_block_size = cat::min(bytes_remaining, block_size); 57 | 58 | // These pages are freed when iterating through the io vectors later. 59 | cat::span buffer = pager.alloc_multi(block_size) 60 | .or_exit("Failed to allocate memory!", 4); 61 | 62 | io_vectors[current_block] = 63 | nix::io_vector(buffer.data(), current_block_size); 64 | ++current_block; 65 | bytes_remaining -= current_block_size; 66 | } 67 | 68 | auto _ = nix::sys_readv(file_descriptor, io_vectors).or_exit(5); 69 | 70 | for (nix::io_vector const& iov : io_vectors) { 71 | output_to_console(iov); 72 | pager.free(iov.data()); 73 | } 74 | } 75 | 76 | // TODO: This example segfaults when running without sanitizers. This happens 77 | // with and without optimizations. 78 | auto 79 | main(int argc, char* p_argv[]) -> int { 80 | if (argc == 1) { 81 | cat::eprint("At least one file path must be provided!").or_exit(); 82 | cat::exit(1); 83 | } 84 | 85 | for (int i = 1; i < argc; ++i) { 86 | read_and_print_file(p_argv[i]); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | auto 8 | main(int argc, char* p_argv[]) -> int { 9 | cat::socket_unix socket; 10 | socket.path_name = cat::make_str_inplace<108>("\0/tmp/temp.sock"); 11 | 12 | socket.create().verify(); 13 | socket.connect().verify(); 14 | 15 | // Send all command line arguments to the server. 16 | for (int i = 1; i < argc; ++i) { 17 | socket.send_string(p_argv[i]).verify(); 18 | if (i < argc - 1) { 19 | socket.send_string(" ").verify(); 20 | } 21 | } 22 | 23 | socket.close().verify(); 24 | } 25 | -------------------------------------------------------------------------------- /examples/echo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Simplify with `cat::span`. 4 | // TODO: Make this cross-platform. 5 | auto 6 | main(int argc, char* p_argv[]) -> int { 7 | for (cat::iword i = 1; i < argc; ++i) { 8 | cat::iword length = 0; 9 | // TODO: Use a `string_length_scalar()`. 10 | while (p_argv[i.raw][length.raw] != 0) { 11 | ++length; 12 | } 13 | 14 | p_argv[i.raw][length.raw] = ' '; 15 | auto _ = nix::sys_write(nix::stdout, p_argv[i.raw], length + 1); 16 | } 17 | 18 | auto _ = nix::sys_write(nix::stdout, "\n"); 19 | } 20 | -------------------------------------------------------------------------------- /examples/hello.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | main() -> int { 5 | cat::print("Hello, world!\n").or_exit(); 6 | } 7 | -------------------------------------------------------------------------------- /examples/hello_libc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // `printf()` is actually optimized here better by GCC than `write()`, 5 | // because its `strlen` invocation can be constant-folded. 6 | 7 | auto 8 | main() -> int32_t { 9 | printf("Hello, Conscat!\n"); 10 | } 11 | -------------------------------------------------------------------------------- /examples/memory_copy_libc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | auto 6 | main() -> int32_t { 7 | std::array source_2000; 8 | std::array dest_2000; 9 | memcpy(&dest_2000, &source_2000, sizeof(dest_2000)); 10 | // Prevent these from being optimized out. 11 | asm volatile("" ::"m"(source_2000)); 12 | asm volatile("" ::"m"(dest_2000)); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /examples/server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | auto 8 | main() -> int { 9 | cat::socket_unix listening_socket; 10 | // A leading null byte puts this path in the abstract namespace. 11 | listening_socket.path_name = cat::make_str_inplace<108>("\0/tmp/temp.sock"); 12 | listening_socket.create().verify(); 13 | listening_socket.bind().verify(); 14 | listening_socket.listen(20).verify(); 15 | 16 | cat::socket_unix recieving_socket; 17 | cat::str_inplace<12> message_buffer; 18 | 19 | bool exit = false; 20 | while (!exit) { 21 | recieving_socket.accept(listening_socket).verify(); 22 | 23 | while (true) { 24 | auto _ = recieving_socket 25 | .recieve(message_buffer.data(), message_buffer.size()) 26 | .verify(); 27 | 28 | cat::str_view const input = message_buffer.data(); 29 | 30 | // TODO: This comparison is always false. 31 | if (cat::compare_strings(input, "exit")) { 32 | cat::println("Closing the server.").or_exit(); 33 | exit = true; 34 | break; 35 | } 36 | 37 | auto _ = cat::print("Recieved: "); 38 | cat::println(input).or_exit(); 39 | break; 40 | } 41 | } 42 | 43 | recieving_socket.close().verify(); 44 | listening_socket.close().verify(); 45 | nix::sys_unlink(listening_socket.path_name.data()).verify(); 46 | } 47 | -------------------------------------------------------------------------------- /examples/window.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | auto 5 | main() -> int { 6 | cat::page_allocator allocator; 7 | // TODO: Work on this more. 8 | [[maybe_unused]] 9 | x11::connection x_connection = x11::initiate_connection(allocator).or_exit( 10 | "Failed to create an X context!"); 11 | } 12 | -------------------------------------------------------------------------------- /format.cmake: -------------------------------------------------------------------------------- 1 | # This script is executed by `CMakeLists.txt` for the `cat-format` target. 2 | 3 | # `CMAKE_ARGV4` is a space-delimited sequence of files to format. 4 | # Spaces are replaced by semicolons to make this a list. 5 | string(REPLACE " " ";" files ${CMAKE_ARGV4}) 6 | foreach(file IN ITEMS ${files}) 7 | # `CMAKE_ARGV3` is a path to a `clang-format` executable, specified 8 | # By the `CAT_CLANG_FORMAT_PATH` variable. 9 | execute_process( 10 | COMMAND ${CMAKE_ARGV3} -i ${file} 11 | ) 12 | endforeach() 13 | -------------------------------------------------------------------------------- /gdb_pretty_printers/ruff.toml: -------------------------------------------------------------------------------- 1 | line-length = 88 2 | indent-width = 4 3 | 4 | # Python 3.11 5 | target-version = "py311" 6 | 7 | [lint] 8 | # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. 9 | # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or 10 | # McCabe complexity (`C901`) by default. 11 | select = ["E4", "E7", "E9", "F"] 12 | fixable = ["ALL"] 13 | 14 | [format] 15 | quote-style = "single" 16 | indent-style = "space" 17 | -------------------------------------------------------------------------------- /src/libraries/algorithm/cat/algorithm: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace cat { 8 | 9 | template 10 | concept is_random_access = requires(T t) { 11 | t.data(); 12 | t.size(); 13 | }; 14 | 15 | // Copy-assign the elements of one collection into another. 16 | template 17 | auto 18 | copy(input_iterator source_begin, input_iterator source_end, 19 | output_iterator destination_begin) -> output_iterator { 20 | using destination_element = remove_reference; 21 | 22 | while (source_begin != source_end) { 23 | // Copy assignment. 24 | *destination_begin = static_cast(*source_begin); 25 | ++source_begin; 26 | ++destination_begin; 27 | } 28 | return destination_begin; 29 | } 30 | 31 | // Move-assign the elements of one collection into another. 32 | template 33 | auto 34 | move(input_iterator source_begin, input_iterator source_end, 35 | output_iterator destination_begin) -> output_iterator { 36 | using destination_element = remove_reference; 37 | 38 | while (source_begin != source_end) { 39 | // Move assignment. 40 | *destination_begin = 41 | static_cast(move(*source_begin)); 42 | ++source_begin; 43 | ++destination_begin; 44 | } 45 | return destination_begin; 46 | } 47 | 48 | // Optimally relocate the elements of one collection into another. 49 | template 50 | auto 51 | relocate(input_iterator source_begin, input_iterator source_end, 52 | output_iterator destination_begin) -> output_iterator { 53 | using source_element = remove_reference; 54 | using destination_element = decltype(*destination_begin); 55 | 56 | // If the source and destination containers are contiguous, and they hold 57 | // the same element type, and that type is trivially relocatable, copy them 58 | // fast. 59 | if constexpr (is_random_access_iterator 60 | && is_random_access_iterator 61 | && is_same 62 | && is_trivially_relocatable) { 63 | copy_memory(__builtin_addressof(*source_begin), 64 | __builtin_addressof(*destination_begin), 65 | (source_end - source_begin) * ssizeof(*source_begin)); 66 | return destination_begin; 67 | } else { 68 | // Otherwise, if the destination can be moved to, do so. 69 | if constexpr (is_move_assignable) { 70 | return move(source_begin, source_end, destination_begin); 71 | } else { 72 | // Otherwise, copy to the destination. 73 | return copy(source_begin, source_end, destination_begin); 74 | } 75 | } 76 | } 77 | 78 | template 79 | constexpr auto 80 | lexicographical_compare(ItLhs lhs_start, ItLhs lhs_end, ItRhs rhs_start, 81 | ItRhs rhs_end) -> bool { 82 | while (lhs_start != lhs_end) { 83 | if (rhs_start == rhs_end || *rhs_start < *lhs_start) { 84 | return false; 85 | } 86 | if (*lhs_start < *rhs_start) { 87 | return true; 88 | } 89 | 90 | ++lhs_start; 91 | ++rhs_start; 92 | } 93 | 94 | return (rhs_start != rhs_end); 95 | } 96 | 97 | template 98 | constexpr auto 99 | lexicographical_compare_three_way(ItLhs lhs_start, ItLhs lhs_end, 100 | ItRhs rhs_start, 101 | ItRhs rhs_end) // Formatting. 102 | requires( 103 | is_common_comparison_category (*rhs_start))>) 104 | { 105 | bool exhaust_left = (lhs_start == lhs_end); 106 | bool exhaust_right = (rhs_start == rhs_end); 107 | 108 | while (!exhaust_left && !exhaust_right) { 109 | auto comparison = (*lhs_start) <=> (*rhs_start); 110 | 111 | if (comparison != 0) { 112 | return comparison; 113 | } 114 | 115 | exhaust_left = (++lhs_start == lhs_end); 116 | exhaust_right = (++rhs_start == rhs_end); 117 | } 118 | 119 | using category_type = decltype((*lhs_start) <=> (*rhs_start)); 120 | return category_type(!exhaust_left ? std::strong_ordering::greater 121 | : !exhaust_right ? std::strong_ordering::less 122 | : std::strong_ordering::equal); 123 | } 124 | 125 | } // namespace cat 126 | -------------------------------------------------------------------------------- /src/libraries/allocator/cat/null_allocator: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace cat { 8 | 9 | class null_allocator : public allocator_interface { 10 | friend allocator_interface; 11 | 12 | private: 13 | template 14 | struct null_memory_handle : detail::base_memory_handle { 15 | // TODO: Simplify with CRTP or deducing-this. 16 | auto 17 | get() -> decltype(auto) { 18 | return *this; 19 | } 20 | 21 | auto 22 | get() const -> decltype(auto) { 23 | return *this; 24 | } 25 | }; 26 | 27 | public: 28 | constexpr null_allocator() = default; 29 | constexpr null_allocator(null_allocator const&) = default; 30 | constexpr null_allocator(null_allocator&&) = default; 31 | 32 | // This allocation always fails. 33 | auto 34 | allocate(iword) -> maybe_ptr { 35 | return nullptr; 36 | } 37 | 38 | // Deallocation is no-op. 39 | void 40 | deallocate(void const*, idx) { 41 | } 42 | 43 | // Produce a handle to allocated memory. 44 | template 45 | auto 46 | make_handle(T* p_handle_storage) -> null_memory_handle { 47 | return null_memory_handle{{}, p_handle_storage}; 48 | } 49 | 50 | // Access a page(s) of virtual memory. 51 | template 52 | auto 53 | access(null_memory_handle& memory) -> T* { 54 | return memory.p_storage; 55 | } 56 | 57 | template 58 | auto 59 | access(null_memory_handle const&) const -> T const* { 60 | return nullptr; 61 | } 62 | 63 | private: 64 | static constexpr bool has_pointer_stability = true; 65 | }; 66 | 67 | [[nodiscard]] 68 | constexpr auto 69 | make_null_allocator() -> null_allocator { 70 | return null_allocator(); 71 | } 72 | 73 | } // namespace cat 74 | -------------------------------------------------------------------------------- /src/libraries/allocator/cat/page_allocator: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace cat { 10 | 11 | // TODO: Support forcibly allocating huge pages. 12 | // Allocate pages from the kernel in userspace. 13 | class page_allocator : public allocator_interface { 14 | friend allocator_interface; 15 | 16 | private: 17 | template 18 | struct page_memory_handle : detail::base_memory_handle { 19 | T* p_storage; 20 | 21 | // TODO: Simplify with CRTP or deducing-this. 22 | auto 23 | get() -> decltype(auto) { 24 | return *this; 25 | } 26 | 27 | auto 28 | get() const -> decltype(auto) { 29 | return *this; 30 | } 31 | }; 32 | 33 | public: 34 | constexpr page_allocator() = default; 35 | constexpr page_allocator(page_allocator const&) = default; 36 | constexpr page_allocator(page_allocator&&) = default; 37 | 38 | auto 39 | allocation_bytes(uword alignment, idx allocation_bytes) 40 | -> maybe_non_zero { 41 | // Pages cannot be aligned by greater than 4 kibibytes. 42 | assert(alignment <= 4_uki); 43 | // Round `allocation_bytes` up to the nearest 4 kibibytes. 44 | return ((allocation_bytes / 4_uki) + 1u) * 4_uki; 45 | } 46 | 47 | // Allocate memory in multiples of a page-size. A page is `4_uki` large 48 | // on x86-64. If fewer than 4096 bytes are allocated, that amount will 49 | // be rounded up to 4096. 50 | auto 51 | allocate(idx allocation_bytes) -> maybe_ptr { 52 | scaredy result = 53 | nix::sys_mmap(0u, allocation_bytes, 54 | // TODO: Fix bit flags operators. 55 | static_cast( 56 | to_underlying(nix::memory_protection_flags::read) 57 | | to_underlying(nix::memory_protection_flags::write)), 58 | static_cast( 59 | to_underlying(nix::memory_flags::privately) 60 | | to_underlying(nix::memory_flags::populate) 61 | | to_underlying(nix::memory_flags::anonymous)), 62 | // Anonymous pages (non-files) must have `-1`. 63 | nix::file_descriptor(-1), 64 | // Anonymous pages (non-files) must have `0`. 65 | 0u); 66 | if (result.has_value()) { 67 | return result.value(); 68 | } 69 | return nullptr; 70 | } 71 | 72 | // Allocate a page(s) of virtual memory that is guaranteed to align to 73 | // any power of 2, but less than `4_uki`. 74 | auto 75 | aligned_allocate(uword alignment, idx allocation_bytes) -> maybe_ptr { 76 | // Pages cannot be aligned by greater than 4 kibibytes. 77 | assert(alignment <= 4_uki); 78 | // A normal page allocation already has strong alignment guarantees. 79 | return this->allocate(allocation_bytes); 80 | } 81 | 82 | // Unmap a pointer handle to page(s) of virtual memory. 83 | void 84 | deallocate(void const* p_storage, idx allocation_bytes) { 85 | // There are some cases where `munmap` might fail even with private 86 | // anonymous pages. These currently cannot be handled, because `.free()` 87 | // does not propagate errors. 88 | auto _ = nix::sys_munmap(p_storage, allocation_bytes); 89 | } 90 | 91 | // Produce a handle to allocated memory. 92 | template 93 | auto 94 | make_handle(T* p_handle_storage) -> page_memory_handle { 95 | return page_memory_handle{{}, p_handle_storage}; 96 | } 97 | 98 | // Access a page(s) of virtual memory. 99 | template 100 | auto 101 | access(page_memory_handle& memory) -> T* { 102 | return memory.p_storage; 103 | } 104 | 105 | template 106 | auto 107 | access(page_memory_handle const& memory) const -> T const* { 108 | return memory.p_storage; 109 | } 110 | 111 | public: 112 | static constexpr bool has_pointer_stability = true; 113 | }; 114 | 115 | [[nodiscard]] 116 | constexpr auto 117 | make_page_allocator() -> page_allocator { 118 | return page_allocator(); 119 | } 120 | 121 | } // namespace cat 122 | -------------------------------------------------------------------------------- /src/libraries/allocator/cat/pool_allocator: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace cat { 8 | 9 | template 10 | class pool_allocator 11 | : public allocator_interface> { 12 | friend allocator_interface; 13 | 14 | // Friend factory functions. 15 | template 16 | friend constexpr auto make_pool_allocator(uintptr, idx) 17 | -> pool_allocator; 18 | 19 | template 20 | friend constexpr auto 21 | make_pool_allocator(span&) -> pool_allocator; 22 | 23 | private: 24 | // Initialize a `pool_allocator`. This should only be called from 25 | // `cat::make_pool_allocator`. 26 | constexpr pool_allocator(uintptr p_address, idx arena_bytes) 27 | : m_nodes(span( 28 | // Pun the opaque `p_address` to the internal `node_union` type. 29 | __builtin_bit_cast(node_union*, p_address.raw), 30 | arena_bytes / sizeof(node_union))) { 31 | // Initialize the free list. 32 | this->reset(); 33 | } 34 | 35 | template 36 | struct pool_memory_handle : detail::base_memory_handle { 37 | T* p_storage; 38 | 39 | // TODO: Simplify with CRTP or deducing-this. 40 | auto 41 | get() -> decltype(auto) { 42 | return *this; 43 | } 44 | 45 | auto 46 | get() const -> decltype(auto) { 47 | return *this; 48 | } 49 | }; 50 | 51 | public: 52 | // `pool_allocator` is move-only. 53 | constexpr pool_allocator(pool_allocator const&) = delete( 54 | "cat::pool_allocator` should be constructed using " 55 | "`cat::make_pool_allocator`."); 56 | constexpr pool_allocator(pool_allocator&&) = default; 57 | 58 | // Reset the bumped pointer to the beginning of this arena. 59 | constexpr void 60 | reset() { 61 | for (idx i; i < m_nodes.size() - 1u; ++i) { 62 | m_nodes[i].p_next = &(m_nodes[i + 1u]); 63 | } 64 | // Mark the final node. 65 | m_nodes.back().p_next = nullptr; 66 | m_p_head = &m_nodes.front(); 67 | } 68 | 69 | private: 70 | auto 71 | allocation_bytes(uword, idx) -> maybe_non_zero { 72 | return max_node_bytes; 73 | } 74 | 75 | auto 76 | allocate(idx) -> maybe_ptr { 77 | // If there is a next node in the free list, make that the head and 78 | // allocate the current head to the user. Otherwise, do not allocate 79 | // anything. 80 | if (m_p_head == nullptr) { 81 | return nullopt; 82 | } 83 | 84 | node_union* p_alloc = m_p_head; 85 | m_p_head = m_p_head->p_next; 86 | 87 | return static_cast(p_alloc); 88 | } 89 | 90 | void 91 | deallocate(void const* p_allocation, idx) { 92 | // Swap the head for this newly freed allocation. 93 | node_union* p_new_head = __builtin_bit_cast(node_union*, p_allocation); 94 | p_new_head->p_next = m_p_head; 95 | m_p_head = p_new_head; 96 | } 97 | 98 | // Produce a handle to allocated memory. 99 | template 100 | auto 101 | make_handle(T* p_handle_storage) -> pool_memory_handle { 102 | return pool_memory_handle{{}, p_handle_storage}; 103 | } 104 | 105 | // Access some memory. 106 | template 107 | auto 108 | access(pool_memory_handle& memory) -> T* { 109 | return memory.p_storage; 110 | } 111 | 112 | template 113 | auto 114 | access(pool_memory_handle const& memory) const -> T const* { 115 | return memory.p_storage; 116 | } 117 | 118 | public: 119 | static constexpr bool has_pointer_stability = true; 120 | 121 | // Do not allocate larger than this size of one node. 122 | static constexpr idx max_allocation_bytes = max_node_bytes; 123 | 124 | union node_union { 125 | node_union* p_next = nullptr; 126 | byte storage[max_node_bytes.raw]; 127 | }; 128 | 129 | private: 130 | span m_nodes; 131 | node_union* m_p_head; 132 | }; 133 | 134 | template 135 | [[nodiscard]] 136 | constexpr auto 137 | make_pool_allocator(uintptr p_address, idx arena_bytes) 138 | -> pool_allocator { 139 | return pool_allocator(p_address, arena_bytes); 140 | } 141 | 142 | template 143 | [[nodiscard]] 144 | constexpr auto 145 | make_pool_allocator(span& span) -> pool_allocator { 146 | return pool_allocator(span.data(), span.size()); 147 | } 148 | 149 | template 150 | [[nodiscard, 151 | deprecated( 152 | "Creating a `cat::pool_allocator` with a pr-value may " 153 | "unintentionally leak memory. Copy an l-value `cat::span` instead.")]] 154 | constexpr auto 155 | make_pool_allocator(span&& span) -> pool_allocator { 156 | return make_pool_allocator(span); 157 | } 158 | 159 | } // namespace cat 160 | -------------------------------------------------------------------------------- /src/libraries/bit/implementations/align_down.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | // Returns a value rounded down from `p_value` to the nearest `alignment` 8 | // boundary. 9 | template 10 | [[nodiscard]] 11 | constexpr auto 12 | cat::align_down(U* p_value, uword alignment) -> U* { 13 | // TODO: Add unary `-` operator to remove `.raw`. 14 | return uintptr{p_value} & (-alignment.raw); 15 | } 16 | 17 | // Returns a value rounded down from `p_value` to the nearest `alignment` 18 | // boundary. This only works for two's complement arithmetic. 19 | template 20 | [[nodiscard]] 21 | constexpr auto 22 | cat::align_down(intptr p_value, uword alignment) -> intptr { 23 | // TODO: Add unary `-` operator to remove `.raw`. 24 | return p_value & (-alignment.raw); 25 | } 26 | 27 | // Returns a value rounded down from `p_value` to the nearest `alignment` 28 | // boundary. This only works for two's complement arithmetic. 29 | template 30 | [[nodiscard]] 31 | constexpr auto 32 | cat::align_down(uintptr p_value, uword alignment) -> uintptr { 33 | // TODO: Add unary `-` operator to remove `.raw`. 34 | return p_value & (-alignment.raw); 35 | } 36 | -------------------------------------------------------------------------------- /src/libraries/bit/implementations/align_up.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | // Returns a value rounded up from `p_value` to the nearest `alignment` 8 | // boundary. 9 | template 10 | [[nodiscard]] 11 | constexpr auto 12 | cat::align_up(U* p_value, uword alignment) -> U* { 13 | return (uintptr{p_value} + (alignment - 1u)) & (~(alignment - 1u)); 14 | } 15 | 16 | // Returns a value rounded up from `p_value` to the nearest `alignment` 17 | // boundary. 18 | template 19 | [[nodiscard]] 20 | constexpr auto 21 | cat::align_up(uintptr p_value, uword alignment) -> uintptr { 22 | return (p_value + (alignment - 1u)) & (~(alignment - 1u)); 23 | } 24 | -------------------------------------------------------------------------------- /src/libraries/bit/implementations/is_aligned.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | // TODO: Guarantee that the inputs have only a single bit set. 8 | 9 | // Returns `true` if `p_value` is aligned to the `alignment` boundary. 10 | template 11 | [[nodiscard]] 12 | constexpr auto 13 | cat::is_aligned(U* p_value, uword alignment) -> bool { 14 | return (uintptr{p_value} & (alignment - 1u)) == 0u; 15 | } 16 | 17 | // Returns `true` if `p_value` is aligned to the `alignment` boundary. 18 | template 19 | [[nodiscard]] 20 | constexpr auto 21 | cat::is_aligned(uintptr p_value, uword alignment) -> bool { 22 | return (p_value & (alignment - 1u)) == 0u; 23 | } 24 | 25 | // Returns `true` if `value` is aligned to the `alignment` boundary. 26 | template 27 | [[nodiscard]] 28 | constexpr auto 29 | cat::is_aligned(cat::index value, cat::uword alignment) -> bool { 30 | return (uword(value) & (alignment - 1u)) == 0u; 31 | } 32 | -------------------------------------------------------------------------------- /src/libraries/cast/cat/cast: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace cat { 9 | 10 | template 11 | constexpr auto 12 | numeric_cast(from_type from) -> maybe { 13 | to_type const to = static_cast(from); 14 | constexpr bool has_same_signedness = 15 | is_signed != is_signed; 16 | 17 | if constexpr (sizeof(to_type) < (sizeof(from)) && has_same_signedness) { 18 | if (static_cast(to) != to) { 19 | // Overflow occurred. 20 | return nullopt; 21 | } 22 | 23 | // TODO: Make an `has_same_signedness` trait. 24 | if constexpr (!has_same_signedness) { 25 | if ((to < to_type()) != (from < from_type())) { 26 | // Overflow occurred. 27 | return nullopt; 28 | } 29 | } 30 | } 31 | 32 | return to; 33 | } 34 | 35 | template 36 | requires(sizeof(from_type) <= 8 && !is_lvalue_reference) 37 | [[gnu::always_inline, gnu::nodebug]] 38 | constexpr auto 39 | bit_int_cast(from_type from) -> decltype(auto) { 40 | // TODO: Use a template that makes some integer from a size. 41 | using integer_type = 42 | conditional>>; 45 | using return_type = copy_cvref_from; 46 | 47 | return __builtin_bit_cast(return_type, from); 48 | } 49 | 50 | } // namespace cat 51 | -------------------------------------------------------------------------------- /src/libraries/debug/cat/debug: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | namespace std { 6 | 7 | class source_location { 8 | private: 9 | // Clang requires these variables be declared at the top, not the bottom. 10 | 11 | // GCC requires these names be exactly what they are. 12 | struct __impl { 13 | char const* _M_file_name; 14 | char const* _M_function_name; 15 | unsigned long _M_line; 16 | unsigned long _M_column; 17 | }; 18 | 19 | __impl const* m_p_location = nullptr; 20 | 21 | public: 22 | static constexpr auto 23 | current(decltype(__builtin_source_location()) p_call_site_location = 24 | __builtin_source_location()) -> source_location { 25 | source_location current_location; 26 | current_location.m_p_location = 27 | static_cast<__impl const*>(p_call_site_location); 28 | return current_location; 29 | } 30 | 31 | [[nodiscard]] 32 | constexpr auto 33 | line() const -> unsigned long { 34 | return (this->m_p_location != nullptr) ? this->m_p_location->_M_line : 0u; 35 | } 36 | 37 | [[nodiscard]] 38 | constexpr auto 39 | column() const -> unsigned long { 40 | return (this->m_p_location != nullptr) ? this->m_p_location->_M_column 41 | : 0u; 42 | } 43 | 44 | [[nodiscard]] 45 | constexpr auto 46 | file_name() const -> char const* { 47 | return (this->m_p_location != nullptr) ? this->m_p_location->_M_file_name 48 | : ""; 49 | } 50 | 51 | [[nodiscard]] 52 | constexpr auto 53 | function_name() const -> char const* { 54 | return (this->m_p_location != nullptr) 55 | ? this->m_p_location->_M_function_name 56 | : ""; 57 | } 58 | }; 59 | 60 | } // namespace std 61 | 62 | namespace cat { 63 | 64 | template 65 | requires(sizeof(char_type) == 1) 66 | class basic_str_span; 67 | 68 | using str_view = basic_str_span; 69 | 70 | using std::source_location; 71 | 72 | // TODO: Mark these functions `[[gnu::artificial]]` or `[[gnu::nodebug]]`. 73 | 74 | void 75 | breakpoint(); 76 | 77 | // If used at runtime, this inserts a `nop` instruction, which may be useful for 78 | // sequencing loops or preventing dead code elimination. In a `constexpr` 79 | // context, this is entirely ignored. 80 | [[gnu::always_inline]] 81 | constexpr void 82 | nop() { 83 | if !consteval { 84 | asm volatile("nop"); 85 | } 86 | } 87 | 88 | // This is exposed so that `unit_tests.hpp` can use it. Its implementation is in 89 | // `assert_handler.cpp`. 90 | namespace detail { 91 | void 92 | print_assert_location( 93 | source_location const& callsite = source_location::current()); 94 | 95 | using assert_handler = void (*)(source_location const&); 96 | } // namespace detail 97 | 98 | void 99 | default_assert_handler(source_location const& callsite); 100 | 101 | // This variable can be mutated to change the default assert handler globally. 102 | inline detail::assert_handler assert_handler = default_assert_handler; 103 | 104 | // TODO: Allow `assert()` and `verify()` in a `constexpr` context, and make them 105 | // throw if their invariant holds false. 106 | 107 | // TODO: Fancier `ASSERT()` macro with expression decomposition. 108 | // TODO: `assert_eq()`, `assert_gt()`, etc. 109 | 110 | void 111 | verify(bool invariant_expression, 112 | void (*p_assert_handler)(source_location const&) = assert_handler, 113 | source_location const& callsite = source_location::current()); 114 | 115 | void 116 | verify(bool invariant_expression, str_view error_string, 117 | void (*p_assert_handler)(source_location const&) = assert_handler, 118 | source_location const& callsite = source_location::current()); 119 | 120 | void 121 | assert(bool invariant_expression, 122 | void (*p_assert_handler)(source_location const&) = assert_handler, 123 | source_location const& callsite = source_location::current()); 124 | 125 | void 126 | assert(bool invariant_expression, str_view error_string, 127 | void (*p_assert_handler)(source_location const&) = assert_handler, 128 | source_location const& callsite = source_location::current()); 129 | 130 | } // namespace cat 131 | -------------------------------------------------------------------------------- /src/libraries/debug/implementations/assert.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Because `assert()`'s arguments are not passed into `verify()` when `NDEBUG` 5 | // is defined, the compiler should not warn when they are unused. 6 | #pragma GCC diagnostic push 7 | #pragma GCC diagnostic ignored "-Wunused-parameter" 8 | 9 | // Check that an expression holds true when `NDEBUG` is not defined. If it holds 10 | // false, invoke `p_assert_handler`. 11 | void 12 | cat::assert(bool invariant_expression, 13 | void (*p_assert_handler)(source_location const&), 14 | source_location const& callsite) { 15 | #ifndef NDEBUG 16 | verify(invariant_expression, p_assert_handler, callsite); 17 | #endif 18 | } 19 | 20 | // Check that an expression holds true when `NDEBUG` is not defined. If it 21 | // holds false, print `error_string` and invoke `p_assert_handler`. 22 | void 23 | cat::assert(bool invariant_expression, str_view const error_string, 24 | void (*p_assert_handler)(source_location const&), 25 | source_location const& callsite) { 26 | #ifndef NDEBUG 27 | verify(invariant_expression, error_string, p_assert_handler, callsite); 28 | #endif 29 | } 30 | 31 | #pragma GCC diagnostic pop 32 | -------------------------------------------------------------------------------- /src/libraries/debug/implementations/breakpoint.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void 5 | cat::breakpoint() { 6 | // TODO: Arm and Windows support. 7 | #ifdef __x86_64__ 8 | // For x86-64, insert a breakpoint interrupt instruction. 9 | asm volatile("int3"); 10 | #else 11 | // Without hardware support, raise SIGILL, which breaks in a debugger. 12 | nix::raise_here(nix::signal::illegal_instruction); 13 | #endif 14 | } 15 | -------------------------------------------------------------------------------- /src/libraries/debug/implementations/default_assert_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void 6 | cat::detail::print_assert_location(source_location const& callsite) { 7 | page_allocator allocator; 8 | // TODO: This will leak. An `inline_allocator` should be used. 9 | auto _ = eprint( 10 | fmt(allocator, "assert failed on line {}, in:\n ", callsite.line()) 11 | .or_exit()); 12 | // TODO: Truncate to only the last one or two directories. 13 | auto _ = eprint(callsite.file_name()); 14 | auto _ = eprint("\ncalled from:\n "); 15 | // Any failures to print text will cascade to the last `eprint()` call, so 16 | // only handle failure there. 17 | eprintln(callsite.function_name()).or_exit(); 18 | } 19 | 20 | void 21 | cat::default_assert_handler(source_location const& callsite) { 22 | detail::print_assert_location(callsite); 23 | 24 | // TODO: Colorize this input prompt. 25 | print("Press: 1 (Continue), 2 (Debug), 3 (Abort)\n").or_exit(); 26 | 27 | while (true) { 28 | unsigned char input = nix::read_char().or_exit(); 29 | if (input >= '1' && input <= '3') { 30 | // ASCII trick that converts an inputted char to a digit. 31 | uint1 digit = input - 49_u1; 32 | 33 | // The value of `digit` is one less than what was inputted. 34 | switch (digit.raw) { 35 | case 0: 36 | // Ignore the assert failure. 37 | return; 38 | case 1: 39 | // Break in a debugger. 40 | breakpoint(); 41 | return; 42 | case 2: 43 | { 44 | // Abort the program. 45 | eprint("Program aborted!\n").or_exit(); 46 | exit(1); 47 | } 48 | default: 49 | __builtin_unreachable(); 50 | } 51 | 52 | return; 53 | } 54 | eprint("Invalid input!\n").or_exit(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/libraries/debug/implementations/verify.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Check that an expression holds true in all builds. If it holds false, invoke 5 | // `p_assert_handler`. 6 | void 7 | cat::verify(bool invariant_expression, 8 | void (*p_assert_handler)(source_location const&), 9 | source_location const& callsite) { 10 | if (invariant_expression) [[likely]] { 11 | return; 12 | } 13 | 14 | p_assert_handler(callsite); 15 | __builtin_unreachable(); 16 | } 17 | 18 | // Check that an expression holds true in all builds. If it holds false, print 19 | // `error_string` and invoke `p_assert_handler`. 20 | void 21 | cat::verify(bool invariant_expression, str_view const error_string, 22 | void (*p_assert_handler)(source_location const&), 23 | source_location const& callsite) { 24 | if (invariant_expression) [[likely]] { 25 | return; 26 | } 27 | auto _ = eprintln(); 28 | eprintln(error_string).or_exit(); 29 | 30 | p_assert_handler(callsite); 31 | __builtin_unreachable(); 32 | } 33 | -------------------------------------------------------------------------------- /src/libraries/enum/cat/enum: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace cat { 8 | 9 | template 10 | consteval auto 11 | type_name() -> cat::str_view { 12 | char const* p_string = __PRETTY_FUNCTION__; 13 | cat::idx size = sizeof(__PRETTY_FUNCTION__) - 2u; 14 | 15 | // Scan backwards from the function signature to find whitespace that 16 | // delimits the beginning of this type name. 17 | for (cat::idx i = size; i >= 0u; --i) { 18 | if (p_string[i] == '=') { 19 | // There is a whitespace after the `=` in this signature, so we add 1 20 | // to the string pointer to compensate. 21 | p_string = p_string + i + 2u; 22 | size -= i + 2u; 23 | return {p_string, size}; 24 | } 25 | } 26 | __builtin_unreachable(); 27 | } 28 | 29 | consteval auto 30 | nameof([[maybe_unused]] auto&& value) -> cat::str_view { 31 | return type_name>(); 32 | } 33 | 34 | template 35 | requires(cat::is_enum) 36 | consteval auto 37 | enum_name_from_nttp() -> cat::str_view { 38 | char const* p_string = __PRETTY_FUNCTION__ + 34u; 39 | cat::idx size = sizeof(__PRETTY_FUNCTION__) - 38u; 40 | // Scan backwards from the function signature to find `enum_value`'s word 41 | // boundary, and trim everything else from the string. This boundary is 42 | // either a scope-resolution operator or whitespace for scoped and unscoped 43 | // enums respectively. 44 | for (cat::idx i = size; i >= 0u; --i) { 45 | if (p_string[i] == ':' || p_string[i] == ' ') { 46 | p_string = p_string + i + 1u; 47 | size -= i - 1u; 48 | return {p_string, size}; 49 | } 50 | } 51 | __builtin_unreachable(); 52 | } 53 | 54 | } // namespace cat 55 | -------------------------------------------------------------------------------- /src/libraries/file/cat/file: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | // TODO: Get this basically working. 8 | 9 | namespace cat { 10 | 11 | enum class file_open_mode : unsigned char { 12 | read, 13 | write, 14 | append 15 | }; 16 | 17 | enum class file_seek_mode : unsigned char { 18 | set, 19 | current, 20 | end 21 | }; 22 | 23 | class raw_type_file { 24 | public: 25 | constexpr raw_type_file() = default; 26 | constexpr raw_type_file(raw_type_file const&) = default; 27 | constexpr raw_type_file(raw_type_file&&) = default; 28 | 29 | auto 30 | open(str_view const path, file_open_mode mode = file_open_mode::read) 31 | -> scaredy { 32 | nix::open_mode open_mode = nix::open_mode::read_write; 33 | nix::open_flags open_flags = nix::open_flags::path; 34 | 35 | // Try to open a file from a path. 36 | auto m_file_descriptor = 37 | prop(nix::sys_open(path.data(), open_mode, open_flags)); 38 | 39 | // TODO: Is this failable? 40 | // Get the status of that opened file. 41 | scaredy fstat_result = nix::sys_fstat(m_file_descriptor); 42 | nix::file_status status = fstat_result.value(); 43 | m_file_size = status.file_size; 44 | m_block_size = status.block_size; 45 | 46 | return {}; 47 | } 48 | 49 | void 50 | close() { 51 | } 52 | 53 | [[nodiscard]] 54 | constexpr auto 55 | is_closed() const -> bool { 56 | // When closed, `file_descriptor` is set to -1. 57 | return m_file_descriptor.value != -1; 58 | } 59 | 60 | constexpr auto 61 | is_readable() -> bool { 62 | // If `open_mode` has either read bitflags set: 63 | return (to_underlying(m_open_mode) 64 | & (to_underlying(nix::open_mode::read_only) 65 | | to_underlying(nix::open_mode::read_write))) 66 | != 0; 67 | } 68 | 69 | constexpr auto 70 | is_writeable() -> bool { 71 | // If `open_mode` has either write bitflags set: 72 | return (to_underlying(m_open_mode) 73 | & (to_underlying(nix::open_mode::write_only) 74 | | to_underlying(nix::open_mode::read_write))) 75 | != 0; 76 | } 77 | 78 | private: 79 | [[maybe_unused]] 80 | nix::file_descriptor m_file_descriptor{-1}; 81 | nix::open_mode m_open_mode; 82 | idx m_file_size; 83 | idx m_block_size; 84 | }; 85 | 86 | } // namespace cat 87 | -------------------------------------------------------------------------------- /src/libraries/format/cat/detail/itoa_jeaiii.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // This header forward-declares Jeaiii's int-to-string conversion functions for 4 | // use by my string formatting. The implementations, and their copyright notice, 5 | // are located in `../../implementations/itoa_jeaiii.cpp`. 6 | 7 | // NOLINTBEGIN 8 | 9 | namespace cat::detail { 10 | 11 | struct pair_jeaiii { 12 | char t, o; 13 | }; 14 | 15 | #ifdef __clang__ 16 | #pragma clang diagnostic push 17 | #pragma clang diagnostic ignored "-Wmissing-braces" 18 | #endif 19 | 20 | #define P(T) \ 21 | T, '0', T, '1', T, '2', T, '3', T, '4', T, '5', T, '6', T, '7', T, '8', T, \ 22 | '9' 23 | static cat::detail::pair_jeaiii const s_pairs[] = { 24 | P('0'), P('1'), P('2'), P('3'), P('4'), 25 | P('5'), P('6'), P('7'), P('8'), P('9')}; 26 | 27 | #define W(N, I) *(cat::detail::pair_jeaiii*)&b[N] = s_pairs[I] 28 | #define A(N) \ 29 | t = (uint8::raw_type(1) << (32 + N / 5 * N * 53 / 16)) \ 30 | / uint4::raw_type(1e##N) \ 31 | + 1 + N / 6 - N / 8, \ 32 | t *= u, t >>= N / 5 * N * 53 / 16, t += N / 6 * 4, W(0, t >> 32) 33 | #define S(N) b[N] = char(uint8::raw_type(10) * uint4::raw_type(t) >> 32) + '0' 34 | #define D(N) t = uint8::raw_type(100) * uint4::raw_type(t), W(N, t >> 32) 35 | 36 | #define L0 b[0] = char(u) + '0' 37 | #define L1 W(0, u) 38 | #define L2 A(1), S(2) 39 | #define L3 A(2), D(2) 40 | #define L4 A(3), D(2), S(4) 41 | #define L5 A(4), D(2), D(4) 42 | #define L6 A(5), D(2), D(4), S(6) 43 | #define L7 A(6), D(2), D(4), D(6) 44 | #define L8 A(7), D(2), D(4), D(6), S(8) 45 | #define L9 A(8), D(2), D(4), D(6), D(8) 46 | 47 | #define LN(N) (L##N, b += N + 1) 48 | #define LZ LN 49 | // if you want to '\0' terminate 50 | // #define LZ(N) &(L##N, b[N + 1] = '\0') 51 | 52 | #define LG(F) \ 53 | (u < 100 ? u < 10 ? F(0) : F(1) \ 54 | : u < 1'000'000 ? u < 10'000 ? u < 1'000 ? F(2) : F(3) \ 55 | : u < 100'000 ? F(4) \ 56 | : F(5) \ 57 | : u < 100'000'000 ? u < 10'000'000 ? F(6) : F(7) \ 58 | : u < 1'000'000'000 ? F(8) \ 59 | : F(9)) 60 | 61 | auto 62 | u32toa_jeaiii(uint4::raw_type i, char* p_b) -> char*; 63 | auto 64 | i32toa_jeaiii(int4::raw_type i, char* p_b) -> char*; 65 | auto 66 | u64toa_jeaiii(uint8::raw_type i, char* p_b) -> char*; 67 | auto 68 | i64toa_jeaiii(int8::raw_type i, char* p_b) -> char*; 69 | 70 | #ifdef __clang__ 71 | #pragma clang diagnostic pop 72 | #endif 73 | 74 | } // namespace cat::detail 75 | 76 | // NOLINTEND 77 | -------------------------------------------------------------------------------- /src/libraries/functional/cat/functional: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace cat { 9 | namespace detail { 10 | template 11 | struct is_reference_wrapper_trait : false_trait {}; 12 | 13 | // This is not needed until a reference wrapper is implemented. 14 | // template 15 | // struct is_reference_wrapper_trait> : true_type {}; 16 | 17 | template 18 | inline constexpr bool is_reference_wrapper = 19 | is_reference_wrapper_trait::value; 20 | 21 | template 22 | constexpr auto 23 | invoke_member_function(Pointed C::* f, T1&& t1, Args&&... args) 24 | -> decltype(auto) { 25 | if constexpr (is_function) { 26 | if constexpr (is_base_of>) { 27 | return (fwd(t1).*f)(fwd(args)...); 28 | } else if constexpr (is_reference_wrapper>) { 29 | return (t1.get().*f)(fwd(args)...); 30 | } else { 31 | return ((*fwd(t1)).*f)(fwd(args)...); 32 | } 33 | } else { 34 | static_assert(is_object && sizeof...(args) == 0); 35 | if constexpr (is_base_of>) { 36 | return fwd(t1).*f; 37 | } else if constexpr (is_reference_wrapper>) { 38 | return t1.get().*f; 39 | } else { 40 | return (*fwd(t1)).*f; 41 | } 42 | } 43 | } 44 | 45 | template 46 | struct invoke_impl_trait { 47 | template 48 | static auto 49 | call(invocable_type&& f, Args&&... args) -> decltype(fwd(f)(fwd(args)...)); 50 | }; 51 | 52 | template 53 | struct invoke_impl_trait { 54 | template > 55 | requires(is_base_of) 56 | static auto 57 | get(T&& t) -> T&&; 58 | 59 | template > 60 | requires(is_reference_wrapper) 61 | static auto 62 | get(T&& t) -> decltype(t.get()); 63 | 64 | template > 65 | requires(!is_base_of && !is_reference_wrapper) 66 | static auto 67 | get(T&& t) -> decltype(*fwd(t)); 68 | 69 | template 70 | requires(is_function) 71 | static auto 72 | call(MT1 B::* pmf, T&& t, Args&&... args) 73 | -> decltype((invoke_impl_trait::get(fwd(t)).*pmf)(fwd(args)...)); 74 | 75 | template 76 | static auto 77 | call(MT B::* pmd, T&& t) -> decltype(invoke_impl_trait::get(fwd(t)).*pmd); 78 | }; 79 | 80 | template ::type> 82 | auto 83 | invoke_detail(invocable_type&& f, Args&&... args) 84 | -> decltype(invoke_impl_trait::call(fwd(f), fwd(args)...)); 85 | 86 | template 87 | struct invoke_result_trait {}; 88 | 89 | template 90 | struct invoke_result_trait(), declval()...))), 92 | invocable_type, Args...> { 93 | using type = decltype(detail::invoke_detail(declval(), 94 | declval()...)); 95 | }; 96 | } // namespace detail 97 | 98 | template 99 | struct invoke_result 100 | : detail::invoke_result_trait {}; 101 | 102 | template 103 | // requires is_invocable 104 | constexpr auto 105 | invoke(invocable_type&& callable, Args&&... arguments) 106 | -> invoke_result 107 | // noexcept(is_nothrow_invocable) 108 | { 109 | if constexpr (is_member_pointer>) { 110 | // Member functions must be special-cased. 111 | return detail::invoke_member_function(callable, fwd(arguments)...); 112 | } else { 113 | // Invoke the callable with a call operator. 114 | return fwd(callable)(fwd(arguments)...); 115 | } 116 | } 117 | 118 | template 119 | // requires is_invocable_r 120 | constexpr auto 121 | invoke_r(invocable_type&& f, Args&&... args) -> return_type 122 | // noexcept(is_nothrow_invocable_r) 123 | { 124 | if constexpr (is_void) { 125 | invoke(fwd(f), fwd(args)...); 126 | } else { 127 | return invoke(fwd(f), fwd(args)...); 128 | } 129 | } 130 | 131 | } // namespace cat 132 | -------------------------------------------------------------------------------- /src/libraries/initializer_list/cat/initializer_list: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace std { 8 | 9 | template 10 | class initializer_list { 11 | public: 12 | using value_type = T; 13 | using reference = T&; 14 | using const_reference = T&; 15 | using size_type = __SIZE_TYPE__; 16 | 17 | constexpr initializer_list() = default; 18 | 19 | constexpr initializer_list(T const* p_data, __SIZE_TYPE__ in_size) 20 | : m_p_data(p_data), m_size(in_size) { 21 | } 22 | 23 | // Clang requires this be `constexpr` rather than `consteval`. 24 | [[nodiscard]] 25 | constexpr auto 26 | size() const -> __SIZE_TYPE__ { 27 | return m_size; 28 | } 29 | 30 | [[nodiscard]] 31 | constexpr auto 32 | begin() const -> T* { 33 | return m_p_data; 34 | } 35 | 36 | [[nodiscard]] 37 | constexpr auto 38 | end() const -> T* { 39 | return m_p_data + m_size; 40 | } 41 | 42 | private: 43 | T const* m_p_data = nullptr; 44 | __SIZE_TYPE__ m_size = 0u; 45 | }; 46 | 47 | template 48 | constexpr auto 49 | begin(initializer_list initializers) -> T const* { 50 | return initializers.begin(); 51 | } 52 | 53 | template 54 | constexpr auto 55 | end(initializer_list initializers) -> T const* { 56 | return initializers.end(); 57 | } 58 | 59 | } // namespace std 60 | 61 | namespace cat { 62 | using std::initializer_list; 63 | } 64 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/block_all_signals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::block_all_signals() -> signals_mask_set { 5 | signals_mask_set current_mask; 6 | // Most Linux runtimes use an `app_mask` here which excludes signals 32-34, 7 | // which are used for something by pthreads. Because libCat doesn't use 8 | // pthreads, it simply blocks all signals here, for now. 9 | sys_rt_sigprocmask(signal_action::block, &all_signals_mask, ¤t_mask) 10 | .assert(); 11 | return current_mask; 12 | } 13 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/clonearguments.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct clone_arguments { 4 | cat::uint8 flags; 5 | nix::file_descriptor* process_id_file_descriptor; 6 | nix::process_id* child_thread_id; 7 | nix::process_id* parent_thread_id; 8 | cat::int8 exit_code; 9 | void* p_stack; 10 | cat::uword stack_size; 11 | // TODO: Deal with these later: 12 | void* p_tls; 13 | nix::process_id* set_tid; 14 | cat::uword set_tid_size; 15 | cat::uint8 cgroup; 16 | }; 17 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/create_socket_local.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::create_socket_local(cat::int8 type, cat::int8 protocol) 5 | -> nix::scaredy_nix { 6 | return nix::sys_socket(1, type, protocol); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/is_a_tty.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::is_a_tty(file_descriptor file_descriptor) -> scaredy_nix { 5 | // `&size` is an output parameter. 6 | #ifndef __clang__ 7 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 8 | #endif 9 | tty_window_size size; 10 | return sys_ioctl(file_descriptor, io_requests::tiocgwinsz, &size); 11 | } 12 | 13 | auto 14 | nix::is_a_tty(tty_descriptor) -> scaredy_nix { 15 | return cat::monostate; 16 | } 17 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // The child thread exits with a false-positive from asan. 4 | [[gnu::no_sanitize_address]] 5 | auto 6 | nix::process::spawn_impl(cat::uintptr stack, cat::idx initial_stack_size, 7 | cat::idx thread_local_buffer_size, void* p_function, 8 | void* p_args_struct) -> scaredy_nix { 9 | m_stack_size = initial_stack_size; 10 | m_p_stack_bottom = stack.get(); 11 | 12 | // We need the top because memory will be pushed to it downwards on 13 | // x86-64. 14 | cat::uintptr stack_top = 15 | stack + m_stack_size + thread_local_buffer_size; 16 | 17 | cat::uintptr tls_buffer = stack_top; 18 | stack_top -= thread_local_buffer_size; 19 | 20 | // TODO: 32 byte alignment is required for AVX2 support. 21 | // stack_top = cat::align_down(stack_top - 16, 32u); 22 | 23 | // Place a pointer to function arguments on the new stack: 24 | stack_top -= 8; 25 | __builtin_memcpy(stack_top.get(), &p_args_struct, 8); 26 | 27 | // Place a pointer to function on the new stack: 28 | // 8 is the size of a pointer, such as `p_function`. 29 | stack_top -= 8; 30 | __builtin_memcpy(stack_top.get(), &p_function, 8); 31 | 32 | // This syscall is made manually here because it's important to be careful 33 | // with the stack and registers and not introduce a new stack frame. 34 | nix::scaredy_nix result; 35 | asm goto volatile( 36 | R"(mov %[tls], %%r8 37 | xor %%r10, %%r10 38 | syscall 39 | # Branch if this is the parent process. 40 | test %%rax, %%rax 41 | jnz %l[parent_thread] 42 | 43 | # Call the function pointer if this is the child process. 44 | pop %%rax 45 | pop %%rdi # Pass the arguments pointer to the first function parameter. 46 | call *%%rax)" 47 | : /* There are no outputs. */ 48 | : "a"(56), "D"(m_flags), "S"(stack_top), 49 | "d"(&(m_id)), [tls] "r"(tls_buffer) 50 | : 51 | : parent_thread); 52 | 53 | // Exit the child thread after its entry function returns. 54 | // TODO: Support propagating an exit code, like `main()` does. 55 | cat::exit(); 56 | 57 | parent_thread: 58 | prop(result); 59 | return cat::monostate; 60 | } 61 | 62 | [[nodiscard]] 63 | auto 64 | nix::process::wait() const -> scaredy_nix { 65 | // Spin to ensure that `m_id` is initialized before waiting on it. 66 | while (m_id.value == 0) { 67 | } 68 | 69 | return sys_waitid(wait_id::process_id, m_id, 70 | wait_options_flags::exited | wait_options_flags::clone 71 | | wait_options_flags::no_wait); 72 | } 73 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/process.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | template F> 8 | // The child thread exits with a false-positive from asan. 9 | [[gnu::no_sanitize_address, gnu::no_sanitize("undefined")]] 10 | auto 11 | nix::process::spawn(cat::is_allocator auto& allocator, 12 | cat::idx const initial_stack_size, 13 | cat::idx const thread_local_buffer_size, F&& function, 14 | Args&&... arguments) -> scaredy_nix { 15 | // Allocate a stack for this thread. 16 | // TODO: This stack memory should not be owned by the `process`, to 17 | // enable simpler memory management patterns. 18 | // TODO: This should union allocator and linux errors. 19 | // TODO: Use size feedback. 20 | cat::span memory = 21 | prop_as(allocator.template align_alloc_multi( 22 | 16u, initial_stack_size + thread_local_buffer_size), 23 | nix::linux_error::inval); 24 | 25 | // TODO: Support call operator for functors. 26 | // cat::tuple args{fwd(arguments)...}; 27 | 28 | cat::byte* p_stack_bottom = memory.data(); 29 | 30 | if constexpr (sizeof...(arguments) == 0 31 | && (__is_pointer(F) 32 | || __is_function(__remove_reference_t(F)))) { 33 | // If there are no arguments, and `function` is a pointer, it can be 34 | // called almost directly. 35 | return this->spawn_impl(p_stack_bottom, initial_stack_size, 36 | thread_local_buffer_size, 37 | reinterpret_cast(function), nullptr); 38 | } else { 39 | // If there are arguments, `function` must be wrapped in a lambda that has 40 | // tuple storage. 41 | static cat::tuple tuple_args{fwd(function), fwd(arguments)...}; 42 | 43 | // Unary `+` converts this lambda to function pointer. 44 | static auto* p_entry = 45 | +[] [[gnu::no_sanitize_address, gnu::no_sanitize("undefined")]] 46 | (cat::tuple * p_arguments) { 47 | auto&& [fn, ... pack_args] = *p_arguments; 48 | fwd(fn)(fwd(pack_args)...); 49 | }; 50 | 51 | return this->spawn_impl(p_stack_bottom, initial_stack_size, 52 | thread_local_buffer_size, 53 | reinterpret_cast(p_entry), 54 | reinterpret_cast(&tuple_args)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/raise.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::raise(signal signal, process_id pid) -> scaredy_nix { 5 | signals_mask_set current_mask = block_all_signals(); 6 | cat::scaredy result = sys_tkill(pid, signal); 7 | sys_rt_sigprocmask(signal_action::set_mask, ¤t_mask, nullptr).assert(); 8 | return result; 9 | } 10 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/raise_here.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::raise_here(signal signal) -> scaredy_nix { 5 | return raise(signal, sys_getpid()); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/read_char.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::read_char() -> scaredy_nix { 5 | tty_io_serial old_settings = tty_get_attributes(stdin).value(); 6 | tty_io_serial new_settings = old_settings; 7 | 8 | new_settings.local_flags = tty_configuration_flags{ 9 | cat::to_underlying(new_settings.local_flags) 10 | & ~(cat::to_underlying(tty_configuration_flags::icanon) 11 | | cat::to_underlying(tty_configuration_flags::echo))}; 12 | 13 | prop(tty_set_attributes(stdin, tty_set_mode::now, new_settings)); 14 | 15 | char input; 16 | auto _ = sys_read(stdin, &input, 1); 17 | tty_set_attributes(stdin, tty_set_mode::now, old_settings).assert(); 18 | return input; 19 | } 20 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_accept.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Make a connection over a `cat::Socket`. This returns a new socket which has 4 | // been connected to. This new `cat::Socket` is not in a listening state. 5 | auto 6 | nix::sys_accept(nix::file_descriptor socket_descriptor, 7 | void const* __restrict p_socket, 8 | cat::iword const* __restrict p_addr_len) 9 | -> nix::scaredy_nix { 10 | return nix::syscall(43, socket_descriptor, p_socket, 11 | p_addr_len); 12 | } 13 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_bind.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_bind(nix::file_descriptor socket_descriptor, void const* p_socket, 5 | cat::iword p_addr_len) -> nix::scaredy_nix { 6 | return nix::syscall(49, socket_descriptor, p_socket, p_addr_len); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_clone.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_clone(clone_flags flags, void* p_stack_entry, process_id* p_parent_id, 5 | process_id* p_child_id, void* p_thread_pointer) 6 | -> scaredy_nix { 7 | return syscall(56, flags, p_stack_entry, p_parent_id, p_child_id, 8 | p_thread_pointer); 9 | } 10 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_close.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_close(nix::file_descriptor object) -> nix::scaredy_nix { 5 | return nix::syscall(3, object); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_connect.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Connect a socket to an address. 4 | auto 5 | nix::sys_connect(nix::file_descriptor socket_descriptor, void const* p_socket, 6 | cat::iword socket_size) -> nix::scaredy_nix { 7 | return nix::syscall(42, socket_descriptor, p_socket, socket_size); 8 | } 9 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_creat.cpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #include 4 | 5 | auto nix::sys_creat(char const* p_file_path, nix::open_mode file_mode) 6 | -> nix::scaredy_nix { 7 | return nix::sys_open(p_file_path, file_mode, 8 | nix::open_flags::create | nix::open_flags::truncate | 9 | nix::open_mode::read_write); 10 | } 11 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_fstat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_fstat(file_descriptor file_descriptor) 5 | -> cat::scaredy { 6 | file_status status; 7 | scaredy_nix result = syscall(5, file_descriptor, &status); 8 | if (result.has_value()) { 9 | return status; 10 | } 11 | return result.error(); 12 | } 13 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_getpid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_getpid() -> process_id { 5 | return syscall(39).value(); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_ioctl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Control the settings of special files, such as stdin or stdout. 4 | auto 5 | nix::sys_ioctl(file_descriptor io_descriptor, io_requests request) 6 | -> scaredy_nix { 7 | return syscall(54, io_descriptor, request); 8 | } 9 | 10 | // Control the settings of special files, such as stdin or stdout, with a 11 | // request that requires a parameter. 12 | auto 13 | nix::sys_ioctl(file_descriptor io_descriptor, io_requests request, 14 | cat::no_type_ptr p_argument) -> scaredy_nix { 15 | return syscall(16, io_descriptor, request, p_argument); 16 | } 17 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_listen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Mark a socket as available to make connections with `sys_accept()`. 4 | auto 5 | nix::sys_listen(nix::file_descriptor socket_descriptor, cat::int8 backlog) 6 | -> nix::scaredy_nix { 7 | return nix::syscall(50, socket_descriptor, backlog); 8 | } 9 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_mmap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // `map_memory()` wraps the `mmap` Linux syscall. This returns the virtual 4 | // memory address which it has allocated a page at. 5 | auto 6 | nix::sys_mmap(cat::uword beginning_address, cat::uword bytes_size, 7 | nix::memory_protection_flags protections, nix::memory_flags flags, 8 | nix::file_descriptor file_descriptor, cat::uword pages_offset) 9 | -> nix::scaredy_nix { 10 | // TODO: Consider `__builtin_assume_aligned()`. 11 | return nix::syscall(9, beginning_address, bytes_size, protections, 12 | flags, file_descriptor, 13 | pages_offset * cat::page_size); 14 | } 15 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_munmap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // `nix::unmap_memory()` wraps the `munmap` Linux syscall. 4 | auto 5 | nix::sys_munmap(void const* p_memory, cat::uword length) 6 | -> nix::scaredy_nix { 7 | poison_memory_region(p_memory, length); 8 | return nix::syscall(11, p_memory, length); 9 | } 10 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_open.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_open(char const* p_file_path, nix::open_mode file_mode, 5 | nix::open_flags flags) -> nix::scaredy_nix { 6 | // TODO: Figure out how to best support `close_exec`. 7 | // TODO: `large_file` should only be enabled on 64-bit targets. 8 | return syscall( 9 | 2, p_file_path, 10 | nix::open_flags::large_file | flags 11 | | static_cast(file_mode), 12 | file_mode); 13 | } 14 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_read.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // `read()` transmits a number of bytes into a file descriptor. 4 | auto 5 | nix::sys_read(file_descriptor file_descriptor, char const* p_string_buffer, 6 | cat::iword length) -> nix::scaredy_nix { 7 | return nix::syscall(0, file_descriptor, p_string_buffer, length); 8 | } 9 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_readv.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_readv(nix::file_descriptor file_descriptor, 5 | cat::span const& vectors) 6 | -> nix::scaredy_nix { 7 | return nix::syscall(19, file_descriptor, vectors.data(), 8 | vectors.size()); 9 | } 10 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_recv.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_recv(nix::file_descriptor socket_descriptor, void* p_message_buffer, 5 | cat::iword buffer_length, cat::Socket const* __restrict p_addr, 6 | cat::iword const* __restrict p_addr_length) 7 | -> nix::scaredy_nix { 8 | auto foo = nix::syscall(45, socket_descriptor, p_message_buffer, 9 | buffer_length, p_addr, p_addr_length); 10 | return foo; 11 | } 12 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_rt_sigprocmask.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_rt_sigprocmask(signal_action action, 5 | signals_mask_set const* __restrict p_other_set, 6 | signals_mask_set* __restrict p_current_set) 7 | -> scaredy_nix { 8 | scaredy_nix result = 9 | syscall4(14, action, p_other_set, p_current_set, 10 | // Except for MIPS, there are 64 signals in the Linux kernel. 11 | (64 + 1) / 8); 12 | return result; 13 | } 14 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_send.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Returns the number of characters sent to `p_destination_socket`. 4 | auto 5 | nix::sys_sendto(nix::file_descriptor socket_descriptor, 6 | void const* p_message_buffer, cat::iword buffer_length, 7 | cat::int8 flags, cat::Socket const* p_destination_socket, 8 | cat::iword addr_length) -> nix::scaredy_nix { 9 | return nix::syscall(44, socket_descriptor, p_message_buffer, 10 | buffer_length, flags, p_destination_socket, 11 | addr_length); 12 | } 13 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_sendto.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Returns the number of characters sent to `p_destination_socket`. 4 | auto 5 | nix::sys_sendto(nix::file_descriptor socket_descriptor, 6 | void const* p_message_buffer, cat::iword buffer_length, 7 | cat::int8 flags, cat::Socket const* p_destination_socket, 8 | cat::iword addr_length) -> nix::scaredy_nix { 9 | return nix::syscall(44, socket_descriptor, p_message_buffer, 10 | buffer_length, flags, p_destination_socket, 11 | addr_length); 12 | } 13 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_socket.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Create and return a socket. 4 | auto 5 | nix::sys_socket(cat::int8 protocol_family, cat::int8 type, cat::int8 protocol) 6 | -> nix::scaredy_nix { 7 | return nix::syscall(41, protocol_family, type, 8 | protocol); 9 | } 10 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_stat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_stat(cat::str_view const file_path) 5 | -> cat::scaredy { 6 | file_status status; 7 | scaredy_nix result = syscall(4, file_path.data(), &status); 8 | if (result.has_value()) { 9 | return status; 10 | } 11 | return result.error(); 12 | } 13 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_tkill.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_tkill(process_id pid, signal signal) -> scaredy_nix { 5 | return syscall(200, pid, signal); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_unlink.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_unlink(char const* p_path_name) -> nix::scaredy_nix { 5 | return nix::syscall(87, p_path_name); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_wait4.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Make distinct wrappers for `wait` syscall variants. 4 | auto 5 | nix::sys_wait4(nix::process_id waiting_on_id, cat::int4* p_status_output, 6 | nix::wait_options_flags options, void* p_resource_usage) 7 | -> nix::scaredy_nix { 8 | // TODO: Use `p_status_output` for failure-handling. 9 | return nix::syscall(61, waiting_on_id, p_status_output, 10 | options, p_resource_usage); 11 | } 12 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_waitid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_waitid(wait_id type, process_id pid, wait_options_flags options) 5 | -> scaredy_nix { 6 | // TODO: `p_signal_info` should replace `nullptr`. 7 | return syscall(247, type, pid, nullptr, options); 8 | } 9 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_write.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: These should take and return `cat::idx`. 4 | 5 | // `write()` forwards its arguments to a failable stdout syscall. It returns 6 | // the number of bytes that it wrote. 7 | auto 8 | nix::sys_write(nix::file_descriptor file_descriptor, 9 | char const* p_string_buffer, cat::iword length) 10 | -> nix::scaredy_nix { 11 | return nix::syscall(1, file_descriptor, p_string_buffer, length); 12 | } 13 | 14 | // `write()` forwards its arguments to a failable stdout syscall. It returns 15 | // the number of bytes that it wrote. 16 | auto 17 | nix::sys_write(nix::file_descriptor file_descriptor, cat::str_view const string) 18 | -> nix::scaredy_nix { 19 | return nix::syscall(1, file_descriptor, string.data(), 20 | string.size()); 21 | } 22 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/sys_writev.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::sys_writev(nix::file_descriptor file_descriptor, 5 | cat::span const& vectors) 6 | -> nix::scaredy_nix { 7 | return nix::syscall(20, file_descriptor, vectors.data(), 8 | vectors.size()); 9 | } 10 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/syscall.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | template 8 | auto 9 | nix::syscall(cat::iword call, Args... parameters) -> nix::scaredy_nix 10 | requires(sizeof...(Args) < 7) 11 | { 12 | static constexpr unsigned long length = sizeof...(Args); 13 | cat::no_type arguments[length] = {parameters...}; 14 | 15 | // TODO: Make this a `union` of reasonable types. 16 | cat::no_type result; 17 | 18 | if constexpr (length == 0) { 19 | result = nix::syscall0(call); 20 | } else if constexpr (length == 1) { 21 | result = nix::syscall1(call, arguments[0]); 22 | } else if constexpr (length == 2) { 23 | result = nix::syscall2(call, arguments[0], arguments[1]); 24 | } else if constexpr (length == 3) { 25 | result = nix::syscall3(call, arguments[0], arguments[1], arguments[2]); 26 | } else if constexpr (length == 4) { 27 | result = nix::syscall4(call, arguments[0], arguments[1], arguments[2], 28 | arguments[3]); 29 | } else if constexpr (length == 5) { 30 | result = nix::syscall5(call, arguments[0], arguments[1], arguments[2], 31 | arguments[3], arguments[4]); 32 | } else { 33 | result = nix::syscall6(call, arguments[0], arguments[1], arguments[2], 34 | arguments[3], arguments[4], arguments[5]); 35 | } 36 | 37 | if (static_cast(result) < 0) { 38 | return static_cast(result); 39 | } 40 | if constexpr (cat::is_void) { 41 | return cat::monostate; 42 | } else { 43 | return static_cast(result); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/syscall0.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::syscall0(cat::iword call) -> cat::iword { 5 | cat::iword result; 6 | asm volatile("syscall" 7 | : "=a"(result) 8 | : "a"(call) 9 | // Clobbering all of these is necessary to prevent a segfault: 10 | : "memory", "cc", "rcx", "r11"); 11 | return result; 12 | } 13 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/syscall1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | [[gnu::no_sanitize_address]] 4 | auto 5 | nix::syscall1(cat::iword call, cat::no_type arg) -> cat::iword { 6 | cat::iword result; 7 | asm volatile("syscall" 8 | : "=a"(result) 9 | : "a"(call), "D"(arg) 10 | // Clobbering all of these is necessary to prevent a segfault: 11 | : "memory", "cc", "rcx", "r11"); 12 | return result; 13 | } 14 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/syscall2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | [[gnu::no_sanitize_address]] 4 | auto 5 | nix::syscall2(cat::iword call, cat::no_type arg1, cat::no_type arg2) 6 | -> cat::iword { 7 | cat::iword result; 8 | asm volatile("syscall" 9 | : "=a"(result) 10 | : "a"(call), "D"(arg1), "S"(arg2) 11 | // Clobbering all of these is necessary to prevent a segfault: 12 | : "memory", "cc", "rcx", "r11"); 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/syscall3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | [[gnu::no_sanitize_address]] 4 | auto 5 | nix::syscall3(cat::iword call, cat::no_type arg1, cat::no_type arg2, 6 | cat::no_type arg3) -> cat::iword { 7 | cat::iword result; 8 | asm volatile("syscall" 9 | : "=a"(result) 10 | : "a"(call), "D"(arg1), "S"(arg2), "d"(arg3) 11 | // Clobbering all of these is necessary to prevent a segfault: 12 | : "memory", "cc", "rcx", "r11"); 13 | return result; 14 | } 15 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/syscall4.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | [[gnu::no_sanitize_address]] 4 | auto 5 | nix::syscall4(cat::iword call, cat::no_type arg1, cat::no_type arg2, 6 | cat::no_type arg3, cat::no_type const arg4) -> cat::iword { 7 | // `arg4` must be `const-qualified` for GCC to compile. 8 | register cat::no_type const r10 asm("r10") = arg4; 9 | 10 | cat::iword result; 11 | asm volatile("syscall" 12 | : "=a"(result) 13 | : "a"(call), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10) 14 | // Clobbering all of these is necessary to prevent a segfault: 15 | : "rcx", "r11", "memory", "cc"); 16 | return result; 17 | } 18 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/syscall5.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | [[gnu::no_sanitize_address]] 4 | auto 5 | nix::syscall5(cat::iword call, cat::no_type arg1, cat::no_type arg2, 6 | cat::no_type arg3, cat::no_type const arg4, 7 | cat::no_type const arg5) -> cat::iword { 8 | // `arg4` and `arg5` must be `const-qualified` for GCC to compile. 9 | register cat::no_type const r10 asm("r10") = arg4; 10 | register cat::no_type const r8 asm("r8") = arg5; 11 | 12 | cat::iword result; 13 | asm volatile("syscall" 14 | : "=a"(result) 15 | : "a"(call), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8) 16 | // Clobbering all of these is necessary to prevent a segfault: 17 | : "rcx", "r11", "memory", "cc"); 18 | return result; 19 | } 20 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/syscall6.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | [[gnu::no_sanitize_address]] 4 | auto 5 | nix::syscall6(cat::iword call, cat::no_type arg1, cat::no_type arg2, 6 | cat::no_type arg3, cat::no_type const arg4, 7 | cat::no_type const arg5, cat::no_type const arg6) -> cat::iword { 8 | // `arg4`, `arg5`, and `arg6` must be `const-qualified` for GCC to compile. 9 | register cat::no_type r10 asm("r10") = arg4; 10 | register cat::no_type r8 asm("r8") = arg5; 11 | register cat::no_type r9 asm("r9") = arg6; 12 | 13 | cat::iword result; 14 | asm volatile("syscall" 15 | : "=a"(result) 16 | : "a"(call), "D"(arg1), "S"(arg2), "d"(arg3), "r"(r10), "r"(r8), 17 | "r"(r9), [a6] "re"(arg6) 18 | : "rcx", "r11", "memory", "cc"); 19 | return result; 20 | } 21 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/tty_get_attributes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::tty_get_attributes(file_descriptor tty) 5 | -> cat::scaredy { 6 | // `&configuration` is an output parameter. 7 | #ifndef __clang__ 8 | #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" 9 | #endif 10 | tty_io_serial configuration; 11 | cat::scaredy result = sys_ioctl(tty, io_requests::tcgets, &configuration); 12 | 13 | if (result.has_value()) { 14 | // `configuration` is initialized if `result` is a success. 15 | return configuration; 16 | } 17 | return result.error(); 18 | } 19 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/tty_set_attributes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::tty_set_attributes(file_descriptor tty, tty_set_mode tty_mode, 5 | tty_io_serial const& configuration) 6 | -> scaredy_nix { 7 | return sys_ioctl(tty, static_cast(tty_mode), &configuration); 8 | } 9 | -------------------------------------------------------------------------------- /src/libraries/linux/implementations/wait_pid.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | nix::wait_pid(process_id pid, file_status* p_file_status, 5 | wait_options_flags options) -> scaredy_nix { 6 | return syscall(61, pid, p_file_status, options); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/memory/implementations/copy_memory_small.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // `tree-loop-distribute-patterns` is an optimization that replaces this code 5 | // with a call to `memcpy`. As this function is called within `memcpy`, that 6 | // produces an infinite loop. 7 | [[gnu::optimize("-fno-tree-loop-distribute-patterns"), 8 | clang::no_builtin("memcpy")]] 9 | void 10 | cat::copy_memory_small(void const* p_source, void* p_destination, uword bytes) { 11 | unsigned char const* p_source_handle = 12 | static_cast(p_source); 13 | unsigned char* p_destination_handle = 14 | static_cast(p_destination); 15 | 16 | for (uword::raw_type i = 0u; i < bytes; ++i) { 17 | p_destination_handle[i] = p_source_handle[i]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/libraries/meta/implementations/constant_evaluate.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | // TODO: Support a function returning `void`. 8 | 9 | consteval auto 10 | cat::constant_evaluate(is_invocable auto value) -> decltype(auto) { 11 | return value(); 12 | } 13 | 14 | consteval auto 15 | cat::constant_evaluate(auto value) -> decltype(auto) { 16 | return value; 17 | } 18 | -------------------------------------------------------------------------------- /src/libraries/new/implementations/new.cpp: -------------------------------------------------------------------------------- 1 | // Placement `new`. 2 | [[nodiscard]] 3 | auto 4 | operator new(unsigned long, void* p_address) -> void* { 5 | return p_address; 6 | } 7 | 8 | [[nodiscard]] 9 | auto 10 | operator new[](unsigned long, void* p_address) -> void* { 11 | return p_address; 12 | } 13 | 14 | [[nodiscard]] 15 | auto 16 | operator new[](unsigned long) -> void* { 17 | return __builtin_bit_cast(void*, 1ul); 18 | } 19 | 20 | [[nodiscard]] 21 | auto 22 | operator new[](unsigned long, std::align_val_t align) -> void* { 23 | return __builtin_bit_cast(void*, align); 24 | } 25 | 26 | void 27 | operator delete[](void*) { 28 | } 29 | 30 | void 31 | operator delete[](void*, unsigned long) { 32 | } 33 | 34 | void 35 | operator delete[](void*, unsigned long, std::align_val_t) { 36 | } 37 | -------------------------------------------------------------------------------- /src/libraries/notype/cat/notype: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace cat { 8 | 9 | // `no_type`'s purpose is to implicitly cast from any type into `void*`. This is 10 | // useful for low-level type erasure in some cases, such as passing syscall 11 | // arguments. 12 | class no_type { 13 | public: 14 | constexpr no_type() = default; 15 | 16 | constexpr no_type(auto input) 17 | requires(sizeof(input) == 1) 18 | : m_q_storage(__builtin_bit_cast(unsigned char, input)) { 19 | } 20 | 21 | constexpr no_type(auto input) 22 | requires(sizeof(input) == 2) 23 | : m_s_storage(__builtin_bit_cast(unsigned short, input)) { 24 | } 25 | 26 | constexpr no_type(auto input) 27 | requires(sizeof(input) == 4) 28 | : m_l_storage(__builtin_bit_cast(unsigned int, input)) { 29 | } 30 | 31 | constexpr no_type(auto input) 32 | requires(sizeof(input) == 8) 33 | : m_q_storage(__builtin_bit_cast(unsigned long long, input)) { 34 | } 35 | 36 | template 37 | requires(!is_void) 38 | [[nodiscard]] 39 | constexpr 40 | operator T() const { 41 | if constexpr (sizeof(T) == 1) { 42 | return __builtin_bit_cast(T, m_b_storage); 43 | } else if constexpr (sizeof(T) == 2) { 44 | return __builtin_bit_cast(T, m_s_storage); 45 | } else if constexpr (sizeof(T) == 4) { 46 | return __builtin_bit_cast(T, m_l_storage); 47 | } else if constexpr (sizeof(T) == 8) { 48 | return __builtin_bit_cast(T, m_q_storage); 49 | } 50 | } 51 | 52 | private: 53 | union { 54 | unsigned char m_b_storage; // Byte sized storage. 55 | unsigned short m_s_storage; // Short sized storage. 56 | unsigned int m_l_storage; // Full sized storage. 57 | unsigned long long m_q_storage; // Quad sized storage. 58 | }; 59 | }; 60 | 61 | // `no_type_ptr` is like `no_type`, but it only converts to or from pointers. 62 | class no_type_ptr { 63 | public: 64 | constexpr no_type_ptr() = default; 65 | 66 | constexpr no_type_ptr(decltype(nullptr)) : m_p_storage(nullptr) { 67 | } 68 | 69 | constexpr no_type_ptr(auto const* p_input) 70 | : m_p_storage(const_cast(static_cast(p_input))) { 71 | } 72 | 73 | template 74 | requires(is_pointer) 75 | [[nodiscard]] 76 | constexpr 77 | operator T() const { 78 | return static_cast(m_p_storage); 79 | } 80 | 81 | private: 82 | void* m_p_storage; 83 | }; 84 | 85 | } // namespace cat 86 | -------------------------------------------------------------------------------- /src/libraries/runtime/cat/runtime: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace cat { 8 | 9 | namespace detail { 10 | // Name mangling prevents the linker from finding this symbol. 11 | extern "C" [[gnu::used 12 | #ifndef NO_ARGC_ARGV 13 | , 14 | gnu::naked 15 | #endif 16 | ]] 17 | void 18 | _start(); // NOLINT 19 | } // namespace detail 20 | 21 | // The `cat::exit()` function is provided globally. This streamlines out the 22 | // existence of `_exit()`. 23 | [[noreturn]] 24 | void 25 | exit(iword exit_code = 0); 26 | 27 | auto 28 | load_base_stack_pointer() -> void*; 29 | 30 | // This must be inlined to align the stack pointer on the stack frame it is 31 | // called from. 32 | [[gnu::always_inline]] 33 | inline void 34 | align_stack_pointer_16() { 35 | // rmsbolt requires `.att_syntax prefix` here for some reason. 36 | asm volatile(".att_syntax prefix ;\nand $-16, %rsp"); 37 | } 38 | 39 | // This must be inlined to align the stack pointer on the stack frame it is 40 | // called from. 41 | [[gnu::always_inline]] 42 | inline void 43 | align_stack_pointer_32() { 44 | // rmsbolt requires `.att_syntax prefix` here for some reason. 45 | asm volatile(".att_syntax prefix ;\nand $-32, %rsp"); 46 | } 47 | 48 | extern "C" [[noreturn]] 49 | void 50 | __stack_chk_fail(); 51 | 52 | extern "C" void 53 | __cxa_atexit(void (*p_invocable)(void*), void* p_arg, void* p_dso_handle); 54 | 55 | extern "C" [[gnu::used]] 56 | void 57 | __cxa_pure_virtual(); 58 | 59 | class jmp_buffer { 60 | // This buffer layout should vary by all ISAs. 61 | 62 | private: 63 | // %rbx: 64 | // %rbp: 65 | // %r12: 66 | // %r13: 67 | // %r14: 68 | // %r15: 69 | [[maybe_unused]] 70 | unsigned long m_registers[6]; 71 | // %rsp: 72 | [[maybe_unused]] 73 | unsigned long m_stack_pointer; 74 | }; 75 | 76 | [[gnu::naked, gnu::returns_twice]] 77 | auto 78 | setjmp(jmp_buffer& jump_point) -> int4; 79 | 80 | [[noreturn, gnu::naked]] 81 | void 82 | longjmp(jmp_buffer& jump_point, int8 return_value); 83 | 84 | } // namespace cat 85 | 86 | inline void* __dso_handle = nullptr; 87 | 88 | extern "C" [[gnu::used]] 89 | inline void 90 | __cxx_global_var_init() { 91 | } 92 | 93 | extern "C" [[gnu::used]] 94 | inline void 95 | __cxa_guard_acquire() { 96 | } 97 | 98 | extern "C" [[gnu::used]] 99 | inline void 100 | __cxa_guard_release() { 101 | } 102 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/__cxa_atexit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This function is a stub. 4 | extern "C" void 5 | cat::__cxa_atexit(void (*)(void*), void*, void*) { 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/__cxa_pure_virtual.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // This function is a stub. 5 | extern "C" void 6 | cat::__cxa_pure_virtual() { 7 | verify(false, "A pure virtual function was called with no implementation."); 8 | } 9 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/__dso_handle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // This function is a stub. 4 | 5 | extern "C" auto cat::__dso_handle(void (*)(void*), void*, void*) -> int { 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/__stack_chk_fail.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // `__stack_chk_fail()` is called when stack overflow occurs in programs 4 | // compiled without `-fno-stack-protector`. This will terminate the program with 5 | // exit code `1`. 6 | extern "C" [[noreturn]] 7 | void 8 | cat::__stack_chk_fail() { 9 | cat::exit(1); 10 | __builtin_unreachable(); 11 | } 12 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/_start.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Attributes on this prototype would have no effect. 6 | auto 7 | main(...) -> int; // NOLINT Without K&R, `...` is the only way to do this. 8 | 9 | namespace { 10 | 11 | using constructor_fn = void (*const)(); 12 | extern "C" { 13 | extern constructor_fn __init_array_start[]; 14 | extern constructor_fn __init_array_end[]; 15 | 16 | void 17 | call_static_constructors() { 18 | // Execute all function pointers in `__init_array_start`. 19 | for (constructor_fn const* pp_ctor_func = __init_array_start; 20 | pp_ctor_func < __init_array_end; ++pp_ctor_func) { 21 | constructor_fn p_ctor = *pp_ctor_func; 22 | p_ctor(); 23 | } 24 | } 25 | 26 | #ifndef NO_ARGC_ARGV 27 | [[noreturn, gnu::used, force_align_arg_pointer(32), gnu::no_stack_protector, 28 | gnu::no_sanitize_address]] 29 | void 30 | call_main_args(int argc, char** pp_argv) { 31 | // The stack pointer must be aligned to prevent SIMD segfaults. 32 | call_static_constructors(); 33 | // Initialize `__cpu_model` and `__cpu_features2` for later use. 34 | x64::detail::__cpu_indicator_init(); 35 | cat::exit(main(argc, pp_argv)); 36 | } 37 | #else 38 | [[noreturn, force_align_arg_pointer(32), gnu::no_stack_protector, 39 | gnu::no_sanitize_address]] 40 | void 41 | call_main_noargs() { 42 | // The stack pointer must be aligned to prevent SIMD segfaults. 43 | cat::align_stack_pointer_32(); 44 | // Initialize `__cpu_model` and `__cpu_features2` for later use. 45 | x64::detail::__cpu_indicator_init(); 46 | call_static_constructors(); 47 | cat::exit(main()); 48 | } 49 | #endif 50 | } 51 | 52 | } // namespace 53 | 54 | // clang-format off 55 | extern "C" 56 | [[gnu::used 57 | #ifndef NO_ARGC_ARGV 58 | // If arguments are loaded, this must be `naked` to prevent pushing `%rbp` 59 | // first, which breaks argument loading. I've tried other solutions, but 60 | // none worked yet. 61 | , gnu::naked 62 | #endif 63 | ]] 64 | // clang-format on 65 | void 66 | cat::detail::_start() { 67 | // `NO_ARGC_ARGV` can be defined in a build system to skip argument loading. 68 | #ifndef NO_ARGC_ARGV 69 | asm(R"(.att_syntax prefix ; # rmsbolt requires this. Try `-masm=att` 70 | pop %rdi # Load `int4 argc`. 71 | mov %rsp, %rsi # Load `char* argv[]`. 72 | call call_main_args 73 | )"); 74 | #else 75 | [[clang::always_inline]] call_main_noargs(); 76 | #endif 77 | } 78 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/exit.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Terminate the program. Without arguments, this exits with a success code for 4 | // the target operating system. 5 | [[noreturn]] 6 | void 7 | cat::exit(iword exit_code) { 8 | asm("syscall" 9 | : 10 | : "D"(exit_code), "a"(60)); 11 | __builtin_unreachable(); // This elides a `ret` instruction. 12 | } 13 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/load_argc.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cons-Cat/libCat/3f54e47f0ed182771fce8d0a80366ce63adb36fc/src/libraries/runtime/implementations/load_argc.cpp -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/load_base_stack_pointer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | auto 4 | cat::load_base_stack_pointer() -> void* { 5 | void* p_stack_base; 6 | asm("mov %%rbp, %[rbp]" 7 | : [rbp] "=r"(p_stack_base)); 8 | return p_stack_base; 9 | } 10 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/longjmp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Should `__builtin_setjmp_reciever()` be used here? 4 | [[noreturn, gnu::naked]] 5 | void 6 | cat::longjmp(jmp_buffer& /* jump_buffer */, int8 /* return_value */) { 7 | asm volatile(R"( 8 | # Increment %rax if `return_value` is non-zero. 9 | # This code is equivalent to: 10 | # %rax = return_value + (!return_value); 11 | xor %rax, %rax 12 | cmp $1, %rsi 13 | adc %rsi, %rax 14 | 15 | # Load registers from `jump_buffer`. 16 | mov (%rdi), %rbx 17 | mov 8(%rdi), %rbp 18 | mov 16(%rdi), %r12 19 | mov 24(%rdi), %r13 20 | mov 32(%rdi), %r14 21 | mov 40(%rdi), %r15 22 | 23 | # Return to the stack pointer saved in `jump_buffer`. 24 | mov 48(%rdi), %rsp 25 | jmp *56(%rdi))"); 26 | } 27 | -------------------------------------------------------------------------------- /src/libraries/runtime/implementations/setjmp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Should `__builtin_setjmp_setup()`/`__builtin_setjmp()` be used here? 4 | [[gnu::naked, gnu::returns_twice]] 5 | auto 6 | cat::setjmp(jmp_buffer& /* jump_point */) -> int4 { 7 | asm volatile(R"( 8 | # Put the pointer to `jump_point` in %rdi. 9 | mov %rbx, (%rdi) 10 | 11 | # Store various registers into `jump_point`. 12 | mov %rbp, 8(%rdi) 13 | mov %r12, 16(%rdi) 14 | mov %r13, 24(%rdi) 15 | mov %r14, 32(%rdi) 16 | mov %r15, 40(%rdi) 17 | 18 | # Store the stack pointer into `jump_point`. 19 | lea 8(%rsp), %rdx 20 | mov %rdx, 48(%rdi) 21 | mov (%rsp), %rdx 22 | mov %rdx, 56(%rdi) 23 | 24 | # Return 0 here, to differentiate the `cat::setjmp()` call here from a 25 | # `cat::longjmp()` to the same point, which will set %rax to a 26 | # potentially different value. 27 | xor %rax, %rax 28 | ret)"); 29 | } 30 | -------------------------------------------------------------------------------- /src/libraries/sanitizer/cat/sanitizer: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace cat { 8 | 9 | // Define either hooks or shims for asan. 10 | 11 | #if __has_feature(address_sanitizer) 12 | extern "C" { 13 | 14 | // TODO: Comment these with documentation and use safer types. 15 | void 16 | __asan_poison_memory_region(void const volatile* p_address, uword size); 17 | void 18 | __asan_unpoison_memory_region(void const volatile* p_address, uword size); 19 | 20 | // These functions are only for debugging. 21 | [[gnu::used]] 22 | void 23 | __asan_describe_address(void* p_address); 24 | 25 | [[gnu::used]] 26 | extern auto 27 | __asan_address_is_poisoned(void const volatile* p_address) -> int; 28 | 29 | [[gnu::used]] 30 | auto 31 | __asan_region_is_poisoned(void* p_begin, uword size) -> void*; 32 | 33 | [[gnu::used]] 34 | auto 35 | __asan_report_present() -> int; 36 | 37 | [[gnu::used]] 38 | auto 39 | __asan_get_report_pc() -> void*; 40 | 41 | [[gnu::used]] 42 | auto 43 | __asan_get_report_bp() -> void*; 44 | 45 | [[gnu::used]] 46 | auto 47 | __asan_get_report_sp() -> void*; 48 | 49 | [[gnu::used]] 50 | auto 51 | __asan_get_report_address() -> void*; 52 | 53 | [[gnu::used]] 54 | auto 55 | __asan_get_report_access_type() -> int; 56 | 57 | [[gnu::used]] 58 | auto 59 | __asan_get_report_access_size() -> __SIZE_TYPE__; 60 | 61 | [[gnu::used]] 62 | auto 63 | __asan_get_report_description() -> char const*; 64 | 65 | [[gnu::used]] 66 | auto 67 | __asan_locate_address(void* p_address, char* p_name, uword name_size, 68 | void** p_region_address, uword* p_region_size) 69 | -> char const*; 70 | 71 | [[gnu::used]] 72 | auto 73 | __asan_get_alloc_stack(void* p_heap, void** pp_trace, uword size, 74 | int* p_thread_id) -> __SIZE_TYPE__; 75 | 76 | [[gnu::used]] 77 | auto 78 | __asan_get_free_stack(void* p_heap, void** pp_trace, uword size, 79 | int* p_thread_id) -> __SIZE_TYPE__; 80 | 81 | [[gnu::used]] 82 | void 83 | __asan_get_shadow_mapping(uword* p_shadow_scale, uword* p_shadow_offset); 84 | } 85 | #else 86 | // No-op functions are provided when asan is not available. 87 | inline void 88 | __asan_poison_memory_region(void const volatile*, uword) { 89 | } 90 | 91 | inline void 92 | __asan_unpoison_memory_region(void const volatile*, uword) { 93 | } 94 | #endif 95 | 96 | template 97 | [[gnu::nodebug, gnu::always_inline]] 98 | constexpr void 99 | poison_memory_region([[maybe_unused]] T const volatile* p_address, 100 | [[maybe_unused]] uword size) { 101 | #if __has_feature(address_sanitizer) 102 | if !consteval { 103 | __asan_poison_memory_region( 104 | reinterpret_cast(p_address), size); 105 | } 106 | #endif 107 | } 108 | 109 | template 110 | [[gnu::nodebug, gnu::always_inline]] 111 | constexpr void 112 | unpoison_memory_region([[maybe_unused]] T const volatile* p_address, 113 | [[maybe_unused]] uword size) { 114 | #if __has_feature(address_sanitizer) 115 | if !consteval { 116 | __asan_unpoison_memory_region( 117 | reinterpret_cast(p_address), size); 118 | } 119 | #endif 120 | } 121 | 122 | } // namespace cat 123 | -------------------------------------------------------------------------------- /src/libraries/simd/cat/detail/simd_avx2.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace x64 { 9 | 10 | template 11 | [[nodiscard]] 12 | auto 13 | testc(cat::simd_mask, T> left, cat::simd_mask, T> right) 14 | -> cat::int4 { 15 | if constexpr (cat::is_same) { 16 | return __builtin_ia32_vtestcps256(left.raw, right.raw); 17 | } else if constexpr (cat::is_same) { 18 | return __builtin_ia32_vtestcpd256(left.raw, right.raw); 19 | } else { 20 | auto left_raw = 21 | reinterpret_cast::raw_type>(left.raw); 22 | auto right_raw = 23 | reinterpret_cast::raw_type>(right.raw); 24 | return __builtin_ia32_ptestc256(left_raw, right_raw); 25 | } 26 | } 27 | 28 | template 29 | [[nodiscard]] 30 | auto 31 | testz(cat::simd_mask, T> left, cat::simd_mask, T> right) 32 | -> cat::int4 { 33 | if constexpr (cat::is_same) { 34 | return __builtin_ia32_vtestzps256(left.raw, right.raw); 35 | } else if constexpr (cat::is_same) { 36 | return __builtin_ia32_vtestzpd256(left.raw, right.raw); 37 | } else { 38 | auto left_raw = 39 | reinterpret_cast::raw_type>(left.raw); 40 | auto right_raw = 41 | reinterpret_cast::raw_type>(right.raw); 42 | return __builtin_ia32_ptestz256(left_raw, right_raw); 43 | } 44 | } 45 | 46 | } // namespace x64 47 | 48 | namespace cat { 49 | 50 | // Implementation of `simd_all_of()` for AVX2. 51 | template 52 | [[nodiscard]] 53 | auto 54 | simd_all_of(simd_mask, T> mask) -> bool { 55 | return testc(mask, mask == mask) != 0; 56 | } 57 | 58 | // Implementation of `simd_any_of()` for AVX2. 59 | template 60 | [[nodiscard]] 61 | auto 62 | simd_any_of(simd_mask, T> mask) -> bool { 63 | return testz(mask, mask == mask) == 0; 64 | } 65 | 66 | // Implementation of `simd_to_bitset` for AVX2. 67 | template 68 | // TODO: Support larger integrals than 1. 69 | requires(is_floating_point || (sizeof(T) == 1)) 70 | [[nodiscard]] 71 | auto 72 | simd_to_bitset(simd_mask, T> mask) -> bitset<32u> { 73 | if constexpr (is_same) { 74 | // Create a bitmask from the most significant bit of every `float` in 75 | // this vector. 76 | return make_bitset<32u>( 77 | make_unsigned(__builtin_ia32_movmskps256(mask.raw))); 78 | } else if constexpr (is_same) { 79 | // Create a bitmask from the most significant bit of every `double` in 80 | // this vector. 81 | return make_bitset<32u>( 82 | make_unsigned(__builtin_ia32_movmskpd256(mask.raw))); 83 | } else { 84 | // Create a bitmask from the most significant bit of every byte in this 85 | // vector. 86 | return make_bitset<32u>( 87 | make_unsigned(__builtin_ia32_pmovmskb256(mask.raw))); 88 | } 89 | } 90 | 91 | } // namespace cat 92 | -------------------------------------------------------------------------------- /src/libraries/simd/cat/detail/simd_avx2_fwd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cat { 4 | 5 | // Forward declarations. 6 | template 7 | requires(is_same) 8 | class alignas(abi_type::alignment.raw) simd; 9 | 10 | template 11 | class alignas(abi_type::alignment.raw) simd_mask; 12 | 13 | } // namespace cat 14 | 15 | namespace x64 { 16 | 17 | // `avx2_abi` is a SIMD ABI that can be expected to work on most reasonable 18 | // x86-64 build target. 19 | template 20 | struct avx2_abi { 21 | using scalar_type = T; 22 | 23 | // Produce a similar `avx2_abi` for type `U`. 24 | template 25 | using make_abi_type = avx2_abi; 26 | 27 | avx2_abi() = delete; 28 | 29 | static constexpr cat::idx size = 32u; 30 | static constexpr cat::uword lanes = size / sizeof(T); 31 | static constexpr cat::uword alignment = 32u; 32 | }; 33 | 34 | template 35 | using avx2_simd = cat::simd, T>; 36 | 37 | template 38 | using avx2_simd_mask = cat::simd_mask, T>; 39 | 40 | template 41 | [[nodiscard]] 42 | auto 43 | testc(cat::simd_mask, T> left, cat::simd_mask, T> right) 44 | -> cat::int4; 45 | 46 | template 47 | [[nodiscard]] 48 | auto 49 | testz(cat::simd_mask, T> left, 50 | cat::simd_mask, T> right) -> cat::int4; 51 | 52 | } // namespace x64 53 | 54 | namespace cat { 55 | template 56 | [[nodiscard]] 57 | auto 58 | all_of(simd_mask, T> mask) -> bool; 59 | 60 | template 61 | [[nodiscard]] 62 | auto 63 | any_of(simd_mask, T> mask) -> bool; 64 | 65 | template 66 | class bitset; 67 | 68 | template 69 | [[nodiscard]] 70 | auto 71 | simd_to_bitset(simd_mask, T> mask) -> bitset<32u>; 72 | 73 | } // namespace cat 74 | -------------------------------------------------------------------------------- /src/libraries/simd/cat/detail/simd_impl.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // These headers cause a circular `#include` in `` due to using 4 | // `cat::bitset`, which uses `cat::array`, which uses `cat::simd`. 5 | // This header is `#include`d in `cat::string` to resolve that error. 6 | 7 | #include 8 | #include 9 | -------------------------------------------------------------------------------- /src/libraries/simd/cat/detail/simd_sse42.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | namespace x64 { 8 | 9 | template 10 | constexpr auto 11 | compare_implicit_length_strings(auto const& vector_1, auto const& vector_2) 12 | -> bool; 13 | 14 | template 15 | constexpr auto 16 | compare_implicit_length_strings_return_index(auto const& vector_1, 17 | auto const& vector_2) -> cat::int4; 18 | 19 | } // namespace x64 20 | -------------------------------------------------------------------------------- /src/libraries/simd/cat/detail/simd_sse42_fwd.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace cat { 4 | 5 | // Forward declarations. 6 | template 7 | requires(is_same) 8 | class alignas(abi_type::alignment.raw) simd; 9 | 10 | template 11 | class alignas(abi_type::alignment.raw) simd_mask; 12 | 13 | } // namespace cat 14 | 15 | namespace x64 { 16 | 17 | // `Sse2abi_type` is a SIMD ABI that can be expected to work on any x86-64 build 18 | // target. 19 | template 20 | struct sse42_abi { 21 | using scalar_type = T; 22 | 23 | // Produce a similar `sse42_abi` for type `U`. 24 | template 25 | using make_abi_type = sse42_abi; 26 | 27 | sse42_abi() = delete; 28 | 29 | static constexpr cat::idx size = 16u; 30 | static constexpr cat::uword lanes = size / sizeof(T); 31 | static constexpr cat::uword alignment = 16u; 32 | }; 33 | 34 | template 35 | using sse42_simd = cat::simd, T>; 36 | 37 | template 38 | using sse42_simd_mask = cat::simd_mask, T>; 39 | 40 | enum class string_control : unsigned char { 41 | // Unsigned 1-byte characters. 42 | unsigned_byte = 0x00, 43 | // Unsigned 2-byte characters. 44 | unsigned_word = 0x01, 45 | // Signed 1-byte characters. 46 | signed_byte = 0x02, 47 | // Signed 2-byte characters. 48 | signed_word = 0x03, 49 | // Compare if any characters are equal. 50 | compare_equal_any = 0x00, 51 | // Compare ranges. 52 | compare_ranges = 0x04, 53 | // Compare if every character is equal. 54 | compare_equal_each = 0x08, 55 | // Compare equal ordered. 56 | compare_equal_ordered = 0x0c, 57 | // Polarity. 58 | positive_polarity = 0x00, 59 | // Negate the results. 60 | negative_polarity = 0x10, 61 | masked_positive_polarity = 0x20, 62 | // Negate the results only before the end of the string. 63 | masked_negative_polarity = 0x30, 64 | // Return the least significant bit. 65 | least_significant = 0x00, 66 | // Return the most significant bit. 67 | most_significant = 0x40, 68 | // Return a bit mask. 69 | bit_mask = 0x00, 70 | // Return a byte/word mask. 71 | unit_mask = 0x40, 72 | }; 73 | 74 | // TODO: Generalize this. 75 | constexpr auto 76 | operator|(string_control flag_1, string_control flag_2) -> string_control { 77 | return static_cast(static_cast(flag_1) 78 | | static_cast(flag_2)); 79 | } 80 | 81 | } // namespace x64 82 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/cmp_implicit_str_c.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | // TODO: Prove that this template parameter is actually more performant than a 6 | // function parameter. 7 | // TODO: Perfect forwarding the function parameters. 8 | template 9 | constexpr auto cat::compare_implicit_length_strings(auto const& vector_1, 10 | auto const& vector_2) 11 | -> bool { 12 | static_assert(cat::is_same_v); 13 | return __builtin_ia32_pcmpistric128(vector_1.value, vector_2.value, 14 | static_cast(mask_type)); 15 | } 16 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/cmp_implicit_str_i.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | // TODO: Prove that this template parameter is actually more performant than a 6 | // function parameter. 7 | template 8 | constexpr auto cat::compare_implicit_length_strings_return_index( 9 | auto const& vector_1, auto const& vector_2) -> int4 { 10 | static_assert(cat::is_same_v); 11 | return __builtin_ia32_pcmpistri128(vector_1.value, vector_2.value, 12 | static_cast(mask_type)); 13 | } 14 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/compare_implicit_length_strings.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | // TODO: Improve this function's name. 8 | // TODO: Perfect forwarding the function parameters. 9 | // TODO: Handle the type of `char` automatically to make this type-safe. 10 | // `control_mask` must be constant-evaluated for the intrinsic to work 11 | // correctly. 12 | template 13 | constexpr auto 14 | x64::compare_implicit_length_strings(auto const& vector_1, auto const& vector_2) 15 | -> bool { 16 | static_assert(cat::is_same); 17 | return __builtin_ia32_pcmpistric128( 18 | vector_1.raw, vector_2.raw, static_cast(control_mask)); 19 | } 20 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/compare_implicit_length_strings_return_index.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | // `control_mask` must be constant-evaluated for the intrinsic to work 8 | // correctly. 9 | template 10 | constexpr auto 11 | x64::compare_implicit_length_strings_return_index(auto const& vector_1, 12 | auto const& vector_2) 13 | -> cat::int4 { 14 | static_assert(cat::is_same); 15 | return __builtin_ia32_pcmpistri128(vector_1.raw, vector_2.raw, 16 | static_cast(control_mask)); 17 | } 18 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_avx2_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_avx2_supported() -> bool { 6 | return __builtin_cpu_supports("avx2"); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_avx512f_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_avx512f_supported() -> bool { 6 | __builtin_cpu_init(); 7 | return __builtin_cpu_supports("avx512f"); 8 | } 9 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_avx512vl_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto is_avx512vl_supported() -> bool { 5 | return __builtin_cpu_supports("avx512vl"); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_avx_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_avx_supported() -> bool { 6 | return __builtin_cpu_supports("avx"); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_mmx_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_mmx_supported() -> bool { 6 | return __builtin_cpu_supports("mmx"); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_sse1_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_sse1_supported() -> bool { 6 | return __builtin_cpu_supports("sse"); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_sse2_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_sse2_supported() -> bool { 6 | return __builtin_cpu_supports("sse2"); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_sse3_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_sse3_supported() -> bool { 6 | return __builtin_cpu_supports("sse3"); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_sse4_1_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_sse4_1_supported() -> bool { 6 | return __builtin_cpu_supports("sse4.1"); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_sse4_2_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto 5 | is_sse4_2_supported() -> bool { 6 | return __builtin_cpu_supports("sse4.2"); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/is_ssse3_supported.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | auto is_ssse3_supported() -> bool { 5 | return __builtin_cpu_supports("ssse3"); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/sfence.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void 4 | cat::sfence() { 5 | __builtin_ia32_sfence(); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/shuffle.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // TODO: Use a SIMD vector concept. 6 | auto 7 | cat::shuffle(auto in_vector, auto mask) { 8 | decltype(in_vector) out_vector; 9 | __builtin_shuffle(in_vector, out_vector, mask); 10 | return out_vector; 11 | } 12 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/stream_in.tpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // TODO: Constrain parameter with a vector concept. 6 | // TODO: This code can be simplified a lot. 7 | /* Non-temporally copy a vector into some address. */ 8 | template 9 | void 10 | cat::stream_in(void* p_destination, T const* source) { 11 | // TODO: Make an integral-vector concept to simplify this. 12 | // Streaming 4-byte floats. 13 | if constexpr (cat::is_same) { 14 | __builtin_ia32_movntps(p_destination, source); 15 | } else if constexpr (cat::is_same) { 16 | __builtin_ia32_movntps256(p_destination, source); 17 | } 18 | // Streaming 8-byte floats. 19 | else if constexpr (cat::is_same) { 20 | __builtin_ia32_movntpd(p_destination, source); 21 | } else if constexpr (cat::is_same) { 22 | __builtin_ia32_movntpd256(p_destination, source); 23 | } 24 | // Streaming 1-byte ints. 25 | else if constexpr (cat::is_same || cat::is_same) { 26 | __builtin_ia32_movnti(p_destination, source); 27 | } else if constexpr (cat::is_same || cat::is_same) { 28 | __builtin_ia32_movntq(p_destination, source); 29 | } else if constexpr (cat::is_same || cat::is_same) { 30 | __builtin_ia32_movntq128(p_destination, source); 31 | } else if constexpr (cat::is_same || cat::is_same) { 32 | __builtin_ia32_movntq256(p_destination, source); 33 | } 34 | // Streaming 2-byte ints. 35 | else if constexpr (cat::is_same || cat::is_same) { 36 | __builtin_ia32_movnti(p_destination, source); 37 | } else if constexpr (cat::is_same || cat::is_same) { 38 | __builtin_ia32_movntq(p_destination, source); 39 | } else if constexpr (cat::is_same || cat::is_same) { 40 | __builtin_ia32_movntq128(p_destination, source); 41 | } else if constexpr (cat::is_same || cat::is_same) { 42 | __builtin_ia32_movntq256(p_destination, source); 43 | } 44 | // Streaming 4-byte ints. 45 | else if constexpr (cat::is_same || cat::is_same) { 46 | __builtin_ia32_movntq(p_destination, source); 47 | } else if constexpr (cat::is_same || cat::is_same) { 48 | __builtin_ia32_movntq128(p_destination, source); 49 | } else if constexpr (cat::is_same || cat::is_same) { 50 | __builtin_ia32_movntdq256(p_destination, source); 51 | } 52 | // Streaming 8-byte ints. 53 | else if constexpr (cat::is_same || cat::is_same) { 54 | __builtin_ia32_movntq128(p_destination, source); 55 | } else if constexpr (cat::is_same || cat::is_same) { 56 | __builtin_ia32_movntdq256(p_destination, source); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/zero_avx_registers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | void 5 | cat::zero_avx_registers() { 6 | __builtin_ia32_vzeroall(); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/simd/implementations/zero_upper_avx_registers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // TODO: Document. 4 | void 5 | cat::zero_upper_avx_registers() { 6 | __builtin_ia32_vzeroupper(); 7 | } 8 | -------------------------------------------------------------------------------- /src/libraries/span/cat/span: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // TODO: Add tests. 11 | 12 | namespace cat { 13 | 14 | // A `span` is represents an array of `T` with runtime-known length. It stores 15 | // an address and a length. It is random-access iterable. 16 | template 17 | class [[clang::trivial_abi, gsl::Pointer(T)]] span 18 | : public collection_interface, T>, 19 | public random_access_iterable_interface { 20 | public: 21 | constexpr span() = default; 22 | constexpr span(span const&) = default; 23 | constexpr span(span&&) = default; 24 | 25 | // Construct a `span` as `{address, length}`. 26 | constexpr span(T* p_in_data [[clang::lifetimebound]], idx in_length) 27 | : m_p_data(p_in_data), m_size(in_length) { 28 | } 29 | 30 | // Construct an empty `span` with `nullptr`. 31 | constexpr span(decltype(nullptr)) : m_p_data(nullptr), m_size(0) { 32 | } 33 | 34 | // Construct a span from a static `initializer_list`. 35 | constexpr span(initializer_list initializers) 36 | requires(is_const) 37 | : m_p_data(initializers.begin()), m_size(initializers.size()) { 38 | } 39 | 40 | // Implicitly convert a contiguous container into a span. Const-correctness 41 | // is managed by the deduction guides below. 42 | template 43 | constexpr span(U& collection [[clang::lifetimebound]] 44 | ) 45 | : m_p_data(collection.data()), m_size(collection.size()) { 46 | } 47 | 48 | template 49 | constexpr span(U&& collection) = 50 | delete("A `cat::span` cannot alias a temporary, because the span would" 51 | "immediately dangle."); 52 | 53 | constexpr auto 54 | operator=(span const&) -> span& = default; 55 | constexpr auto 56 | operator=(span&&) -> span& = default; 57 | 58 | // Construct a span from a static `initializer_list`. 59 | constexpr auto 60 | operator=(initializer_list initializers) -> span& 61 | requires(is_const) 62 | { 63 | m_p_data = initializers.begin(); 64 | m_size = initializers.size(); 65 | return *this; 66 | } 67 | 68 | // Implicitly convert a contiguous container into a span. Const-correctness 69 | // is managed by the deduction guides below. 70 | template 71 | auto 72 | operator=(U& collection [[clang::lifetimebound]]) -> span& { 73 | m_p_data = collection.data(); 74 | m_size = collection.size(); 75 | return *this; 76 | } 77 | 78 | template 79 | auto 80 | operator=(U&& collection) = 81 | delete("A `cat::span` cannot alias a temporary, because the span would" 82 | "immediately dangle."); 83 | 84 | // Get the non-`const` address that this `span` starts at. 85 | [[nodiscard]] 86 | constexpr auto 87 | data() [[clang::lifetimebound]] -> T* { 88 | return m_p_data; 89 | } 90 | 91 | // Get the `const` address that this `span` starts at. 92 | [[nodiscard]] 93 | constexpr auto 94 | data() const -> T const* { 95 | return m_p_data; 96 | } 97 | 98 | // Get the number of elements owned by this `span`. 99 | [[nodiscard]] 100 | constexpr auto 101 | size() const -> idx { 102 | return m_size; 103 | } 104 | 105 | // These data members should be `public` so that this is a structural type. 106 | 107 | // This is arranged as pointer, then size, to follow the ABI of Linux 108 | // standard `iovec`s. Unfortunately, that is opposite of Windws' standard 109 | // `WSABUF`. 110 | T* m_p_data; 111 | idx m_size; 112 | }; 113 | 114 | template 115 | span(initializer_list) -> span; 116 | 117 | // Deduce the `value_type` of containers. 118 | template 119 | span(T&) -> span; 120 | 121 | // Deduce the `value_type` of containers. 122 | template 123 | span(T const&) -> span; 124 | 125 | // Create a span between two addresses. 126 | template 127 | constexpr auto 128 | make_span_between(T* p_start, T* p_end) { 129 | return span(p_start, p_end - p_start); 130 | } 131 | 132 | template 133 | using view = span const; 134 | 135 | // Simplify non-negative signed integers. 136 | template 137 | using maybe_span = maybe, 138 | [](span value) -> bool { 139 | // This span is `nullopt` if its 140 | // `.p_storage` is null. 141 | return value.m_p_data; 142 | }, 143 | // Default to a null span. 144 | span(nullptr)>>; 145 | 146 | } // namespace cat 147 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/compare_strings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | auto 6 | cat::detail::compare_strings_detail(str_view const string_1, 7 | str_view const string_2) -> bool { 8 | if (string_1.size() != string_2.size()) { 9 | return false; 10 | } 11 | 12 | // TODO: Use a type for an ISA-specific widest vector. 13 | using vector = char1x32; 14 | 15 | array vectors_1; 16 | array vectors_2; 17 | array comparisons; 18 | idx length_iterator = string_1.size(); 19 | uword vector_size = sizeof(vector); 20 | char const* p_string_1_iterator = string_1.data(); 21 | char const* p_string_2_iterator = string_2.data(); 22 | 23 | auto loop = [&](idx size) -> bool { 24 | while (length_iterator >= vector_size * size) { 25 | for (idx i; i < size; ++i) { 26 | vectors_1[i].load(string_1.data() + (i * size)); 27 | vectors_2[i].load(string_2.data() + (i * size)); 28 | comparisons[i] = (vectors_1[i] == vectors_2[i]); 29 | } 30 | 31 | for (idx i; i < size; ++i) { 32 | // If any lanes are not equal to each other: 33 | if (!comparisons[i].all_of()) { 34 | return false; 35 | } 36 | } 37 | 38 | length_iterator -= vector_size * size; 39 | p_string_1_iterator += vector_size * size; 40 | p_string_2_iterator += vector_size * size; 41 | } 42 | 43 | return true; 44 | }; 45 | 46 | // Compare four, two, then one vectors of characters at a time. 47 | if (!loop(4u)) { 48 | return false; 49 | } 50 | if (!loop(2u)) { 51 | return false; 52 | } 53 | if (!loop(1u)) { 54 | return false; 55 | } 56 | 57 | // Compare remaining characters individually. 58 | for (idx i = 0u; i < length_iterator; 59 | ++i, ++p_string_1_iterator, ++p_string_2_iterator) { 60 | if (*p_string_1_iterator != *p_string_2_iterator) { 61 | return false; 62 | } 63 | } 64 | 65 | return true; 66 | } 67 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/eprint.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | auto 5 | cat::eprint(str_view const string) -> maybe_idx { 6 | // There is no reasonable way for a `write` syscall for `nix::stderr` to 7 | // fail, except by running out of buffer space, which fails gracefully 8 | // anyways. 9 | idx const output_length = nix::sys_write(nix::stderr, string).value(); 10 | return output_length; 11 | } 12 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/eprintln.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | auto 5 | cat::eprintln(str_view const string) -> maybe_idx { 6 | // There is no reasonable way for a `write` syscall for `nix::stderr` to 7 | // fail, except by running out of buffer space, which fails gracefully 8 | // anyways. 9 | idx output_length = nix::sys_write(nix::stderr, string).value(); 10 | output_length += nix::sys_write(nix::stderr, "\n").value(); 11 | return output_length; 12 | } 13 | 14 | auto 15 | cat::eprintln() -> maybe_idx { 16 | return nix::sys_write(nix::stderr, "\n").value(); 17 | } 18 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/memcpy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // `__SIZE_TYPE__` is a GCC macro. 4 | extern "C" auto 5 | std::memcpy(void* p_destination, void const* p_source, __SIZE_TYPE__ bytes) 6 | -> void* { 7 | cat::copy_memory(p_source, p_destination, bytes); 8 | return p_destination; 9 | } 10 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/memset.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" [[clang::no_builtin]] 4 | auto 5 | std::memset(void* p_source, int byte_value, __SIZE_TYPE__ bytes) -> void* { 6 | #ifdef __clang__ 7 | // TODO: Clang inserts `memset()` calls as some structs' `default` 8 | // assignment operators. The vectorized `set_memory` causes some 9 | // problems with that, so this trivial loop is done insead. Find a 10 | // better solution. 11 | for (unsigned long long i = 0; i < bytes; ++i) { 12 | *(static_cast(p_source) + i) = 13 | static_cast(byte_value); 14 | } 15 | #else 16 | cat::set_memory(p_source, static_cast(byte_value), bytes); 17 | #endif 18 | return p_source; 19 | } 20 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/print.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | auto 5 | cat::print(str_view const string) -> maybe_idx { 6 | // The only way this function can fail is either `sys_write()` returns 7 | // `nix::linux_error::nospc`, or it prints fewer characters than intended. 8 | idx length = prop_as(nix::sys_write(nix::stdout, string), nullopt); 9 | if (length < string.size()) { 10 | return nullopt; 11 | } 12 | return length; 13 | } 14 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/println.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | auto 5 | cat::println(str_view string) -> maybe_idx { 6 | // There is no reasonable way for a `write` syscall for `nix::stdout` to 7 | // fail, except by running out of buffer space, which fails gracefully 8 | // anyways. 9 | idx length = nix::sys_write(nix::stdout, string).value(); 10 | length += nix::sys_write(nix::stdout, "\n").value(); 11 | if (length < string.size() + 1) { 12 | return nullopt; 13 | } 14 | return length; 15 | } 16 | 17 | auto 18 | cat::println() -> maybe_idx { 19 | idx length = nix::sys_write(nix::stdout, "\n").value(); 20 | if (length == 0) { 21 | return nullopt; 22 | } 23 | return length; 24 | } 25 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/string_length.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | // This function requires SSE4.2, unless it is used in a `constexpr` context. 10 | constexpr auto 11 | cat::string_length(char const* p_string) -> idx { 12 | if consteval { 13 | idx result; 14 | while (true) { 15 | if (p_string[result.raw] == '\0') { 16 | return result; 17 | } 18 | result++; 19 | } 20 | } else { 21 | // TODO: Implement with portable SIMD, and tune performance. 22 | constexpr char1x16 zeros = '\0'; 23 | 24 | for (idx i;; i += 16u) { 25 | // TODO: `.raw` should not be necessary here. 26 | char1x16 const data = char1x16::loaded(p_string + i.raw); 27 | constexpr x64::string_control mask = 28 | x64::string_control::unsigned_byte 29 | | x64::string_control::compare_equal_each 30 | | x64::string_control::least_significant; 31 | 32 | // If there are one or more `0` bytes in `data`: 33 | if (x64::compare_implicit_length_strings(data, zeros)) { 34 | int4 const index = 35 | x64::compare_implicit_length_strings_return_index(data, 36 | zeros); 37 | // Adding `1` is required to count the null terminator. 38 | return idx(i + index); 39 | } 40 | } 41 | 42 | __builtin_unreachable(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/libraries/string/implementations/strlen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | [[deprecated("strlen() is deprecated! Use cat::string_length() instead.")]] 4 | auto strlen(char const* p_string) -> __SIZE_TYPE__ { 5 | return static_cast<__SIZE_TYPE__>(cat::string_length(p_string)); 6 | } 7 | -------------------------------------------------------------------------------- /src/libraries/thread/cat/thread: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace cat { 9 | 10 | struct thread { 11 | public: 12 | thread() = default; 13 | thread(thread const&) = delete; 14 | 15 | // TODO: Default or delete a move constructor and assignment operator? 16 | 17 | template 18 | auto 19 | spawn(is_allocator auto& allocator, idx initial_stack_size, 20 | idx thread_local_buffer_size, is_invocable auto&& function, 21 | Args&&... arguments) -> maybe { 22 | scaredy result = 23 | m_handle.spawn(allocator, initial_stack_size, thread_local_buffer_size, 24 | fwd(function), fwd(arguments)...); 25 | if (result.has_value()) { 26 | return monostate; 27 | } 28 | return nullopt; 29 | } 30 | 31 | [[nodiscard]] 32 | auto 33 | join() const -> maybe { 34 | scaredy result = m_handle.wait(); 35 | if (result.has_value()) { 36 | return monostate; 37 | } 38 | return nullopt; 39 | } 40 | 41 | private: 42 | // This is platform-specific hidden code. 43 | [[maybe_unused]] 44 | nix::process m_handle; 45 | }; 46 | 47 | inline void 48 | relax_cpu() { 49 | asm volatile("pause" :: 50 | : "memory"); 51 | } 52 | 53 | } // namespace cat 54 | -------------------------------------------------------------------------------- /src/libraries/tuple/implementations/tuple_cat.tpp: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace cat { 9 | 10 | namespace detail { 11 | template 12 | constexpr auto 13 | get_outer_type_list(type_map) { 14 | return (type_list_filled, 15 | // `ssizeof_pack` causes an internal compiler 16 | // error in GCC 12 here. 17 | // ssizeof_pack()> 18 | idx(sizeof...(outer_tuple_elements))>{} 19 | + ...); 20 | } 21 | 22 | template 23 | constexpr auto 24 | get_inner_type_list(type_map) { 25 | // Make a `type_list` from every `tuple_element`, and concatenate the 26 | // lists together. 27 | return (type_list>{} + ...); 28 | } 29 | 30 | // This takes a forwarding tuple as a parameter. The forwarding tuple only 31 | // contains references, so it should just be taken by value. 32 | template 34 | constexpr auto 35 | tuple_cat_detail(outer_tuple&& tuple, type_list, 36 | type_list) 37 | -> cat::tuple { 38 | return {// For every tuple passed in through `outer_tuple`, move the n'th 39 | // tuple's element storage into a new `tuple` at the same index. 40 | move(tuple.template Identity::storage) 41 | .template Identity::storage...}; 42 | } 43 | } // namespace detail 44 | 45 | template 46 | constexpr auto 47 | tuple_cat(types&&... tuples) { 48 | if constexpr (sizeof...(types) == 0) { 49 | return tuple<>{}; 50 | } else { 51 | // Create a `tuple` out of all the argument tuples. 52 | using outer_tuple = tuple...>; 53 | using outer_tuple_type_map = outer_tuple::Map; 54 | 55 | // Get the `type_list` from the inner and outer tuples. 56 | constexpr type_list outer_elements_list = 57 | detail::get_outer_type_list(outer_tuple_type_map()); 58 | constexpr type_list inner_elements_list = 59 | detail::get_inner_type_list(outer_tuple_type_map()); 60 | 61 | return detail::tuple_cat_detail(outer_tuple(move(tuples)...), 62 | outer_elements_list, inner_elements_list); 63 | } 64 | } 65 | 66 | } // namespace cat 67 | -------------------------------------------------------------------------------- /src/libraries/utility/cat/utility: -------------------------------------------------------------------------------- 1 | // -*- mode: c++ -*- 2 | // vim: set ft=cpp: 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace cat { 8 | 9 | template 10 | constexpr auto 11 | unconst(T const& value) -> T& { 12 | return const_cast(value); 13 | } 14 | 15 | template 16 | constexpr auto 17 | unconst(T const* p_value) -> T* { 18 | return const_cast(p_value); 19 | } 20 | 21 | // Infer the length of a parameter pack from a function argument. 22 | template 23 | constexpr auto 24 | ssizeof_pack(types...) -> iword { 25 | return make_signed(sizeof...(types)); 26 | } 27 | 28 | // Specify the length of a parameter pack in a template argument. 29 | template 30 | constexpr auto 31 | ssizeof_pack() -> iword { 32 | return make_signed(sizeof...(types)); 33 | } 34 | 35 | constexpr auto 36 | salignof(auto const& anything) -> iword { 37 | return iword{alignof(anything)}; 38 | } 39 | 40 | template 41 | constexpr auto 42 | salignof() -> iword { 43 | return iword{alignof(T)}; 44 | } 45 | 46 | namespace detail { 47 | template 48 | constexpr auto 49 | get_value_list_at() -> T { 50 | if constexpr (current == index) { 51 | return value; 52 | } else { 53 | return get_value_list_at(); 54 | } 55 | } 56 | } // namespace detail 57 | 58 | template 59 | struct value_type_list { 60 | using value_type = T; 61 | 62 | static constexpr idx size = sizeof...(values); 63 | 64 | template 65 | static constexpr auto 66 | get() -> T { 67 | return detail::get_value_list_at(); 68 | } 69 | }; 70 | 71 | namespace detail { 72 | template 73 | constexpr auto 74 | make_value_list() { 75 | if constexpr (count > 0) { 76 | // Recurse, appending `value` to `integers`. 77 | return make_value_list(); 78 | } else { 79 | return value_type_list(); 80 | } 81 | } 82 | 83 | // TODO: Look into `__integer_pack()`. 84 | template 85 | constexpr auto 86 | make_integer_sequence() { 87 | if constexpr (count > 0) { 88 | // Recurse, appending `count - 1` to `integers`. 89 | return make_integer_sequence(); 91 | } else { 92 | return value_type_list(); 93 | } 94 | } 95 | } // namespace detail 96 | 97 | // Make a `value_type_list` for any `T`, including non-integer types. 98 | template 99 | constexpr auto value_list = detail::make_value_list(); 100 | 101 | template 102 | using make_value_type_list = decltype(value_list); 103 | 104 | template 105 | constexpr auto integer_sequence = 106 | #if __has_builtin(__integer_pack) 107 | // TODO: Get `__integer_pack` working for GCC. 108 | detail::make_integer_sequence(); 109 | // value_type_list(); 110 | #else 111 | // TODO: Support `__make_integer_seq` for Clang. 112 | detail::make_integer_sequence(); 113 | #endif 114 | 115 | template 116 | constexpr auto zeros_list = value_list; 117 | 118 | template 119 | constexpr auto ones_list = value_list; 120 | 121 | template 122 | using make_integer_sequence = remove_cv)>; 123 | 124 | // TODO: What is this? 125 | template 126 | using index_list_type = value_type_list; 127 | 128 | template 129 | constexpr auto index_list = value_list; 130 | 131 | template 132 | using make_index_list = make_value_type_list; 133 | 134 | template 135 | constexpr auto index_sequence = integer_sequence; 136 | 137 | template 138 | using make_index_sequence = make_integer_sequence; 139 | 140 | template 141 | using index_sequence_over_types = make_index_sequence; 142 | 143 | template 144 | using index_sequence_over_values = make_index_sequence; 145 | 146 | template 147 | requires(is_enum) 148 | constexpr auto 149 | to_underlying(T any_enum) { 150 | return static_cast>(any_enum); 151 | } 152 | 153 | // TODO: Support `cat::ref`, to enable `variant` and `tuple` to hold references 154 | // more easily. 155 | 156 | template 157 | [[nodiscard, gnu::nodebug]] 158 | constexpr auto 159 | as_const(T& value) -> cat::add_const& { 160 | return value; 161 | } 162 | 163 | template 164 | void 165 | as_const(T const&&) = delete; 166 | 167 | } // namespace cat 168 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Link `.gdbinit` to build directories. 2 | 3 | # `Ninja Multi-Config` generator: 4 | if(CMAKE_CONFIGURATION_TYPES) 5 | add_custom_target( 6 | cat-gdb-tests ALL 7 | COMMAND ${CMAKE_COMMAND} -E create_symlink 8 | ${PROJECT_SOURCE_DIR}/.gdbinit ${CMAKE_CURRENT_BINARY_DIR}/$/.gdbinit 9 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/$ 10 | ) 11 | add_dependencies(cat cat-gdb-tests) 12 | endif() # For `Ninja` generator, this is done by `cat-gdb-examples`. 13 | 14 | add_library(cat-tests INTERFACE) # Do not produce a `.so` or `.a` artifact. 15 | target_link_libraries(cat-tests INTERFACE cat) 16 | 17 | list( 18 | APPEND 19 | CAT_COMPILE_OPTIONS_TEST 20 | ${CAT_COMPILE_OPTIONS} 21 | 22 | # Tests should fail if they produce sanitizer warnings. 23 | # TODO: Re-enable this when threads consistently work. 24 | # -fno-sanitize-recover=all 25 | # Tests should be allowed to have unused variables. 26 | -Wno-unused-variable 27 | $<$:-Wno-self-assign-overloaded> 28 | ) 29 | 30 | # Toggle to disable compiling unit tests. 31 | option(CAT_BUILD_UNIT_TESTS "Compile all unit tests." ON) 32 | 33 | if(CAT_BUILD_UNIT_TESTS) 34 | # Include `tests/unit_tests.hpp`. 35 | target_include_directories(cat-tests INTERFACE ${CMAKE_SOURCE_DIR}/tests/) 36 | 37 | # Add unit test implementation files. 38 | # Specifying each source file individually makes disabling tests convenient. 39 | target_sources( 40 | cat-tests INTERFACE 41 | ${CMAKE_SOURCE_DIR}/tests/src/test_meta.cpp 42 | ${CMAKE_SOURCE_DIR}/tests/src/test_arithmetic.cpp 43 | #${CMAKE_SOURCE_DIR}/tests/src/test_alloc.cpp 44 | ${CMAKE_SOURCE_DIR}/tests/src/test_arrays.cpp 45 | ${CMAKE_SOURCE_DIR}/tests/src/test_compare_strings.cpp 46 | ${CMAKE_SOURCE_DIR}/tests/src/test_string_length.cpp 47 | ${CMAKE_SOURCE_DIR}/tests/src/test_format_strings.cpp 48 | ${CMAKE_SOURCE_DIR}/tests/src/test_linear_allocator.cpp 49 | ${CMAKE_SOURCE_DIR}/tests/src/test_pool_allocator.cpp 50 | ${CMAKE_SOURCE_DIR}/tests/src/test_list.cpp 51 | ${CMAKE_SOURCE_DIR}/tests/src/test_math.cpp 52 | ${CMAKE_SOURCE_DIR}/tests/src/test_maybe.cpp 53 | ${CMAKE_SOURCE_DIR}/tests/src/test_paging_memory.cpp 54 | # ${CMAKE_SOURCE_DIR}/tests/src/test_raii.cpp 55 | ${CMAKE_SOURCE_DIR}/tests/src/test_typelist.cpp 56 | ${CMAKE_SOURCE_DIR}/tests/src/test_scaredy.cpp 57 | ${CMAKE_SOURCE_DIR}/tests/src/test_set_memory.cpp 58 | ${CMAKE_SOURCE_DIR}/tests/src/test_simd.cpp 59 | ${CMAKE_SOURCE_DIR}/tests/src/test_tuple.cpp 60 | ${CMAKE_SOURCE_DIR}/tests/src/test_variant.cpp 61 | ${CMAKE_SOURCE_DIR}/tests/src/test_vec.cpp 62 | # ${CMAKE_SOURCE_DIR}/tests/src/test_ring.cpp 63 | ${CMAKE_SOURCE_DIR}/tests/src/test_invoke.cpp 64 | ${CMAKE_SOURCE_DIR}/tests/src/test_cast.cpp 65 | ${CMAKE_SOURCE_DIR}/tests/src/test_bit.cpp 66 | ${CMAKE_SOURCE_DIR}/tests/src/test_bitset.cpp 67 | ${CMAKE_SOURCE_DIR}/tests/src/test_stringify.cpp 68 | ${CMAKE_SOURCE_DIR}/tests/src/test_cpuid.cpp 69 | ${CMAKE_SOURCE_DIR}/tests/src/test_thread.cpp 70 | ) 71 | 72 | add_executable(unit_tests unit_tests.cpp) 73 | 74 | target_compile_options(unit_tests PRIVATE ${CAT_COMPILE_OPTIONS_TEST}) 75 | target_compile_definitions(unit_tests PRIVATE "NO_ARGC_ARGV") 76 | target_link_libraries(unit_tests PRIVATE cat-tests) 77 | target_link_options(unit_tests PRIVATE ${CAT_LINK_OPTIONS}) 78 | 79 | # Enable running `unit_tests` through `ctest`. 80 | add_test(NAME UnitTests COMMAND unit_tests) 81 | else() 82 | # Build something so that symlinking `.gdbinit` does not fail. 83 | add_executable(test_dummy ${CMAKE_SOURCE_DIR}/tests/src/test_dummy.cpp) 84 | endif() 85 | -------------------------------------------------------------------------------- /tests/src/test_arrays.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../unit_tests.hpp" 8 | 9 | struct move_only { 10 | int i = 0; 11 | move_only() = default; 12 | move_only(move_only const&) = delete; 13 | 14 | move_only(move_only&& other) : i(other.i) { 15 | } 16 | }; 17 | 18 | test(arrays) { 19 | // TODO: This passes in GCC 15 but not in Clang 19. 20 | #ifndef __clang__ 21 | static_assert(cat::is_trivial>); 22 | #endif 23 | 24 | // List-initializing a array: 25 | cat::array array_1{0, 1, 2, 3, 4}; 26 | // List-assigning a array: 27 | array_1 = {5, 6, 7, 8, 9}; 28 | // Default initializing a array: 29 | cat::array array_2; 30 | 31 | // Move constructing a array: 32 | [[maybe_unused]] 33 | cat::array array_move_only = {move_only()}; 34 | // Move assigning a array: 35 | [[maybe_unused]] 36 | cat::array array_3 = mov array_move_only; 37 | 38 | // `const` array. 39 | cat::array const array_const = {0, 1, 2}; 40 | [[maybe_unused]] 41 | int4 const_val = array_const.at(1).or_exit(); 42 | 43 | // Repeat those tests in a constexpr context. 44 | [] consteval { 45 | cat::array const_array_1{}; 46 | cat::array const_array_2 = {1}; 47 | const_array_2 = const_array_1; 48 | }(); 49 | 50 | // Test that the array is iterable. 51 | idx count; 52 | for (int& a : array_1) { 53 | cat::verify(a == array_1[count]); 54 | ++count; 55 | } 56 | 57 | for (int& a : cat::as_reverse(array_1)) { 58 | --count; 59 | cat::verify(a == array_1[count]); 60 | } 61 | 62 | cat::verify(array_1.front() == 5); 63 | cat::verify(array_1.back() == 9); 64 | 65 | count = 0u; 66 | for (int const& a : cat::as_const(array_1)) { 67 | cat::verify(a == array_1[count]); 68 | ++count; 69 | } 70 | auto _ = array_1.cbegin(); 71 | 72 | for (int const& a : cat::as_const_reverse(array_1)) { 73 | --count; 74 | cat::verify(a == array_1[count]); 75 | } 76 | 77 | int4 array_to = *(array_1.begin().advance_to(--array_1.end())); 78 | cat::verify(array_to == array_1.back()); 79 | 80 | // Index in and out of bounds. 81 | cat::verify(array_1.at(0).value() == 5); 82 | cat::verify(!array_1.at(6).has_value()); 83 | 84 | // Deducing type. 85 | cat::array implicit_array_1 = {0, 1, 2, 3, 4}; 86 | cat::array implicit_array_2{0, 1, 2, 3, 4}; 87 | cat::array implicit_array_3(0, 1, 2, 3, 4); 88 | static_assert(implicit_array_1.size() == 5u); 89 | static_assert(implicit_array_1.capacity() == 5); 90 | auto _ = implicit_array_1.capacity(); 91 | static_assert(implicit_array_2.size() == 5); 92 | static_assert(implicit_array_3.size() == 5); 93 | 94 | // Max elements. 95 | // constexpr cat::array array_4 = {0, 2, 8, 5}; 96 | // constexpr int4 max_1 = cat::max(array_4); 97 | // cat::verify(max_1 == 8); 98 | 99 | // int4 min_1 = cat::min(array_4); 100 | // cat::verify(min_1 == 0); 101 | 102 | // TODO: string deduction: 103 | // cat::array implicit_string = "Hi, Conscat!"; 104 | // static_assert(implicit_string.size() == 105 | // cat::string_length("Hi, Conscat!")); 106 | 107 | // TODO: Test `constexpr`. 108 | 109 | // Slicing array. 110 | cat::span span = array_1; 111 | span = array_1.first(1u); 112 | auto _ = array_1.subspan(0u, 2u); 113 | auto _ = array_1.last(2u); 114 | 115 | cat::span const span_const = array_1.first(1u); 116 | auto _ = array_const.subspan(0u, 2u); 117 | auto _ = array_const.last(2u); 118 | 119 | // Test array copy-assignment. 120 | cat::array base_array = {0_i4, 0, 0, 0}; 121 | cat::array copy_array = {1, 2, 3, 4}; 122 | cat::array copy_converting_array = {int2{1}, 2, 3, 4}; 123 | base_array = copy_array; 124 | base_array = copy_converting_array; 125 | // TODO: Test these properly. 126 | // cat::array move_only_array = {5, 6, 7, 8}; 127 | // cat::array move_converting_array = {int2{5}, 6, 7, 8}; 128 | // base_array = move(move_array); 129 | // base_array = move(move_converting_array); 130 | 131 | // Test array fill. 132 | cat::array filled_array = cat::make_array_filled<8>(6_i4); 133 | for (idx i; i < 8; ++i) { 134 | cat::verify(filled_array[i] == 6); 135 | } 136 | 137 | filled_array.fill(9); 138 | for (idx i; i < 8; ++i) { 139 | cat::verify(filled_array[i] == 9); 140 | } 141 | 142 | // Test list initialization. 143 | cat::array from_array{1, 2, 3}; 144 | cat::verify(from_array[0] == 1); 145 | cat::verify(from_array[1] == 2); 146 | cat::verify(from_array[2] == 3); 147 | 148 | cat::array huge_array{1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3}; 149 | } 150 | -------------------------------------------------------------------------------- /tests/src/test_cast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | struct test_cast1 { 6 | char storage[1]; 7 | }; 8 | 9 | struct test_cast2 { 10 | char storage[2]; 11 | }; 12 | 13 | struct test_cast4 { 14 | char storage[4]; 15 | }; 16 | 17 | struct test_cast8 { 18 | char storage[8]; 19 | }; 20 | 21 | test(cast) { 22 | static_assert(cat::is_same); 23 | static_assert(cat::is_same); 24 | static_assert(cat::is_same); 25 | static_assert(cat::is_same); 26 | // static_assert(cat::is_same){})), 28 | // int8 const>); 29 | } 30 | -------------------------------------------------------------------------------- /tests/src/test_compare_strings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | test(compare_strings) { 6 | char const* p_string_1 = "Hello!"; 7 | char const* const p_string_2 = "Hello!"; 8 | 9 | cat::str_view string_1 = "Hello!"; 10 | cat::str_view const string_2 = "Hello!"; 11 | cat::str_view string_3 = "Goodbye!"; 12 | 13 | cat::str_view long_string_1 = 14 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 15 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 16 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 17 | cat::str_view long_string_2 = 18 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 19 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 20 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 21 | 22 | // Test a succesful string pointer case. 23 | cat::verify(cat::compare_strings(p_string_1, p_string_2)); 24 | 25 | // Test a succesful string case. 26 | cat::verify(cat::compare_strings(string_1, string_2)); 27 | 28 | // Test a succesful large string case. 29 | cat::verify(cat::compare_strings(long_string_1, long_string_2)); 30 | 31 | // Test a failure case. 32 | cat::verify(!cat::compare_strings(string_1, string_3)); 33 | 34 | [[maybe_unused]] 35 | cat::str_view const_string_1 = "Hello, "; 36 | [[maybe_unused]] 37 | constexpr cat::str_view const_string_2 = "world!"; 38 | 39 | // Fixed length strings. 40 | constexpr cat::str_inplace const_string_3 = "Hello, "; 41 | constexpr cat::str_inplace const_string_4 = "world!"; 42 | 43 | // Test collection operations. 44 | auto _ = const_string_1[1]; 45 | cat::verify(!const_string_3.at(10).has_value()); 46 | 47 | // TODO: Make this `constexpr`. 48 | cat::str_inplace hello_world = (const_string_3 + const_string_4); 49 | constexpr cat::str_inplace const_hello_world = 50 | (const_string_3 + const_string_4); 51 | 52 | constexpr cat::str_view const_hello_world_2 = "Hello, world!"; 53 | constexpr cat::str_view const_hello_world_3 = const_hello_world_2; 54 | constexpr cat::str_view const_hello_world_4 = const_hello_world_3; 55 | constexpr cat::str_inplace<13> const_hello_world_5 = 56 | cat::str_inplace("Hello, ") + "world!"; 57 | constexpr cat::zstr_inplace<14> const_hello_world_6 = 58 | cat::make_zstr_inplace<8>("Hello, ") + "world!"; 59 | constexpr cat::str_inplace const_hello_world_7 = 60 | "Hello, " + cat::str_inplace("world!"); 61 | cat::zstr_inplace<14> const_hello_world_8 = 62 | "Hello, " + cat::make_zstr_inplace<7>("world!"); 63 | const_hello_world_8 = "Hello, world!"; 64 | static_assert(const_hello_world_5 == "Hello, world!"); 65 | static_assert(const_hello_world_6 == "Hello, world!"); 66 | 67 | cat::verify(cat::compare_strings(hello_world, "Hello, world!")); 68 | cat::verify(cat::compare_strings(const_hello_world, "Hello, world!")); 69 | cat::verify(cat::compare_strings(const_hello_world_2, "Hello, world!")); 70 | cat::verify(cat::compare_strings(const_hello_world_3, "Hello, world!")); 71 | 72 | // TODO: Bind a `str_view` to `zstr` containers. 73 | // TODO: Pass `zstr` containers into `cat::compare_strings()`. 74 | 75 | iword const h = const_string_1.find('H').value(); 76 | iword const e = const_string_1.find('e').value(); 77 | iword const l = const_string_1.find('l').value(); 78 | iword const o = const_string_1.find('o').value(); 79 | cat::verify(h == 0); 80 | cat::verify(e == 1); 81 | cat::verify(l == 2); 82 | cat::verify(o == 4); 83 | 84 | // Compare single-character in-place strings to a `char`. 85 | cat::str_inplace char_str = "X"; 86 | cat::verify(char_str == 'X'); 87 | 88 | cat::zstr_inplace char_zstr = cat::make_zstr_inplace<2u>("X"); 89 | cat::verify(char_zstr == 'X'); 90 | } 91 | -------------------------------------------------------------------------------- /tests/src/test_copy_memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | auto 6 | main() -> int { 7 | cat::array source_200; 8 | for (int4 i = 0; i < 200; ++i) { 9 | source_200[i] = i; 10 | } 11 | 12 | cat::array dest_200; 13 | cat::copy_memory(&dest_200, &source_200, ssizeof(dest_200)); 14 | for (int4 i = 0; i < 200; ++i) { 15 | cat::verify(source_200[i] == dest_200[i]); 16 | } 17 | 18 | cat::array source_2000; 19 | for (int4 i = 0; i < 2'000; ++i) { 20 | source_2000[i] = i; 21 | } 22 | 23 | cat::array dest_2000; 24 | for (int4 i = 0; i < 2'000; ++i) { 25 | dest_2000[i] = 0; 26 | } 27 | 28 | cat::copy_memory(&source_2000, &dest_2000, ssizeof(dest_2000)); 29 | for (int4 i = 0; i < 2'000; ++i) { 30 | cat::verify(source_2000[i] == dest_2000[i]); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /tests/src/test_cpuid.cpp: -------------------------------------------------------------------------------- 1 | #include "../unit_tests.hpp" 2 | 3 | test(cpuid) { 4 | cat::verify(__builtin_cpu_supports("cmov")); 5 | cat::verify(__builtin_cpu_supports("mmx")); 6 | cat::verify(__builtin_cpu_supports("sse")); 7 | cat::verify(__builtin_cpu_supports("sse2")); 8 | 9 | cat::verify(__builtin_cpu_supports("avx2")); 10 | 11 | cat::verify(__builtin_cpu_is("skylake")); 12 | 13 | cat::verify(__builtin_cpu_supports("x86-64")); 14 | cat::verify(__builtin_cpu_supports("x86-64-v2")); 15 | cat::verify(__builtin_cpu_supports("x86-64-v3")); 16 | } 17 | -------------------------------------------------------------------------------- /tests/src/test_dummy.cpp: -------------------------------------------------------------------------------- 1 | auto 2 | main() -> int {}; 3 | -------------------------------------------------------------------------------- /tests/src/test_format_strings.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../unit_tests.hpp" 6 | 7 | test(format_strings) { 8 | // Initialize an allocator. 9 | cat::page_allocator pager; 10 | cat::span page = pager.alloc_multi(4_uki).or_exit(); 11 | defer { 12 | pager.free(page); 13 | }; 14 | auto allocator = make_linear_allocator(page); 15 | 16 | // Test `int4` conversion. 17 | cat::str_view int_string = cat::to_chars(allocator, 10).or_exit(); 18 | cat::verify(cat::compare_strings(int_string, "10")); 19 | cat::verify(int_string.size() == 2); 20 | 21 | // TODO: `constexpr` string comparison. 22 | // TODO: Test out of memory error handling. 23 | // TODO: Test formatting maximum value of integers. 24 | // TODO: Test `int1`, `uint1`, `int2`, `uint2`, `uint4`, `int8`, and 25 | // `uint8`. 26 | 27 | // TOOD: These stopped working for some reason. 28 | // constexpr cat::str_inplace const_int = cat::to_chars<136>(); 29 | // constexpr cat::str_inplace const_negative = cat::to_chars<-1650>(); 30 | 31 | // cat::verify(cat::compare_strings(const_int.data(), "136")); 32 | // cat::verify(cat::compare_strings(const_negative.data(), "-1650")); 33 | 34 | // Test formatting `int`. 35 | allocator.reset(); 36 | cat::str_view formatted_string_int = 37 | cat::fmt(allocator, "bb{}aa{}cc", 52, 130).or_exit(); 38 | // TODO: `formatted_string_int` has an incorrect `.size()`, but the content 39 | // is correct. 40 | cat::verify(cat::compare_strings(formatted_string_int, "bb52aa130cc")); 41 | // cat::println(formatted_string_int); 42 | 43 | // Test formatting `float`. 44 | allocator.reset(); 45 | cat::str_view string_float = cat::to_chars(allocator, 1.234f).or_exit(); 46 | cat::verify(cat::compare_strings(string_float.data(), "1.234E0"), 47 | string_float); 48 | // cat::println(string_float); 49 | 50 | cat::str_view formatted_string_float = 51 | cat::fmt(allocator, "a{}b", 1.234f).or_exit(); 52 | cat::verify(cat::compare_strings(formatted_string_float, "a1.234E0b")); 53 | // cat::println(formatted_string_float); 54 | 55 | cat::str_view formatted_string_double = 56 | cat::fmt(allocator, "a{}b", 1.234).or_exit(); 57 | cat::verify(cat::compare_strings(formatted_string_double, "a1.234E0b")); 58 | // cat::println(formatted_string_double); 59 | 60 | // Test `cat::to_string_at()`. 61 | cat::array array; 62 | cat::span array_span{array.data(), array.size()}; 63 | 64 | // TODO: This segfaults with optimizations enabled in GCC 13. 65 | // cat::string string_int_13 = 66 | // cat::to_string_at(int4{13}, array_span).verify(); 67 | // cat::verify(string_int_13.size() == 4); 68 | // cat::verify(cat::compare_strings(string_int_13.data(), "13")); 69 | 70 | // TODO: These stopped working for some reason. 71 | 72 | // cat::string string_neg_13 = 73 | // cat::to_string_at(int4{-13}, array_span).verify(); 74 | // cat::verify(string_neg_13.size() == 4); 75 | // cat::verify(cat::compare_strings(string_neg_13.data(), "-13")); 76 | 77 | // Test `cat::to_string_at()` in a `constexpr` context. 78 | // auto make_hi_in_const = [](int4 value) constexpr->cat::string { 79 | // cat::array array{}; 80 | // cat::span array_span{array.data(), array.size()}; 81 | // auto _ = cat::to_string_at(value, array_span).value(); 82 | // return "Hi"; 83 | // }; 84 | // [[maybe_unused]] constexpr auto hi = make_hi_in_const(1); 85 | } 86 | -------------------------------------------------------------------------------- /tests/src/test_invoke.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | void 6 | invoke_fn_void() { 7 | } 8 | 9 | auto 10 | invoke_fn_int() -> int { 11 | return 1; 12 | } 13 | 14 | test(invoke) { 15 | // TODO: This does not compile yet. 16 | // auto _ = cat::invoke(&invoke_fn_void); 17 | } 18 | -------------------------------------------------------------------------------- /tests/src/test_linear_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../unit_tests.hpp" 8 | 9 | test(linear_allocator) { 10 | // Initialize an allocator. 11 | cat::page_allocator pager; 12 | cat::span page = pager.alloc_multi(24u).verify(); 13 | defer { 14 | pager.free(page); 15 | }; 16 | cat::is_allocator auto allocator = cat::make_linear_allocator(page); 17 | 18 | // It should not be possible to allocate 7 times here, because 24 bytes 19 | // can only hold 6 `int4`s. 20 | for (int i = 0; i < 7; ++i) { 21 | cat::maybe handle = allocator.alloc(); 22 | if (!handle.has_value()) { 23 | cat::verify(i == 6); 24 | goto overallocated; 25 | } 26 | } 27 | cat::verify(false); 28 | 29 | overallocated: 30 | // Invalidate all memory handles, and allocate again. 31 | allocator.reset(); 32 | for (int4 i = 0; i < 4; ++i) { 33 | cat::maybe handle = allocator.alloc(); 34 | cat::verify(handle.has_value()); 35 | } 36 | // This allocated 16 bytes, which is 8-byte-aligned. Another int allocation 37 | // would make it 4-byte-aligned. However, 8 bytes should be allocated here 38 | // to keep it 8-byte-aligned. 39 | auto* p_handle = allocator.align_alloc(8u).value(); 40 | cat::verify(cat::is_aligned(p_handle, 8u)); 41 | 42 | // Allocate another int. 43 | auto* p_handle_2 = allocator.alloc().value(); 44 | cat::verify(cat::is_aligned(p_handle_2, 4u)); 45 | // This is now 4-byte-aligned. 46 | cat::verify(!cat::is_aligned(p_handle_2, 8u)); 47 | 48 | // Small size allocations shouldn't bump the allocator. 49 | for (int4 i = 0; i < 20; ++i) { 50 | auto memory = allocator.inline_alloc(); 51 | cat::verify(memory.has_value()); 52 | } 53 | cat::maybe handle_3 = allocator.alloc(); 54 | cat::verify(handle_3.has_value()); 55 | 56 | // Test that allocations are reusable. 57 | allocator.reset(); 58 | decltype(allocator.alloc()) handles[4]; 59 | for (signed char i = 0; i < 4; ++i) { 60 | handles[i] = allocator.alloc(); 61 | cat::verify(handles[i].has_value()); 62 | *(handles[i].value()) = i; 63 | } 64 | for (signed char i = 0; i < 4; ++i) { 65 | cat::verify(allocator.get(handles[i].value()) == i); 66 | } 67 | 68 | // Test that allocations have pointer stability. 69 | allocator.reset(); 70 | int4* p_handles[4]; 71 | for (int i = 0; i < 4; ++i) { 72 | int4* p_handle = allocator.alloc().or_exit(); 73 | *p_handle = i; 74 | p_handles[i] = p_handle; 75 | } 76 | for (int i = 0; i < 4; ++i) { 77 | cat::verify(*(p_handles[i]) == i); 78 | allocator.free(p_handles[i]); 79 | } 80 | 81 | allocator.reset(); 82 | // Test allocation constructors. 83 | int4* p_initialized = allocator.alloc(100).or_exit(); 84 | cat::verify(*p_initialized == 100); 85 | 86 | // Test sized allocations. 87 | allocator.reset(); 88 | auto _ = allocator.alloc().or_exit(); 89 | // Because the allocator is now 2 byte aligned, an extra 2 bytes have to be 90 | // reserved to allocate a 4-byte aligned value: 91 | cat::verify(allocator.nalloc().or_exit() == 6u); 92 | 93 | // TODO: Test multi allocations. 94 | // TODO: Test inline multi allocations. 95 | } 96 | -------------------------------------------------------------------------------- /tests/src/test_math.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | #include "cat/debug" 5 | 6 | test(math) { 7 | using namespace cat::arithmetic_literals; 8 | 9 | // Test `min()`. 10 | cat::verify(cat::min(0) == 0); 11 | cat::verify(cat::min(0u) == 0u); 12 | cat::verify(cat::min(0.f) == 0.f); 13 | cat::verify(cat::min(0.) == 0.); 14 | 15 | cat::verify(cat::min(0, 1) == 0); 16 | cat::verify(cat::min(0u, 1u) == 0u); 17 | cat::verify(cat::min(0.f, 1.f) == 0.f); 18 | cat::verify(cat::min(0., 1.) == 0.); 19 | 20 | cat::verify(cat::min(0, 1, 2) == 0); 21 | cat::verify(cat::min(0u, 1u, 2u) == 0u); 22 | cat::verify(cat::min(0.f, 1.f, 2.f) == 0.f); 23 | cat::verify(cat::min(0., 1., 2.) == 0.); 24 | 25 | // Test `max()`. 26 | cat::verify(cat::max(0) == 0); 27 | cat::verify(cat::max(0u) == 0u); 28 | cat::verify(cat::max(0.f) == 0.f); 29 | cat::verify(cat::max(0.) == 0.); 30 | 31 | cat::verify(cat::max(0, 1) == 1); 32 | cat::verify(cat::max(0u, 1u) == 1u); 33 | cat::verify(cat::max(0.f, 1.f) == 1.f); 34 | cat::verify(cat::max(0., 1.) == 1.); 35 | 36 | cat::verify(cat::max(0, 1, 2) == 2); 37 | cat::verify(cat::max(0u, 1u, 2u) == 2u); 38 | cat::verify(cat::max(0.f, 1.f, 2.f) == 2.f); 39 | cat::verify(cat::max(0., 1., 2.) == 2.); 40 | 41 | // Test `abs()`. 42 | cat::verify(cat::abs(1) == 1); 43 | cat::verify(cat::abs(1_i1) == 1_i1); 44 | cat::verify(cat::abs(1_i2) == 1_i2); 45 | cat::verify(cat::abs(1_i4) == 1_i4); 46 | cat::verify(cat::abs(1_i8) == 1_i8); 47 | 48 | cat::verify(cat::abs(1.f) == 1.f); 49 | cat::verify(cat::abs(1.) == 1.); 50 | cat::verify(cat::abs(-1) == 1); 51 | cat::verify(cat::abs(-1.5f) == 1.5f); 52 | cat::verify(cat::abs(-1.5) == 1.5); 53 | cat::verify(cat::abs(-1.5_f4) == 1.5_f4); 54 | cat::verify(cat::abs(-1.5_f8) == 1.5_f8); 55 | 56 | static_assert(cat::is_same); 57 | static_assert(cat::is_same); 58 | static_assert(cat::is_same); 59 | 60 | cat::verify(cat::abs(1u) == 1); 61 | cat::verify(cat::abs(1_u1) == 1_u1); 62 | cat::verify(cat::abs(1_u2) == 1_u2); 63 | cat::verify(cat::abs(1_u4) == 1_u4); 64 | cat::verify(cat::abs(1_u8) == 1_u8); 65 | 66 | // Test `pow()`. 67 | cat::verify(cat::pow(2, 2) == 4); 68 | cat::verify(cat::pow(2, 1) == 2); 69 | cat::verify(cat::pow(2, 0) == 1); 70 | cat::verify(cat::pow(1, 10) == 1); 71 | cat::verify(cat::pow(8, -1) == 0); 72 | 73 | cat::verify(cat::pow(2u, 2u) == 4u); 74 | 75 | // cat::verify(cat::pow(2.f, 2) == 4.f); 76 | // cat::verify(cat::pow(2.f, 1) == 2.f); 77 | // cat::verify(cat::pow(2.f, 0) == 1.f); 78 | // cat::verify(cat::pow(8.f, -1) == 0.f); 79 | 80 | // cat::verify(cat::pow(2, 2) == 4.); 81 | // cat::verify(cat::pow(2, 1) == 2.); 82 | // cat::verify(cat::pow(2, 0) == 1.); 83 | // cat::verify(cat::pow(8, -1) == 0.); 84 | 85 | // Test `has_single_bit()`. 86 | cat::verify(cat::has_single_bit(0)); 87 | cat::verify(cat::has_single_bit(1)); 88 | cat::verify(cat::has_single_bit(2)); 89 | cat::verify(!cat::has_single_bit(3)); 90 | cat::verify(cat::has_single_bit(4)); 91 | cat::verify(cat::has_single_bit(8u)); 92 | cat::verify(cat::has_single_bit(256)); 93 | 94 | // Test `round_to_pow2()`. 95 | static_assert(cat::round_to_pow2(0u) == 0u); 96 | static_assert(cat::round_to_pow2(1u) == 1u); 97 | static_assert(cat::round_to_pow2(2u) == 2u); 98 | static_assert(cat::round_to_pow2(3u) == 4u); 99 | static_assert(cat::round_to_pow2(4u) == 4u); 100 | static_assert(cat::round_to_pow2(5u) == 8u); 101 | 102 | static_assert(cat::round_to_pow2(0) == 0); 103 | static_assert(cat::round_to_pow2(1) == 1); 104 | static_assert(cat::round_to_pow2(2) == 2); 105 | static_assert(cat::round_to_pow2(3) == 4); 106 | static_assert(cat::round_to_pow2(4) == 4); 107 | static_assert(cat::round_to_pow2(5) == 8); 108 | 109 | // TODO: How should rounding functions handle negative inputs? 110 | 111 | // Test `round_up_to_multiple_of()`. 112 | static_assert(cat::round_up_to_multiple_of(5, 2) == 6); 113 | static_assert(cat::round_up_to_multiple_of(5u, 1) == 5u); 114 | static_assert(cat::round_up_to_multiple_of(5, 8) == 8); 115 | 116 | // Test `round_down_to_multiple_of()`. 117 | static_assert(cat::round_down_to_multiple_of(5, 2) == 4); 118 | static_assert(cat::round_down_to_multiple_of(5u, 1) == 5u); 119 | static_assert(cat::round_down_to_multiple_of(5, 8) == 0); 120 | 121 | // Test `clamp()`. 122 | cat::verify(cat::clamp(-10, 0, 10) == 0); 123 | cat::verify(cat::clamp(5, 0, 10) == 5); 124 | cat::verify(cat::clamp(20, 0, 10) == 10); 125 | 126 | cat::verify(cat::clamp(0u, 1u, 10u) == 1u); 127 | cat::verify(cat::clamp(5u, 1u, 10u) == 5u); 128 | cat::verify(cat::clamp(20u, 1u, 10u) == 10u); 129 | 130 | cat::verify(cat::clamp(-10.f, 0.f, 10.f) == 0.f); 131 | cat::verify(cat::clamp(5.f, 0.f, 10.f) == 5.f); 132 | cat::verify(cat::clamp(20.f, 0.f, 10.f) == 10.f); 133 | 134 | cat::verify(cat::clamp(-10., 0., 10.) == 0.); 135 | cat::verify(cat::clamp(5., 0., 10.) == 5.); 136 | cat::verify(cat::clamp(20., 0., 10.) == 10.); 137 | } 138 | -------------------------------------------------------------------------------- /tests/src/test_paging_memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../unit_tests.hpp" 7 | 8 | namespace { 9 | constinit int4 paging_counter_ctor = 0; 10 | constinit int4 paging_counter_dtor = 0; 11 | 12 | struct test_page_type { 13 | test_page_type() { 14 | ++paging_counter_ctor; 15 | } 16 | 17 | ~test_page_type() { 18 | ++paging_counter_dtor; 19 | } 20 | }; 21 | } // namespace 22 | 23 | test(paging_memory) { 24 | // Initialize an allocator. 25 | cat::page_allocator allocator; 26 | 27 | // Allocate a page. 28 | cat::span memory = 29 | allocator.alloc_multi(1'000u).or_exit("Failed to page memory!"); 30 | // Free the page at the end of this program. 31 | defer { 32 | allocator.free(memory); 33 | }; 34 | 35 | // Write to the page. 36 | memory[0] = 10; 37 | cat::verify(memory[0] == 10); 38 | 39 | // `allocation_type` with small-size optimization. 40 | auto small_memory_1 = allocator.inline_alloc().verify(); 41 | allocator.get(small_memory_1) = 2; 42 | // Both values should be on stack, so these addresses are close 43 | // together. 44 | cat::verify(small_memory_1.is_inline()); 45 | // The handle's address should be the same as the data's if it was 46 | // allocated on the stack. 47 | int4& intref = *reinterpret_cast(&small_memory_1); 48 | intref = 10; 49 | cat::verify(allocator.get(small_memory_1) == 10); 50 | 51 | allocator.free(small_memory_1); 52 | 53 | auto small_memory_5 = allocator.inline_alloc_multi(1'000u).verify(); 54 | // `small_memory_1` should be larger than the small storage buffer. 55 | cat::verify(!small_memory_5.is_inline()); 56 | allocator.free(small_memory_1); 57 | 58 | // Small-size handles have unique storage. 59 | auto small_memory_2 = allocator.inline_alloc().verify(); 60 | allocator.get(small_memory_2) = 1; 61 | auto small_memory_3 = allocator.inline_alloc().verify(); 62 | allocator.get(small_memory_3) = 2; 63 | auto small_memory_4 = allocator.inline_alloc().verify(); 64 | allocator.get(small_memory_4) = 3; 65 | cat::verify(allocator.get(small_memory_2) == 1); 66 | cat::verify(allocator.get(small_memory_3) == 2); 67 | cat::verify(allocator.get(small_memory_4) == 3); 68 | 69 | // Test constructor being called. 70 | cat::maybe testtype = allocator.alloc(); 71 | allocator.free(testtype.value()); 72 | 73 | // That constructor increments `paging_counter_ctor`. 74 | cat::verify(paging_counter_ctor == 1); 75 | // That destructor increments `paging_counter_dtor`. 76 | cat::verify(paging_counter_dtor == 1); 77 | 78 | // Test multi-allocations. 79 | auto array_memory = allocator.alloc_multi(9u).verify(); 80 | // Those 9 constructors increment `paging_counter_ctor`. 81 | cat::verify(paging_counter_ctor == 10); 82 | 83 | allocator.free(array_memory); 84 | // Those 9 destructors increment `paging_counter_dtor`. 85 | cat::verify(paging_counter_dtor == 10); 86 | 87 | auto smalltesttype = allocator.inline_alloc().verify(); 88 | allocator.get(smalltesttype) = test_page_type(); 89 | allocator.free(smalltesttype); 90 | 91 | // Aligned memory allocations. 92 | auto aligned_mem = allocator.align_alloc_multi(32u, 4u).verify(); 93 | aligned_mem[0] = 10; 94 | cat::verify(aligned_mem[0] == 10); 95 | allocator.free(aligned_mem); 96 | }; 97 | -------------------------------------------------------------------------------- /tests/src/test_pool_allocator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | test(pool_allocator) { 6 | // Initialize an allocator. 7 | cat::page_allocator pager; 8 | cat::span page = pager.alloc_multi(128u).verify(); 9 | defer { 10 | pager.free(page); 11 | }; 12 | cat::is_allocator auto allocator = cat::make_pool_allocator<8>(page); 13 | 14 | // Make an allocation. 15 | int4* p_int1 = allocator.alloc(10).value(); 16 | cat::verify(*p_int1 == 10); 17 | 18 | // Make another allocation. 19 | int8* p_int2 = allocator.alloc(20).value(); 20 | cat::verify(*p_int2 == 20); 21 | 22 | // Test allocation limit is correct. 23 | // The allocator owns 128 bytes, divided into 16 sections of 8-bytes. 24 | // Two allocations have already been made, so 14 remain. 25 | for (idx i; i < 14; ++i) { 26 | auto* _ = allocator.alloc(10).verify(); 27 | } 28 | // There should now be no remaining allocations, so this fails. 29 | cat::maybe failed_allocation = allocator.alloc(); 30 | cat::verify(!failed_allocation.has_value()); 31 | 32 | // Make an allocation after resetting. 33 | allocator.reset(); 34 | int4* p_int3 = allocator.alloc(30).verify(); 35 | cat::verify(*p_int3 == 30); 36 | 37 | // Test freeing memory. 38 | for (idx i; i < 15; ++i) { 39 | auto* _ = allocator.alloc().verify(); 40 | } 41 | failed_allocation = allocator.alloc(); 42 | cat::verify(!failed_allocation.has_value()); 43 | 44 | allocator.free(p_int3); 45 | 46 | // Make one allocation after freeing. 47 | int4* p_int4 = allocator.alloc(40).verify(); 48 | cat::verify(*p_int4 == 40); 49 | } 50 | -------------------------------------------------------------------------------- /tests/src/test_raii.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../unit_tests.hpp" 6 | 7 | inline constinit int4 raii_counter = 0; 8 | 9 | struct type { 10 | cat::string data; 11 | 12 | type() = default; 13 | 14 | type(cat::string string) : data(mov string) { 15 | // cat::print(data); 16 | // cat::println(" constructor"); 17 | } 18 | 19 | // NOLINTNEXTLINE This is a non-trivial destructor. 20 | ~type() { 21 | // cat::print("~"); 22 | // cat::println(this->data); 23 | } 24 | 25 | auto 26 | operator=(cat::string string) -> type& { 27 | this->data = mov string; 28 | return *this; 29 | } 30 | 31 | void 32 | raii() const { 33 | // cat::print(this->data); 34 | // cat::println(" calls raii()!"); 35 | ++raii_counter; 36 | } 37 | }; 38 | 39 | // NOLINTNEXTLINE 40 | void pass_by_value(cat::unique_weak){}; 41 | 42 | test(raii) { 43 | // TODO: Fix `unique` and re-enable these tests. 44 | // cat::println("Construct objects."); 45 | // Test constructor. 46 | cat::unique_weak foo(cat::string("foo")); 47 | // Test assignment. 48 | foo = cat::string("foo"); 49 | cat::verify(foo.has_ownership()); 50 | 51 | cat::unique_weak moo(cat::string("moo")); 52 | cat::verify(moo.has_ownership()); 53 | 54 | // Test move-assignment. 55 | // cat::println("Move moo into foo."); 56 | foo = mov moo; 57 | cat::verify(!moo.has_ownership()); 58 | 59 | // cat::println("Move foo into func()."); 60 | // A move is required: 61 | pass_by_value(mov foo); 62 | cat::verify(!foo.has_ownership()); 63 | 64 | // This is deliberately ill-formed: 65 | // func(foo); 66 | 67 | // Default construct ` cat::unique`. 68 | cat::unique_weak goo; 69 | cat::verify(goo.has_ownership()); 70 | // Extract goo. 71 | auto _ = goo.borrow(); 72 | cat::verify(!goo.has_ownership()); 73 | 74 | // `raii()` should have been called exactly three times. 75 | cat::verify(raii_counter == 3); 76 | 77 | // Deduction guides should work here. 78 | cat::unique_weak weak = 1; 79 | cat::unique unique = weak.borrow(); 80 | 81 | // Borrowing `weak`'s data makes it lose ownership. 82 | cat::verify(!weak.has_ownership()); 83 | weak = 2; 84 | cat::verify(weak.has_ownership()); 85 | 86 | // Permanately transferring ownership a `cat::unique`'s storage is unsafe, 87 | // but possible: 88 | weak = unique.borrow(); 89 | cat::verify(weak.has_ownership()); 90 | 91 | // `cat::unique` can be assigned over, which will call its old data's 92 | // destructor. 93 | unique = 2; 94 | 95 | cat::unique original = 0; 96 | cat::unique const into = mov original; 97 | } 98 | -------------------------------------------------------------------------------- /tests/src/test_ring.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../unit_tests.hpp" 6 | 7 | test(ring) { 8 | // Initialize an allocator. 9 | cat::page_allocator pager; 10 | pager.reset(); 11 | auto page = pager.opq_alloc_multi(4_ki - 32).or_exit(); 12 | defer { 13 | pager.free(page); 14 | }; 15 | auto allocator = cat::linear_allocator::backed_handle(pager, page); 16 | 17 | cat::ring ring_int4; 18 | cat::verify(ring_int4.size() == 0); 19 | cat::verify(ring_int4.capacity() == 0); 20 | 21 | // Push onto ring. 22 | ring_int4.reserve(allocator, 4).verify(); 23 | ring_int4.push_back(1); 24 | ring_int4.push_back(3); 25 | ring_int4.push_back(2); 26 | ring_int4.push_back(0); 27 | 28 | cat::verify(ring_int4[0] == 1); 29 | cat::verify(ring_int4[1] == 3); 30 | cat::verify(ring_int4[2] == 2); 31 | cat::verify(ring_int4[3] == 0); 32 | 33 | // Wrapping `.push_back()`. 34 | ring_int4.push_back(20); 35 | cat::verify(ring_int4[0] == 20); 36 | 37 | // Set element. 38 | ring_int4[1] = 10; 39 | cat::verify(ring_int4[1] == 10); 40 | 41 | ring_int4.at(1).value() = 5; 42 | cat::verify(ring_int4[1] == 5); 43 | } 44 | -------------------------------------------------------------------------------- /tests/src/test_set_memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "../unit_tests.hpp" 5 | 6 | test(set_memory) { 7 | using namespace cat::arithmetic_literals; 8 | 9 | cat::page_allocator allocator; 10 | cat::span page = allocator.alloc_multi(4_uki).or_exit(); 11 | uint1* p_page = page.data(); 12 | 13 | // TODO: Make a `cat::compare_memory()` or something for this. 14 | 15 | cat::set_memory(p_page, 1_u1, 4_uki); 16 | cat::verify(p_page[1'000] == 1_u1); 17 | 18 | // Test that unaligned memory still sets correctly. 19 | cat::set_memory(p_page + 3, 2_u1, 2_uki - 6); 20 | cat::verify(p_page[0] == 1_u1); 21 | cat::verify(p_page[2] == 1_u1); 22 | cat::verify(p_page[3] == 2_u1); 23 | // TODO: Why did this stop working? 24 | // cat::verify(p_page[(2_ki - 4).raw] == 2_u1); 25 | cat::verify(p_page[(2_ki - 3).raw] == 1_u1); 26 | 27 | // Test zeroing out memory. 28 | cat::zero_memory(p_page, 4_uki); 29 | cat::verify(p_page[0] == 0_u1); 30 | cat::verify(p_page[(4_ki).raw - 1] == 0_u1); 31 | 32 | // Test setting values larger than 1 byte. 33 | cat::set_memory(p_page, 1_i2, 2_uki); 34 | cat::verify(static_cast(static_cast(p_page))[10] == 1_i2); 35 | // The next byte after this should be 0. 36 | cat::verify(static_cast(static_cast(p_page))[21] == 0); 37 | 38 | cat::set_memory(p_page, 1_i4, 1_uki); 39 | cat::verify(static_cast(static_cast(p_page))[10] == 1_i4); 40 | 41 | cat::set_memory(p_page, 1_i8, 0.5_uki); 42 | cat::verify(static_cast(static_cast(p_page))[10] == 1_i4); 43 | 44 | // Test scalar `set_memory()`. 45 | cat::set_memory_scalar(p_page, 1_u1, 4_uki); 46 | cat::verify(p_page[1'001] == 1_u1); 47 | 48 | // Test scalar `zero_memory()`. 49 | cat::zero_memory_scalar(p_page, 4_uki); 50 | cat::verify(p_page[1'001] == 0_u1); 51 | }; 52 | -------------------------------------------------------------------------------- /tests/src/test_simd.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | // TODO: Test more SIMD functions. 6 | test(simd) { 7 | // Test that vector arithmetic does not segfault. 8 | int4x4 vec1 = {0, 1, 2, 3}; 9 | int4x4 vec2{0, 1, 2, 3}; 10 | vec1 += vec2; 11 | auto _ = vec1 + vec2; 12 | 13 | // TODO: Test correctness of vector operations. 14 | } 15 | -------------------------------------------------------------------------------- /tests/src/test_string_length.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "unit_tests.hpp" 5 | 6 | test(string_length) { 7 | char const* p_string_1 = "Hello!"; 8 | char const* const p_string_2 = "Hello!"; 9 | 10 | char const* const p_string_3 = "Hello!"; 11 | char const* p_string_4 = "Hello!"; 12 | 13 | char const* const p_string_5 = "Hello!"; 14 | char const* const p_string_6 = "Hello!"; 15 | 16 | char const* p_string_7 = "/tmp/temp.sock"; 17 | 18 | cat::iword len_1 = cat::string_length(p_string_1); 19 | cat::iword len_2 = cat::string_length(p_string_2); 20 | cat::iword len_3 = cat::string_length(p_string_3); 21 | cat::iword len_4 = cat::string_length(p_string_4); 22 | cat::iword len_5 = cat::string_length(p_string_5); 23 | cat::iword len_6 = cat::string_length(p_string_6); 24 | cat::iword len_7 = cat::string_length(p_string_7); 25 | 26 | cat::verify(len_1 == len_2); 27 | cat::verify(len_1 == 6); 28 | cat::verify(len_3 == len_4); 29 | cat::verify(len_5 == len_6); 30 | cat::verify(len_7 == 14); 31 | 32 | // Test `string`s. 33 | cat::str_view string_1 = p_string_1; 34 | cat::verify(string_1.size() == len_1); 35 | cat::verify(string_1.subspan(1, 4).size() == 3); 36 | cat::verify(string_1.first(4).size() == 4); 37 | cat::verify(string_1.last(3).size() == 3); 38 | cat::verify(cat::str_view("Hello!").size() == len_1); 39 | 40 | auto inplace = cat::make_str_inplace<10u>("Hello"); 41 | cat::verify(inplace.size() == 10); // "Hello\0\0\0\0\0" 42 | 43 | auto inplace_2 = cat::make_str_inplace<5u>("Hello"); 44 | cat::verify(inplace_2.size() == 5); 45 | 46 | auto inplace_3 = cat::str_inplace("Hello"); 47 | cat::verify(inplace_3.size() == 5); 48 | 49 | auto strv = cat::str_view("Hello"); 50 | cat::verify(strv.size() == 5); 51 | 52 | cat::zstr_view zstr("Hello"); 53 | cat::verify(zstr.size() == 6); 54 | zstr = p_string_6; 55 | cat::verify(zstr.size() == 7); 56 | 57 | cat::str_view str_over_zstr = zstr; 58 | cat::verify(str_over_zstr.size() == 6); 59 | str_over_zstr = zstr; 60 | cat::verify(str_over_zstr.size() == 6); 61 | 62 | cat::page_allocator pager; 63 | cat::zstr_span mut_zstr = pager.calloc_multi(6).verify(); 64 | mut_zstr[0] = 'a'; 65 | mut_zstr[1] = 'b'; 66 | mut_zstr[2] = 'c'; // A \0 gap is at the 4th byte. 67 | mut_zstr[4] = 'd'; 68 | defer { 69 | pager.free(mut_zstr); 70 | }; 71 | cat::verify(mut_zstr.size() == 6); 72 | cat::verify(cat::zstr_view(mut_zstr).size() == 6); 73 | 74 | cat::verify(cat::str_span(mut_zstr).size() == 5); 75 | cat::verify(cat::str_view(mut_zstr).size() == 5); 76 | cat::verify(cat::str_view(cat::zstr_view(mut_zstr)).size() == 5); 77 | 78 | cat::zstr_inplace<10u> inplace_z = cat::make_zstr_inplace<10u>("Hello"); 79 | cat::verify(inplace_z.size() == 10); // "Hello\0\0\0\0\0" 80 | cat::verify(cat::str_span(inplace_z).size() == 9); 81 | cat::verify(cat::str_view(inplace_z).size() == 9); 82 | 83 | cat::verify(cat::zstr_span(inplace_z).size() == 10); 84 | cat::verify(cat::zstr_view(inplace_z).size() == 10); 85 | 86 | auto inplace_z_2 = cat::make_zstr_inplace<6u>("Hello"); 87 | cat::verify(inplace_z_2.size() == 6); // "Hello\0" 88 | 89 | auto inplace_z_3 = inplace_z_2 + inplace_z_2; 90 | cat::verify(inplace_z_3.size() == 11); // "HelloHello\0" 91 | 92 | // Binding these containers to this `char` array is only well-formed when the 93 | // final element is `\0`. 94 | static constexpr char const char_array[] = {'H', 'i', 'a', '\0'}; 95 | cat::str_view arr_view = char_array; 96 | arr_view = char_array; 97 | cat::str_inplace arr_inplace = char_array; 98 | cat::str_inplace arr_inplace2 = cat::make_str_inplace<4>(char_array); 99 | cat::zstr_view arr_zview = char_array; 100 | arr_zview = char_array; 101 | cat::zstr_inplace arr_zinplac = cat::make_zstr_inplace<4>(char_array); 102 | } 103 | -------------------------------------------------------------------------------- /tests/src/test_stringify.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | namespace foo { 6 | template 7 | struct stringify_me {}; 8 | } 9 | 10 | test(stringify) { 11 | static_assert(cat::nameof(1) == "int"); 12 | static_assert(cat::nameof(1ll) == "long long"); 13 | static_assert(cat::nameof(foo::stringify_me<1>()) == "foo::stringify_me<1>"); 14 | } 15 | -------------------------------------------------------------------------------- /tests/src/test_thread.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../unit_tests.hpp" 10 | 11 | namespace { 12 | 13 | constinit cat::atomic atomic = 0; 14 | 15 | thread_local int tls1 = 1; 16 | thread_local int tls2 = 2; 17 | 18 | void 19 | function_1() { 20 | for (idx i = 0; i < 3; ++i) { 21 | ++atomic; 22 | ++tls1; 23 | ++tls2; 24 | } 25 | cat::verify(tls1 == 4); 26 | cat::verify(tls2 == 5); 27 | } 28 | 29 | void 30 | function_2() { 31 | ++atomic; 32 | } 33 | 34 | } // namespace 35 | 36 | test(thread) { 37 | cat::thread threads[5]; 38 | cat::page_allocator allocator; 39 | 40 | threads[0].spawn(allocator, 2_uki, 2_uki, function_1).verify(); 41 | threads[1].spawn(allocator, 2_uki, 2_uki, &function_2).verify(); 42 | 43 | threads[2] 44 | .spawn(allocator, 2_uki, 2_uki, 45 | [] { 46 | ++atomic; 47 | }) 48 | .verify(); 49 | 50 | threads[3] 51 | .spawn( 52 | allocator, 2_uki, 2_uki, 53 | [](int) { 54 | ++atomic; 55 | }, 56 | 1) 57 | .verify(); 58 | 59 | threads[4] 60 | .spawn( 61 | allocator, 2_uki, 2_uki, 62 | +[] { 63 | ++atomic; 64 | }) 65 | .verify(); 66 | 67 | for (idx i; i < 3; ++i) { 68 | ++atomic; 69 | // ++tls1; 70 | } 71 | // TODO: Initialize tls on the main thread. 72 | // cat::verify(tls1 == 4); 73 | 74 | threads[0].join().verify(); 75 | threads[1].join().verify(); 76 | threads[2].join().verify(); 77 | threads[3].join().verify(); 78 | threads[4].join().verify(); 79 | cat::verify(atomic.load() == 10); 80 | } 81 | -------------------------------------------------------------------------------- /tests/src/test_tuple.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | struct tup_non_trivial { 6 | tup_non_trivial(int) {}; 7 | }; 8 | 9 | test(tuple) { 10 | using namespace cat::arithmetic_literals; 11 | 12 | using intint = cat::tuple; 13 | static_assert(cat::is_trivial); 14 | static_assert(sizeof(intint) == 8); 15 | 16 | using floatfloat = cat::tuple; 17 | static_assert(cat::is_trivial); 18 | static_assert(sizeof(floatfloat) == 8); 19 | 20 | using non_and_int4 = cat::tuple; 21 | [[maybe_unused]] 22 | non_and_int4 test_intint4{1, int4{0}}; 23 | static_assert(!cat::is_trivial); 24 | static_assert(sizeof(non_and_int4) == 4); 25 | 26 | // Test `tuple` storage. 27 | intint tuple; 28 | int& left_1 = tuple.get<0>(); 29 | left_1 = 10; 30 | int left_2 = tuple.get<0>(); 31 | cat::verify(left_2 == 10); 32 | cat::verify(left_2 == left_1); 33 | cat::verify(left_2 == tuple.first()); 34 | tuple.second() = 20; 35 | 36 | // Test destructuring. 37 | auto& [int_1, int_2] = tuple; 38 | cat::verify(int_1 == 10); 39 | cat::verify(int_2 == 20); 40 | 41 | // Test aggregate construction. 42 | cat::tuple intchar = {100, 'a'}; 43 | cat::verify(intchar.first() == 100); 44 | cat::verify(intchar.second() == 'a'); 45 | 46 | // Test aggregate assignment. 47 | intchar = {200, 'b'}; 48 | cat::tuple intchar2{200, 'b'}; 49 | cat::verify(intchar.first() == 200); 50 | cat::verify(intchar.second() == 'b'); 51 | 52 | // Test `const`. 53 | cat::tuple const intchar_const = {100, 'a'}; 54 | cat::verify(intchar_const.first() == 100); 55 | cat::verify(intchar_const.second() == 'a'); 56 | 57 | // Test move semantics. 58 | cat::tuple&& intchar_move = {100, 'a'}; 59 | cat::verify(cat::move(intchar_move.first()) == 100); 60 | cat::verify(cat::move(intchar_move.second()) == 'a'); 61 | 62 | cat::tuple const intchar_move_const = {100, 'a'}; 63 | cat::verify(cat::move(intchar_move_const.first()) == 100); 64 | cat::verify(cat::move(intchar_move_const.second()) == 'a'); 65 | 66 | // Test type deduction. 67 | cat::tuple deduced = {0, 'b', 10.f}; 68 | static_assert(cat::is_same()), int&>); 69 | static_assert(cat::is_same()), char&>); 70 | static_assert(cat::is_same()), float&>); 71 | 72 | // Test `tuple` auto-generated getters. 73 | cat::tuple five_tuple; 74 | static_assert(cat::is_same); 75 | static_assert(cat::is_same); 76 | static_assert(cat::is_same); 77 | static_assert(cat::is_same); 78 | static_assert(cat::is_same); 79 | 80 | // Test structured bindings. 81 | auto& [one, two, three, four, five] = five_tuple; 82 | one = 'a'; 83 | two = 2; 84 | three = true; 85 | four = nullptr; 86 | five = 1u; 87 | 88 | // Test that `tuple` size is zero-overhead. 89 | static_assert(sizeof(intint) == sizeof(int) * 2); 90 | 91 | // This type is 32 bytes due to padding for member alignment. 92 | struct Five { 93 | // Eight bytes: 94 | char c; 95 | int4 i; 96 | // Eight bytes: 97 | bool b; 98 | // Sixteen bytes: 99 | void* p; 100 | uint8 u; 101 | }; 102 | 103 | static_assert(sizeof(five_tuple) == sizeof(Five)); 104 | 105 | // Test empty tuple. 106 | [[maybe_unused]] 107 | cat::tuple<> empty_tuple; 108 | [[maybe_unused]] 109 | cat::tuple<> empty_tuple_2{}; 110 | 111 | // tuple of tuples. 112 | cat::tuple, cat::tuple> tuple_of_tuple = { 113 | tuple, intchar}; 114 | cat::tuple tuple_of_tuple_ctad = {tuple, intchar}; 115 | static_assert( 116 | cat::is_same); 117 | 118 | cat::tuple_cat(); 119 | // cat::tuple_cat(empty_tuple); 120 | // cat::tuple_cat(intint{0, 1}, floatfloat{2.f, 3.f}); 121 | 122 | // // Test `tuple` concatenation. 123 | // cat::tuple concat_lhs = cat::tuple{10}; 124 | // cat::tuple concat_rhs = cat::tuple{1.f}; 125 | // cat::tuple concat_tuple = concat_lhs.concat(concat_rhs); 126 | // cat::verify(concat_tuple.first() == 10); 127 | // cat::verify(concat_tuple.second() == 1.f); 128 | 129 | /* 130 | // Test `tuple` conversions. 131 | cat::tuple floatfloat = cat::tuple{10, 20}; 132 | cat::verify(floatfloat.first() == 10.f); 133 | cat::verify(floatfloat.second() == 20.f); 134 | 135 | int l_int = 1; 136 | int r_int = 2; 137 | cat::tuple tuple_ref_conv{l_int, r_int}; 138 | cat::tuple tuple_val = tuple_ref_conv; 139 | // cat::tuple tuple_ref = tuple_val; 140 | */ 141 | } 142 | -------------------------------------------------------------------------------- /tests/src/test_typelist.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | test(typelist) { 6 | using types = cat::type_list; 7 | using type_0 = types::get<0>; 8 | using type_1 = types::get<1>; 9 | 10 | static_assert(cat::is_same); 11 | static_assert(cat::is_same); 12 | 13 | static_assert(types::has_type); 14 | static_assert(types::has_type); 15 | static_assert(types::has_type); 16 | static_assert(!types::has_type); 17 | 18 | static_assert(types::count_type == 1); 19 | static_assert(!(types::count_type == 2)); 20 | static_assert(types::count_type == 1); 21 | static_assert(types::count_type == 0); 22 | 23 | static_assert(types::is_unique); 24 | static_assert(types::is_unique); 25 | static_assert(!types::is_unique); 26 | 27 | using types2 = cat::type_list; 28 | static_assert(types::is_unique_list); 29 | static_assert(!types2::is_unique_list); 30 | static_assert(types2::count_type == 2); 31 | 32 | using concat_types = types::concat_types; 33 | static_assert( 34 | cat::is_same>); 35 | 36 | using merge_types = types::merge; 37 | static_assert( 38 | cat::is_same>); 39 | 40 | using fill_types = cat::type_list_filled; 41 | static_assert(cat::is_same>); 42 | } 43 | -------------------------------------------------------------------------------- /tests/src/test_variant.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "../unit_tests.hpp" 4 | 5 | test(variant) { 6 | cat::variant variant(int{1}); 7 | cat::verify(variant.is()); 8 | cat::verify(variant.holds_alternative()); 9 | int foo_int = variant.get(); 10 | cat::verify(foo_int == 1); 11 | 12 | static_assert(variant.alternative_index == 0u); 13 | static_assert(variant.alternative_index == 1u); 14 | static_assert(variant.alternative_index == 2u); 15 | 16 | variant = 'o'; 17 | cat::verify(variant.is()); 18 | cat::verify(variant.holds_alternative()); 19 | char foo_char = variant.get(); 20 | cat::verify(foo_char == 'o'); 21 | 22 | cat::maybe maybe_1 = variant.get_if(); 23 | cat::verify(maybe_1.has_value()); 24 | cat::maybe maybe_2 = variant.get_if(); 25 | cat::verify(!maybe_2.has_value()); 26 | 27 | // Test variant size. 28 | static_assert(sizeof(variant) == 8); 29 | static_assert(sizeof(variant.discriminant) == 4); 30 | 31 | cat::variant big_variant; 32 | static_assert(sizeof(big_variant) == 16); 33 | static_assert(sizeof(big_variant.discriminant) == 4); 34 | 35 | // Test variant subtype constructor and assignment operator. 36 | cat::variant variant2 = variant; 37 | cat::verify(variant2.is()); 38 | cat::verify(variant2.holds_alternative()); 39 | variant2 = 1; 40 | cat::verify(variant2.is()); 41 | cat::verify(variant2.holds_alternative()); 42 | variant2 = variant; 43 | cat::verify(variant2.is()); 44 | cat::verify(variant2.holds_alternative()); 45 | 46 | variant = 1; 47 | cat::variant variant3 = variant; 48 | cat::verify(variant3.is()); 49 | cat::verify(variant3.holds_alternative()); 50 | variant3 = int2{10}; 51 | cat::verify(variant3.is()); 52 | cat::verify(variant3.holds_alternative()); 53 | variant3 = variant; 54 | cat::verify(variant3.is()); 55 | cat::verify(variant3.holds_alternative()); 56 | 57 | // Test getting variant type by index. 58 | static_assert(cat::is_same()), int>); 59 | static_assert(cat::is_same()), char>); 60 | static_assert(cat::is_same()), uint4>); 61 | static_assert(cat::is_same()), int2>); 62 | 63 | // Test constant-evaluating `variant`. 64 | constexpr cat::variant const_variant = 1; 65 | static_assert(const_variant.get() == 1); 66 | 67 | // Test `.is()`. 68 | variant3 = int{1}; 69 | cat::verify(variant3.is()); 70 | cat::verify(variant3.is(1)); 71 | 72 | variant3 = 'b'; 73 | cat::verify(variant3.is()); 74 | cat::verify(variant3.is('b')); 75 | 76 | // `.is()` accepts unconditionally invalid types, unlike 77 | // `.holds_alternative()`. 78 | cat::verify(!variant3.is()); 79 | 80 | // Test pattern matching. 81 | variant3 = int{1}; 82 | bool matched = false; 83 | cat::match(variant3)( // 84 | one_of().then_do([]() { 85 | // This should never match. 86 | cat::exit(1); 87 | }), 88 | one_of().then_do([&]() { 89 | // This should match, because it is an `int`. 90 | matched = true; 91 | })); 92 | cat::verify(matched); 93 | 94 | matched = false; 95 | cat::match(variant3)( // 96 | is_a().then_do([&]() { 97 | cat::exit(1); 98 | }), 99 | is_a().then_do([&]() { 100 | matched = true; 101 | })); 102 | cat::verify(matched); 103 | 104 | // `variant3` holds an integer, but floats are convertible to integers. 105 | matched = false; 106 | cat::match(variant3)( // 107 | is_a(2.f).then_do([&]() { 108 | cat::exit(1); 109 | }), 110 | is_a(1.f).then_do([&]() { 111 | matched = true; 112 | })); 113 | cat::verify(matched); 114 | 115 | // Test member access pattern matching syntax. 116 | matched = false; 117 | variant3.match( // 118 | is_a(2.f).then_do([&]() { 119 | cat::exit(1); 120 | }), 121 | is_a(1.f).then_do([&]() { 122 | matched = true; 123 | })); 124 | cat::verify(matched); 125 | }; 126 | -------------------------------------------------------------------------------- /tests/unit_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "./unit_tests.hpp" 2 | 3 | #include 4 | #include 5 | 6 | // The jump buffer must be constructed in `main()` instead of globally so that 7 | // it can be guaranteed to occur before any unit tests are called. 8 | namespace { 9 | inline constinit cat::jmp_buffer* p_jump_buffer = nullptr; 10 | } // namespace 11 | 12 | namespace cat { 13 | void 14 | test_fail(cat::source_location const& source_location) { 15 | cat::detail::print_assert_location(source_location); 16 | // Gracefully handle print in tests. 17 | auto _ = cat::println(); 18 | ++tests_failed; 19 | cat::longjmp(*p_jump_buffer, 2); 20 | } 21 | } // namespace cat 22 | 23 | extern "C" { 24 | extern constructor_fn __init_array_start[]; 25 | extern constructor_fn __init_array_end[]; 26 | } 27 | 28 | auto 29 | main() -> int { 30 | // Change the default assert handler. 31 | cat::assert_handler = &cat::test_fail; 32 | 33 | // Set the jump buffer pointer before any constructors are called. 34 | cat::jmp_buffer jump_buffer; 35 | p_jump_buffer = &jump_buffer; 36 | 37 | // Call all unit test functions that were pushed into `test_fns` by the 38 | // `CAT_TEST` macro. 39 | for (cat::idx i = 0; i < test_fns.value().size(); ++i) { 40 | auto _ = ::cat::print(::cat::fmt(pager, "Running test {}", i).value()); 41 | if (cat::setjmp(jump_buffer)) { 42 | // Jump here when a test fails, skipping the rest of a test's 43 | // constructor function. 44 | continue; 45 | } 46 | (reinterpret_cast(test_fns.value()[i]))(); 47 | } 48 | 49 | // `tests_passed` and `tests_failed` are modified within the `CAT_TEST` 50 | // macro. 51 | // TODO: This will leak. An `inline_allocator` should be used. 52 | auto _ = cat::print(cat::fmt(pager, "\n{} tests passed.\n{} tests failed.\n", 53 | tests_passed, tests_failed) 54 | .or_exit()); 55 | } 56 | -------------------------------------------------------------------------------- /tests/unit_tests.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // All unit tests have access to these symbols: 7 | using namespace cat::literals; 8 | using namespace cat::integers; 9 | 10 | [[gnu::used]] 11 | constinit inline cat::page_allocator pager; 12 | 13 | constinit inline idx tests_passed; 14 | constinit inline idx tests_failed; 15 | 16 | using constructor_fn = void (*const)(); 17 | constinit inline cat::maybe> test_fns; 18 | 19 | [[noreturn]] 20 | void 21 | test_fail(cat::source_location const& source_location); 22 | 23 | // This macro declares a unit test named `test_name`, which is executed 24 | // automatically in this program's constructor calls. 25 | #define CAT_TEST(test_name) \ 26 | void test_##test_name(); \ 27 | void test_##test_name##_prologue(); \ 28 | \ 29 | [[gnu::constructor]] \ 30 | void cat_register_test##test_name() { \ 31 | /* For some reason, linking asan causes this vector to be uninitialized. \ 32 | * I've tried `[[gnu::constructor]]` priorities to order it, but that \ 33 | * doesn't work. This hacky check works around it. \ 34 | */ \ 35 | if (!test_fns.has_value()) { \ 36 | test_fns = cat::make_vec_reserved(pager, 4_uki / 8).value(); \ 37 | } \ 38 | /* This is memory is pre-reserved, so `push_back` cannot fail. */ \ 39 | auto _ = test_fns.verify().push_back( \ 40 | reinterpret_cast(test_##test_name##_prologue)); \ 41 | } \ 42 | \ 43 | void test_##test_name##_prologue() { \ 44 | /* TODO: Align the whitespace after `:` for 1 and 2 digit tests. */ \ 45 | constexpr ::cat::str_view string = ": test_" #test_name "...\n"; \ 46 | /* Gracefully handle print failure in unit tests.*/ \ 47 | auto _ = ::cat::print(string); \ 48 | test_##test_name(); \ 49 | ++tests_passed; \ 50 | } \ 51 | void test_##test_name() 52 | 53 | // `CAT_TEST` should never be `#undef`'d. The redefinable macro `test` exists 54 | // to make this macro more ergonomic. 55 | #pragma clang final(CAT_TEST) 56 | 57 | #define test CAT_TEST 58 | --------------------------------------------------------------------------------