Repository: doodspav/patomic (language: python)

Issue:
Define public API (with default impl)
# To Do:
- bring over all public API from `devel` branch
- document public API with doxygen style comments, and try to come up with an extra doxygen attribute to link public api to requirements
- implement public API minimally (i.e. return nullptr or whatever as long as it meets requirements)

- write BTs and UTs that cover as much as possible


Discussion / hints:

Fix patch:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 84ac005cf..04f6d2b40 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,43 +20,60 @@ project(
 # don't change include order, OptionVariables checks if project is top level
 include(cmake/ProjectIsTopLevel.cmake)
 include(cmake/OptionVariables.cmake)
+include(cmake/CompilerChecks.cmake)
 
 
 # ---- Declare Library ----
 
 # target that we can modify (can't modify ALIAS targets)
 # target name should not be the same as ${PROJECT_NAME}, causes add_subdirectory issues
-set(target_name "patomic-patomic")
+set(target_name "patomic-lib")
 add_library(${target_name} ${build_type})
 
 # alias to cause error at configuration time instead of link time if target is missing
 add_library(patomic::patomic ALIAS ${target_name})
 
-# add /include files to target
-# unfortunately can't have CML file in /include
-target_sources(
-    ${target_name} PRIVATE
-    # include
-    include/patomic/patomic.h
-)
-
-# add /src files to target
+# add all source files to target
+add_subdirectory(include)
 add_subdirectory(src)
 
 
-# ---- Generate Export Headers ----
+# ---- Generate Headers ----
 
 # used in export header generated below
-if(NOT PATOMIC_BUILD_SHARED)
+if(NOT PATOMIC_BUILD_SHARED_LIBS)
     target_compile_definitions(${target_name} PUBLIC PATOMIC_STATIC_DEFINE)
 endif()
 
 # generate header file with export macros prefixed with BASE_NAME
 include(GenerateExportHeader)
-generate_export_header(
-    ${target_name}
+generate_export_header(${target_name}
     BASE_NAME patomic
-    EXPORT_FILE_NAME include/patomic/patomic_export.h
+    INCLUDE_GUARD_NAME PATOMIC_GENERATED_API_EXPORT_H
+    EXPORT_FILE_NAME include/patomic/api/export.h
+)
+
+# generate header file with version macros and functions
+configure_file(
+    cmake/in/version.h.in
+    include/patomic/api/version.h
+    @ONLY
+)
+
+# generate header file with compiler feature support macros
+configure_file(
+    cmake/in/_config.h.in
+    src/include/patomic/_config.h
+    @ONLY
+)
+
+# add generated headers to target
+target_sources(${target_name} PRIVATE
+    # include (generated)
+    "${PROJECT_BINARY_DIR}/include/patomic/api/export.h"
+    "${PROJECT_BINARY_DIR}/include/patomic/api/version.h"
+    # src/include (generated)
+    "${PROJECT_BINARY_DIR}/src/include/patomic/_config.h"
 )
 
 
@@ -64,8 +81,7 @@ generate_export_header(
 
 # hide all symbols by default
 # use SameMajorVersion versioning for shared library lookup
-set_target_properties(
-    ${target_name} PROPERTIES
+set_target_properties(${target_name} PROPERTIES
     C_VISIBILITY_PRESET hidden
     VISIBILITY_INLINES_HIDDEN YES
     VERSION "${PROJECT_VERSION}"
@@ -74,20 +90,29 @@ set_target_properties(
     OUTPUT_NAME "patomic"
 )
 
-# header files generated by CMake
-target_include_directories(
-    ${target_name} SYSTEM PUBLIC
+# header files generated by CMake in /include
+target_include_directories(${target_name} SYSTEM PUBLIC
     "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include>"
 )
 
+# header files generated by CMake in /src/include
+target_include_directories(${target_name} SYSTEM PRIVATE
+    "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src/include>"
+)
+
 # header files from /include
-target_include_directories(
-    ${target_name} ${warning_guard} PUBLIC
+target_include_directories(${target_name} SYSTEM PUBLIC
     "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
 )
 
-# require C90 compiler support
-target_compile_features(${target_name} PUBLIC c_std_90)
+# header files from /src/include
+target_include_directories(${target_name} SYSTEM PRIVATE
+    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src/include>"
+)
+
+# require C90 compiler support for compiling
+# private because using this library can be done from C90/C++98
+target_compile_features(${target_name} PRIVATE c_std_90)
 
 
 # ---- Install Rules ----
diff --git a/CMakePresets.json b/CMakePresets.json
index bdea28a21..e43a605f2 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -94,8 +94,8 @@
       "name": "_patomic-ci-flags-warning-clang",
       "hidden": true,
       "cacheVariables": {
-        "CMAKE_C_FLAGS_INIT": "-Weverything -Werror -Wpedantic -Wno-c++98-compat -Wno-covered-switch-default -Wno-padded -Wno-unused-function -Wno-atomic-alignment -Wno-poison-system-directories",
-        "CMAKE_CXX_FLAGS_INIT": "-Wall -Wextra -Werror -Wpedantic"
+        "CMAKE_C_FLAGS_INIT": "-Weverything -Werror -Wpedantic -Wno-c++98-compat -Wno-covered-switch-default -Wno-padded -Wno-unused-function -Wno-atomic-alignment -Wno-poison-system-directories -Wno-reserved-identifier -Wno-documentation-unknown-command",
+        "CMAKE_CXX_FLAGS_INIT": "-Wall -Wextra -Werror -Wpedantic -Wno-c++17-attribute-extensions"
       }
     },
     {
diff --git a/ci/toolchain/qemu-linux-gnu.cmake b/ci/toolchain/qemu-linux-gnu.cmake
index 9c09f9134..086fea4c6 100644
--- a/ci/toolchain/qemu-linux-gnu.cmake
+++ b/ci/toolchain/qemu-linux-gnu.cmake
@@ -69,7 +69,7 @@ endif()
 
 # ---- Force Use of Cache Variables ----
 
-set(cache_variables )
+unset(cache_variables)
 list(APPEND cache_variables
     "PATOMIC_CI_SYSROOT"
     "PATOMIC_CI_XARCH"
diff --git a/cmake/CompilerChecks.cmake b/cmake/CompilerChecks.cmake
new file mode 100644
index 000000000..c37dfb922
--- /dev/null
+++ b/cmake/CompilerChecks.cmake
@@ -0,0 +1,150 @@
+include(CheckCSourceCompiles)
+
+
+# ---- Check Source Compiles ----
+#
+# Checks that a string containing complete C source code compiles, and sets an
+# output variable to 1 if it does, and 0 if it does not.
+#
+# If all the conditions passed to WILL_SUCCEED_IF_ALL are true, then the check
+# is skipped, and the output variable is set to 1. If no conditions are passed
+# then this does nothing.
+#
+# If any of the conditions passed to WILL_FAIL_IF_ANY_NOT are false, then the
+# check is skipped, and the output variable is set to 0. If no conditions are
+# passed then this does nothing.
+#
+# This function is necessary because check_c_source_compiles will set the
+# output variable to an empty string, which cannot directly be used in a C
+# preprocessing directive in the desired manner.
+# It has the added benefit of skipping costly compilation checks if it can
+# be predetermined that the check will succeed or fail without needing to
+# run it.
+#
+# check_c_source_compiles_or_zero(
+#     SOURCE <source>
+#     OUTPUT_VARIABLE <outputVar>
+#     [WILL_SUCCEED_IF_ALL <condition>...]
+#     [WILL_FAIL_IF_ANY_NOT <condition>...]
+# )
+function(check_c_source_compiles_or_zero)
+
+    # setup what arguments we expect
+
+    cmake_parse_arguments(
+        "ARG"
+        ""
+        "OUTPUT_VARIABLE"
+        "SOURCE;WILL_SUCCEED_IF_ALL;WILL_FAIL_IF_ANY_NOT"
+        ${ARGN}
+    )
+
+
+    # validate arguments
+
+    # setup
+    set(args_valid TRUE)
+    set(func_name "check_c_source_compiles_or_zero")
+
+    # check that SOURCE is set
+    if(NOT ARG_SOURCE)
+        message(WARNING "Option 'SOURCE' must be be specified with a value when invoking '${func_name}'")
+        set(args_valid FALSE)
+    endif()
+
+    # check that OUTPUT_VARIABLE is set
+    if(NOT ARG_OUTPUT_VARIABLE)
+        message(WARNING "Option 'OUTPUT_VARIABLE' must be specified with a value when invoking '${func_name}'")
+        set(args_valid FALSE)
+    endif()
+
+    # check that there are no leftover arguments
+    if(ARG_UNPARSED_ARGUMENTS)
+        message(WARNING "The following arguments were not recognised when invoking '${func_name}': ${ARG_UNPARSED_ARGUMENTS}")
+        set(args_valid FALSE)
+    endif()
+
+    # abort if validation failed
+    if(NOT args_valid)
+        message(FATAL_ERROR "Aborting '${func_name}' due to invalid arguments")
+    endif()
+
+
+    # see if we can skip check due to implicit success
+
+    # setup
+    set(success_count_true 0)
+
+    # check the conditions provided
+    foreach(cond IN LISTS ARG_WILL_SUCCEED_IF_ALL)
+        if(cond)
+            math(EXPR success_count_true "${success_count_true} + 1")
+        endif()
+    endforeach()
+
+    # skip if we can (all conditions are true or all conditions are false)
+    if(ARG_WILL_SUCCEED_IF_ALL)
+        list(LENGTH ARG_WILL_SUCCEED_IF_ALL success_count)
+        if(success_count_true EQUAL success_count)
+            message(STATUS "Performing Test ${ARG_OUTPUT_VARIABLE}")
+            message(STATUS "Performing Test ${ARG_OUTPUT_VARIABLE} - Success (implicit)")
+            set(${ARG_OUTPUT_VARIABLE} 1 PARENT_SCOPE)
+            return()
+        endif()
+    endif()
+
+
+    # see if we can skip check due to implicit failure
+
+    # setup
+    set(failure_count_false 0)
+
+    # check the conditions provided
+    foreach(cond IN LISTS ARG_WILL_FAIL_IF_ANY_NOT)
+        if(NOT cond)
+            math(EXPR failure_count_false "${failure_count_false} + 1")
+        endif()
+    endforeach()
+
+    # skip if we can (any conditions are false)
+    if(ARG_WILL_FAIL_IF_ANY_NOT AND NOT failure_count_false EQUAL 0)
+        message(STATUS "Performing Test ${ARG_OUTPUT_VARIABLE}")
+        message(STATUS "Performing Test ${ARG_OUTPUT_VARIABLE} - Failed (implicit)")
+        set(${ARG_OUTPUT_VARIABLE} 0 PARENT_SCOPE)
+        return()
+    endif()
+
+
+    # defer check to CMake module
+
+    # perform check
+    # shadow parent scope variable so that output message uses its name
+    check_c_source_compiles(
+        "${ARG_SOURCE}"
+        ${ARG_OUTPUT_VARIABLE}
+    )
+    set(result "${${ARG_OUTPUT_VARIABLE}}")
+
+    # propagate result
+    if(result)
+        set(${ARG_OUTPUT_VARIABLE} 1 PARENT_SCOPE)
+    else()
+        set(${ARG_OUTPUT_VARIABLE} 0 PARENT_SCOPE)
+    endif()
+
+endfunction()
+
+
+# ---- Perform Checks ----
+
+# include all compiler checks performed by CMake
+# this relies on the previously defined functions above
+#
+# WARNING:
+#   DO NOT change the include order, checks can depend on other checks
+include(cmake/check/HasHeader.cmake)
+include(cmake/check/HasKeyword.cmake)
+include(cmake/check/HasType.cmake)
+include(cmake/check/HasVariable.cmake)
+include(cmake/check/HasAttribute.cmake)
+include(cmake/check/HasFunction.cmake)
diff --git a/cmake/InstallRules.cmake b/cmake/InstallRules.cmake
index b8c683c91..03a3751e6 100644
--- a/cmake/InstallRules.cmake
+++ b/cmake/InstallRules.cmake
@@ -7,6 +7,8 @@ install(
     "${PROJECT_BINARY_DIR}/include/"  # generated header files
     DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
     COMPONENT ${package_name}-development
+    FILES_MATCHING PATTERN "*.h"
+    PATTERN "CMake*" EXCLUDE
 )
 
 # copy target build output artifacts to OS dependent locations
@@ -35,7 +37,7 @@ configure_file(
 # copy config file for find_package to find
 install(
     FILES "${PROJECT_BINARY_DIR}/cmake/${package_name}-config.cmake"
-    DESTINATION "${PATOMIC_INSTALL_CMAKEDIR}"
+    DESTINATION "${CMAKE_INSTALL_CMAKEDIR}"
     COMPONENT ${package_name}-development
 )
 
@@ -48,7 +50,7 @@ write_basic_package_version_file(
 # copy version file for find_package to find for version check
 install(
     FILES "${PROJECT_BINARY_DIR}/${package_name}-config-version.cmake"
-    DESTINATION "${PATOMIC_INSTALL_CMAKEDIR}"
+    DESTINATION "${CMAKE_INSTALL_CMAKEDIR}"
     COMPONENT ${package_name}-development
 )
 
@@ -56,7 +58,7 @@ install(
 install(
     EXPORT ${package_name}-targets
     NAMESPACE patomic::
-    DESTINATION "${PATOMIC_INSTALL_CMAKEDIR}"
+    DESTINATION "${CMAKE_INSTALL_CMAKEDIR}"
     COMPONENT ${package_name}-development
 )
 
diff --git a/cmake/OptionVariables.cmake b/cmake/OptionVariables.cmake
index 3f5c61e14..8a25389ea 100644
--- a/cmake/OptionVariables.cmake
+++ b/cmake/OptionVariables.cmake
@@ -4,18 +4,17 @@
 
 # ---- Options Summary ----
 
-# -------------------------------------------------------------------------------------------------------------------
-# | Option                       | Availability  | Default                                                          |
-# |==============================|===============|==================================================================|
-# | BUILD_SHARED_LIBS            | Top-Level     | OFF                                                              |
-# | BUILD_TESTING                | Top-Level     | OFF                                                              |
-# | CMAKE_INSTALL_INCLUDEDIR     | Top-Level     | include/${package_name}-${PROJECT_VERSION}                       |
-# |------------------------------|---------------|------------------------------------------------------------------|
-# | PATOMIC_BUILD_SHARED         | Always        | ${BUILD_SHARED_LIBS}                                             |
-# | PATOMIC_BUILD_TESTING        | Always        | ${BUILD_TESTING} AND ${PROJECT_IS_TOP_LEVEL}                     |
-# | PATOMIC_INCLUDES_WITH_SYSTEM | Not Top-Level | ON                                                               |
-# | PATOMIC_INSTALL_CMAKEDIR     | Always        | ${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}-${PROJECT_VERSION} |
-# -------------------------------------------------------------------------------------------------------------------
+# ----------------------------------------------------------------------------------------------------------------
+# | Option                    | Availability  | Default                                                          |
+# |===========================|===============|==================================================================|
+# | BUILD_SHARED_LIBS         | Top-Level     | OFF                                                              |
+# | BUILD_TESTING             | Top-Level     | OFF                                                              |
+# | CMAKE_INSTALL_CMAKEDIR    | Top-Level     | ${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}-${PROJECT_VERSION} |
+# | CMAKE_INSTALL_INCLUDEDIR  | Top-Level     | include/${package_name}-${PROJECT_VERSION}                       |
+# |---------------------------|---------------|------------------------------------------------------------------|
+# | PATOMIC_BUILD_SHARED_LIBS | Always        | ${BUILD_SHARED_LIBS}                                             |
+# | PATOMIC_BUILD_TESTING     | Always        | ${BUILD_TESTING} AND ${PROJECT_IS_TOP_LEVEL}                     |
+# ----------------------------------------------------------------------------------------------------------------
 
 
 # ---- Build Shared ----
@@ -26,35 +25,16 @@ if(PROJECT_IS_TOP_LEVEL)
     option(BUILD_SHARED_LIBS "Build shared libs" OFF)
 endif()
 option(
-    PATOMIC_BUILD_SHARED
-    "Override BUILD_SHARED_LIBS for ${package_name} library"
+    PATOMIC_BUILD_SHARED_LIBS
+    "Override BUILD_SHARED_LIBS for ${package_name} package"
     ${BUILD_SHARED_LIBS}
 )
-mark_as_advanced(PATOMIC_BUILD_SHARED)
 set(build_type STATIC)
-if(PATOMIC_BUILD_SHARED)
+if(PATOMIC_BUILD_SHARED_LIBS)
     set(build_type SHARED)
 endif()
-
-
-# ---- Warning Guard ----
-
-# target_include_directories with SYSTEM modifier will request the compiler to
-# omit warnings from the provided paths, if the compiler supports that.
-# This is to provide a user experience similar to find_package when
-# add_subdirectory or FetchContent is used to consume this project.
-set(warning_guard "")
-if(NOT PROJECT_IS_TOP_LEVEL)
-    option(
-        PATOMIC_INCLUDES_WITH_SYSTEM
-        "Use SYSTEM modifier for ${package_name}'s includes, disabling warnings"
-        ON
-    )
-    mark_as_advanced(PATOMIC_INCLUDES_WITH_SYSTEM)
-    if(PATOMIC_INCLUDES_WITH_SYSTEM)
-        set(warning_guard SYSTEM)
-    endif()
-endif()
+# don't mark BUILD_SHARED_LIBS as advanced
+mark_as_advanced(PATOMIC_BUILD_SHARED_LIBS)
 
 
 # ---- Enable Testing ----
@@ -71,10 +51,11 @@ if(PROJECT_IS_TOP_LEVEL AND BUILD_TESTING)
 endif()
 option(
     PATOMIC_BUILD_TESTING
-    "Override BUILD_TESTING for ${package_name} library"
+    "Override BUILD_TESTING for ${package_name} package"
     ${build_testing}
 )
-set(build_testing )
+unset(build_testing)
+# don't mark BUILD_TESTING as advanced
 mark_as_advanced(PATOMIC_BUILD_TESTING)
 
 
@@ -82,7 +63,7 @@ mark_as_advanced(PATOMIC_BUILD_TESTING)
 
 # Adds an extra directory to the include path by default, so that when you link
 # against the target, you get `<prefix>/include/<package>-X` added to your
-# include paths rather than `<prefix/include`.
+# include paths rather than `<prefix>/include`.
 # This doesn't affect include paths used by consumers of this project, but helps
 # prevent consumers having access to other projects in the same include
 # directory (e.g. /usr/include).
@@ -93,10 +74,13 @@ mark_as_advanced(PATOMIC_BUILD_TESTING)
 if(PROJECT_IS_TOP_LEVEL)
     set(
         CMAKE_INSTALL_INCLUDEDIR "include/${package_name}-${PROJECT_VERSION}"
-        CACHE STRING ""
+        CACHE STRING "CMake top level include location relative to the install prefix"
     )
+    # set it back to PATH type for GUI assistance
+    set_property(CACHE CMAKE_INSTALL_INCLUDEDIR PROPERTY TYPE PATH)
     # marked as advanced in GNUInstallDirs version, so we follow their lead
     mark_as_advanced(CMAKE_INSTALL_INCLUDEDIR)
+    # CMAKE_INSTALL_FULL_INCLUDEDIR will be generated by GNUInstallDirs below
 endif()
 
 
@@ -113,16 +97,35 @@ include(GNUInstallDirs)
 # and uses the full version to aid in debugging.
 # This doesn't affect include paths used by consumers of this project.
 # The variable type is STRING rather than PATH, because otherwise passing
-# -DPATOMIC_INSTALL_CMAKEDIR=lib/cmake on the command line would expand to an
+# -DCMAKE_INSTALL_CMAKEDIR=lib/cmake on the command line would expand to an
 # absolute path with the base being the current CMake directory, leading to
 # unexpected errors.
 #
-# Note: this will yield the lowest compatible version on platforms that sort
-# their directories' contents unless you set CMAKE_FIND_PACKAGE_SORT_DIRECTION
-# to DEC before calling find_package.
-set(
-    PATOMIC_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}-${PROJECT_VERSION}"
-    CACHE STRING "CMake ${package_name} package's config location relative to the install prefix"
-)
-# depends on CMAKE_INSTALL_LIBDIR which is marked as advanced in GNUInstallDirs
-mark_as_advanced(PATOMIC_INSTALL_CMAKEDIR)
+# Note: in order to get the latest compatible version by default, you should
+# set CMAKE_FIND_PACKAGE_SORT_ORDER to NATURAL before calling find_package.
+if(PROJECT_IS_TOP_LEVEL)
+    set(
+        CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${package_name}-${PROJECT_VERSION}"
+        CACHE STRING "(unofficial) CMake top level config location relative to the install prefix"
+    )
+    # set it back to PATH type for GUI assistance
+    set_property(CACHE CMAKE_INSTALL_CMAKEDIR PROPERTY TYPE PATH)
+    # depends on CMAKE_INSTALL_LIBRDIR which is marked as advanced in GNUInstallDirs
+    mark_as_advanced(CMAKE_INSTALL_CMAKEDIR)
+    # generate absolute path version
+    set(dir "LIBDIR")  # use LIBDIR since that forms the root of CMAKEDIR
+    set(dir_param )
+    if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20)
+        set(dir_param "${dir}")
+    endif()
+    GNUInstallDirs_get_absolute_install_dir(
+        CMAKE_INSTALL_FULL_CMAKEDIR
+        CMAKE_INSTALL_CMAKEDIR
+        ${dir_param}
+    )
+    unset(dir_param)
+    unset(dir)
+elseif(NOT CMAKE_INSTALL_CMAKEDIR AND NOT CMAKE_SKIP_INSTALL_RULES)
+    # required because this is unofficial and has no default set by GNUInstallDirs
+    message(FATAL_ERROR "Cache variable CMAKE_INSTALL_CMAKEDIR was not defined; must be set manually")
+endif()
diff --git a/cmake/check/HasAttribute.cmake b/cmake/check/HasAttribute.cmake
new file mode 100644
index 000000000..bdbe07297
--- /dev/null
+++ b/cmake/check/HasAttribute.cmake
@@ -0,0 +1,37 @@
+# ---- Has Attribute ----
+
+# -------------------------------------------------------------------------------------------------
+# | Variable                   | Check                                                            |
+# |============================|==================================================================|
+# | COMPILER_HAS_NORETURN      | '_Noreturn' is available as a function attribute                 |
+# | COMPILER_HAS_NORETURN_ATTR | '__attribute__((noreturn))' is available as a function attribute |
+# | COMPILER_HAS_NORETURN_DSPC | '__declspec(noreturn)' is available as a function attribute      |
+# -------------------------------------------------------------------------------------------------
+
+
+# '_Noreturn' is available as a function attribute
+check_c_source_compiles_or_zero(
+    SOURCE
+        "static _Noreturn void loop(void) { while (1); } \n\
+         int main(void) { loop(); }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_NORETURN
+)
+
+# '__attribute__((noreturn))' is available as a function attribute
+check_c_source_compiles_or_zero(
+    SOURCE
+        "static __attribute__((noreturn)) void loop(void) { while (1); } \n\
+         int main(void) { loop(); }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_NORETURN_ATTR
+)
+
+# '__declspec(noreturn)' is available as a function attribute
+check_c_source_compiles_or_zero(
+    SOURCE
+        "static __declspec(noreturn) void loop(void) { while (1); } \n\
+         int main(void) { loop(); }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_NORETURN_DSPC
+)
diff --git a/cmake/check/HasFunction.cmake b/cmake/check/HasFunction.cmake
new file mode 100644
index 000000000..ac29a419b
--- /dev/null
+++ b/cmake/check/HasFunction.cmake
@@ -0,0 +1,57 @@
+# ---- Has Function ----
+
+# -----------------------------------------------------------------------------------------------------------------------------------------------
+# | Variable                         | Check                                                                                                    |
+# |==================================|==========================================================================================================|
+# | COMPILER_HAS_MS_ASSUME           | '__assume(int)' is available as a function                                                               |
+# | COMPILER_HAS_BUILTIN_UNREACHABLE | '__builtin_unreachable(void)' is available as a function                                                 |
+# | COMPILER_HAS_WCHAR_FWIDE         | '<wchar.h>' header is available and makes 'fwide(FILE*, int)' available as a function                    |
+# | COMPILER_HAS_WCHAR_FWPRINTF      | '<wchar.h>' header is available and makes 'fwprintf(FILE*, const wchar_t*, ...)' available as a function |
+# -----------------------------------------------------------------------------------------------------------------------------------------------
+
+
+# '__assume(int)' is available as a function
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(int argc, char **argv) {  \n\
+             if (argc < 1) { __assume(0); } \n\
+             return (int) argv[0][0];       \n\
+         }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_MS_ASSUME
+)
+
+# '__builtin_unreachable(void)' is available as a function
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(int argc, char **argv) {              \n\
+             if (argc < 1) { __builtin_unreachable(); } \n\
+             return (int) argv[0][0];                   \n\
+         }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_BUILTIN_UNREACHABLE
+)
+
+# '<wchar.h>' header is available and makes 'fwide(FILE*, int)' available as a function
+check_c_source_compiles_or_zero(
+    SOURCE
+        "#include <wchar.h> \n\
+         #include <stdio.h> \n\
+         int main(void) { return fwide(stdout, 0); }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_WCHAR_FWIDE
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_WCHAR_H}
+)
+
+# '<wchar.h>' header is available and makes 'fwprintf(FILE*, const wchar_t*, ...)' available as a function
+check_c_source_compiles_or_zero(
+    SOURCE
+        "#include <wchar.h> \n\
+         #include <stdio.h> \n\
+         int main(void) { return fwprintf(stdout, L\"test\"); }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_WCHAR_FWPRINTF
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_WCHAR_H}
+)
diff --git a/cmake/check/HasHeader.cmake b/cmake/check/HasHeader.cmake
new file mode 100644
index 000000000..a98aa9d32
--- /dev/null
+++ b/cmake/check/HasHeader.cmake
@@ -0,0 +1,27 @@
+# ---- Has Header ----
+
+# ----------------------------------------------------------
+# | Variable              | Check                          |
+# |=======================|================================|
+# | COMPILER_HAS_STDINT_H | <stdint.h> header is available |
+# | COMPILER_HAS_WCHAR_H  | <wchar.h> header is available  |
+# ----------------------------------------------------------
+
+
+# <stdint.h> header is available
+check_c_source_compiles_or_zero(
+    SOURCE
+        "#include <stdint.h> \n\
+         int main(void) {}"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_STDINT_H
+)
+
+# <wchar.h> header is available
+check_c_source_compiles_or_zero(
+    SOURCE
+        "#include <wchar.h> \n\
+         int main(void) {}"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_WCHAR_H
+)
diff --git a/cmake/check/HasKeyword.cmake b/cmake/check/HasKeyword.cmake
new file mode 100644
index 000000000..f723fa087
--- /dev/null
+++ b/cmake/check/HasKeyword.cmake
@@ -0,0 +1,16 @@
+# ---- Has Keyword ----
+
+# ----------------------------------------------------------------------
+# | Variable               | Check                                     |
+# |========================|===========================================|
+# | COMPILER_HAS_EXTN      | '__extension__' is available as a keyword |
+# ----------------------------------------------------------------------
+
+
+# '__extension__' is available as a keyword
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { __extension__ int x = 0; return x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_EXTN
+)
diff --git a/cmake/check/HasType.cmake b/cmake/check/HasType.cmake
new file mode 100644
index 000000000..adcd13f8c
--- /dev/null
+++ b/cmake/check/HasType.cmake
@@ -0,0 +1,112 @@
+# ---- Has Type ----
+
+# ---------------------------------------------------------------------------------------------------------------------------
+# | Variable                        | Check                                                                                 |
+# |=================================|=======================================================================================|
+# | COMPILER_HAS_LONG_LONG          | 'long long' is available as a type                                                    |
+# | COMPILER_HAS_LONG_LONG_EXTN     | '__extension__ long long' is available as a type                                      |
+# | COMPILER_HAS_MS_INT128          | '__int128' is available as a type                                                     |
+# | COMPILER_HAS_MS_INT128_EXTN     | '__extension__ __int128' is available as a type                                       |
+# | COMPILER_HAS_STDINT_INTPTR      | <stdint.h> header is available and makes 'intptr_t' available as a type               |
+# | COMPILER_HAS_STDINT_INTPTR_EXTN | <stdint.h> header is available and makes '__extension__ intptr_t' available as a type |
+# | COMPILER_HAS_STDDEF_INTPTR      | <stddef.h> header is available and makes 'intptr_t' available as a type               |
+# | COMPILER_HAS_STDDEF_INTPTR_EXTN | <stddef.h> header is available and makes '__extension__ intptr_t' available as a type |
+# ---------------------------------------------------------------------------------------------------------------------------
+
+
+# 'long long' is available as a type
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { long long x = 0; return (int) x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_LONG_LONG
+)
+
+# '__extension__ long long' is available as a type
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { __extension__ long long x = 0; return (int) x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_LONG_LONG_EXTN
+    WILL_SUCCEED_IF_ALL
+        ${COMPILER_HAS_LONG_LONG}
+        ${COMPILER_HAS_EXTN}
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_EXTN}
+)
+
+# '__int128' is available as a type
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { __int128 x = 0; return (int) x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_MS_INT128
+)
+
+# '__extension__ __int128' is available as a type
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { __extension__ __int128 x = 0; return (int) x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_MS_INT128_EXTN
+    WILL_SUCCEED_IF_ALL
+        ${COMPILER_HAS_MS_INT128}
+        ${COMPILER_HAS_EXTN}
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_EXTN}
+)
+
+# <stdint.h> header is available and makes 'intptr_t' available as a type
+check_c_source_compiles_or_zero(
+    SOURCE
+        "#include <stdint.h> \n\
+         int main(void) { intptr_t x = 0; return (int) x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_STDINT_INTPTR
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_STDINT_H}
+)
+
+# <stdint.h> header is available and makes '__extension__ intptr_t' available as a type
+check_c_source_compiles_or_zero(
+    SOURCE
+        "#include <stdint.h> \n\
+         int main(void) { __extension__ intptr_t x = 0; return (int) x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_STDINT_INTPTR_EXTN
+    WILL_SUCCEED_IF_ALL
+        ${COMPILER_HAS_STDINT_INTPTR}
+        ${COMPILER_HAS_EXTN}
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_EXTN}
+        ${COMPILER_HAS_STDINT_H}
+)
+
+# <stddef.h> header is available and makes 'intptr_t' available as a type
+check_c_source_compiles_or_zero(
+    SOURCE
+        "#include <stddef.h> \n\
+         int main(void) { intptr_t x = 0; return (int) x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_STDDEF_INTPTR
+)
+
+# <stddef.h> header is available and makes '__extension__ intptr_t' available as a type
+set(intptr_in_stddef_if_in_stdint 1)
+if(COMPILER_HAS_STDINT_INTPTR AND NOT COMPILER_HAS_STDDEF_INTPTR)
+    set(intptr_in_stddef_if_in_stdint 0)
+endif()
+check_c_source_compiles_or_zero(
+    SOURCE
+        "#include <stddef.h> \n\
+         int main(void) { __extension__ intptr_t x = 0; return (int) x; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_STDDEF_INTPTR_EXTN
+    WILL_SUCCEED_IF_ALL
+        ${COMPILER_HAS_STDDEF_INTPTR}
+        ${COMPILER_HAS_EXTN}
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_EXTN}
+        ${intptr_in_stddef_if_in_stdint}
+)
+unset(intptr_in_stddef_if_in_stdint)
diff --git a/cmake/check/HasVariable.cmake b/cmake/check/HasVariable.cmake
new file mode 100644
index 000000000..4d2058094
--- /dev/null
+++ b/cmake/check/HasVariable.cmake
@@ -0,0 +1,76 @@
+# ---- Has Variable ----
+
+# -----------------------------------------------------------------------------------------------------------
+# | Variable                              | Check                                                           |
+# |=======================================|=================================================================|
+# | COMPILER_HAS_FUNC                     | '__func__' is available as a variable                           |
+# | COMPILER_HAS_FUNC_EXTN                | '__extension__ __func__' is available as a variable             |
+# | COMPILER_HAS_GNU_FUNCTION             | '__FUNCTION__' is available as a variable                       |
+# | COMPILER_HAS_GNU_FUNCTION_EXTN        | '__extension__ __FUNCTION__' is available as a variable         |
+# | COMPILER_HAS_GNU_PRETTY_FUNCTION      | '__PRETTY_FUNCTION__' is available as a variable                |
+# | COMPILER_HAS_GNU_PRETTY_FUNCTION_EXTN | '__extension__ __PRETTY_FUNCTION__' is available as a variable  |
+# -----------------------------------------------------------------------------------------------------------
+
+
+# '__func__' is available as a variable
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { return (int) __func__[0]; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_FUNC
+)
+
+# '__extension__ __func__' is available as a variable
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { return (int) __extension__ __func__[0]; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_FUNC_EXTN
+    WILL_SUCCEED_IF_ALL
+        ${COMPILER_HAS_FUNC}
+        ${COMPILER_HAS_EXTN}
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_EXTN}
+)
+
+# '__FUNCTION__' is available as a variable
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { return (int) __FUNCTION__[0]; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_GNU_FUNCTION
+)
+
+# '__extension__ __FUNCTION__' is available as a variable
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { return (int) __extension__ __FUNCTION__[0]; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_GNU_FUNCTION_EXTN
+    WILL_SUCCEED_IF_ALL
+        ${COMPILER_HAS_GNU_FUNCTION}
+        ${COMPILER_HAS_EXTN}
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_EXTN}
+)
+
+# '__PRETTY_FUNCTION__' is available as a variable
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { return (int) __PRETTY_FUNCTION__[0]; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_GNU_PRETTY_FUNCTION
+)
+
+# '__extension__ __PRETTY_FUNCTION__' is available as a variable
+check_c_source_compiles_or_zero(
+    SOURCE
+        "int main(void) { return (int) __extension__ __PRETTY_FUNCTION__[0]; }"
+    OUTPUT_VARIABLE
+        COMPILER_HAS_GNU_PRETTY_FUNCTION_EXTN
+    WILL_SUCCEED_IF_ALL
+        ${COMPILER_HAS_GNU_PRETTY_FUNCTION}
+        ${COMPILER_HAS_EXTN}
+    WILL_FAIL_IF_ANY_NOT
+        ${COMPILER_HAS_EXTN}
+)
diff --git a/cmake/in/_config.h.in b/cmake/in/_config.h.in
new file mode 100644
index 000000000..f1c0fae81
--- /dev/null
+++ b/cmake/in/_config.h.in
@@ -0,0 +1,348 @@
+#ifndef PATOMIC_GENERATED_CONFIG_H
+#define PATOMIC_GENERATED_CONFIG_H
+
+
+#ifndef PATOMIC_HAS_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__' is available as a keyword.
+     *
+     * @note
+     *   Usually required: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_EXTN @COMPILER_HAS_EXTN@
+#endif
+
+
+#ifndef PATOMIC_HAS_STDINT_H
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stdint.h> header is available.
+     *
+     * @note
+     *   Usually requires: C99.
+     */
+    #define PATOMIC_HAS_STDINT_H @COMPILER_HAS_STDINT_H@
+#endif
+
+
+#ifndef PATOMIC_HAS_WCHAR_H
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <wchar.h> header is available.
+     *
+     * @note
+     *   Usually requires: C95.
+     */
+    #define PATOMIC_HAS_WCHAR_H @COMPILER_HAS_WCHAR_H@
+#endif
+
+
+#ifndef PATOMIC_HAS_LONG_LONG
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   'long long' is available as a type.
+     *
+     * @note
+     *   Usually requires: C99.
+     */
+    #define PATOMIC_HAS_LONG_LONG @COMPILER_HAS_LONG_LONG@
+#endif
+
+
+#ifndef PATOMIC_HAS_LONG_LONG_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ long long' is available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_LONG_LONG_EXTN @COMPILER_HAS_LONG_LONG_EXTN@
+#endif
+
+
+#ifndef PATOMIC_HAS_MS_INT128
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__int128' is available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_MS_INT128 @COMPILER_HAS_MS_INT128@
+#endif
+
+
+#ifndef PATOMIC_HAS_MS_INT128_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ __int128' is available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_MS_INT128_EXTN @COMPILER_HAS_MS_INT128_EXTN@
+#endif
+
+
+#ifndef PATOMIC_HAS_STDINT_INTPTR
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stdint.h> header is available and makes 'intptr_t' available as a
+     *   type.
+     *
+     * @note
+     *   Usually requires: C99 (but the type is optional even then).
+     */
+    #define PATOMIC_HAS_STDINT_INTPTR @COMPILER_HAS_STDINT_INTPTR@
+#endif
+
+
+#ifndef PATOMIC_HAS_STDINT_INTPTR_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stdint.h> header is available and makes '__extension__ intptr_t'
+     *   available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_STDINT_INTPTR_EXTN @COMPILER_HAS_STDINT_INTPTR_EXTN@
+#endif
+
+
+#ifndef PATOMIC_HAS_STDDEF_INTPTR
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stddef.h> header is available and makes 'intptr_t' available as a
+     *   type.
+     *
+     * @note
+     *   Usually requires: old compilers not supporting <stdint.h> (pre-C99).
+     *   Microsoft puts intptr_t here in old versions of msvc.
+     */
+    #define PATOMIC_HAS_STDDEF_INTPTR @COMPILER_HAS_STDDEF_INTPTR@
+#endif
+
+
+#ifndef PATOMIC_HAS_STDDEF_INTPTR_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stddef.h> header is available and makes '__extension__ intptr_t'
+     *   available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_STDDEF_INTPTR_EXTN @COMPILER_HAS_STDDEF_INTPTR_EXTN@
+#endif
+
+
+#ifndef PATOMIC_HAS_FUNC
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__func__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: C99.
+     */
+    #define PATOMIC_HAS_FUNC @COMPILER_HAS_FUNC@
+#endif
+
+
+#ifndef PATOMIC_HAS_FUNC_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ __func__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_FUNC_EXTN @COMPILER_HAS_FUNC_EXTN@
+#endif
+
+
+#ifndef PATOMIC_HAS_GNU_FUNCTION
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__FUNCTION__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_GNU_FUNCTION @COMPILER_HAS_GNU_FUNCTION@
+#endif
+
+
+#ifndef PATOMIC_HAS_GNU_FUNCTION_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ __FUNCTION__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_GNU_FUNCTION_EXTN @COMPILER_HAS_GNU_FUNCTION_EXTN@
+#endif
+
+
+#ifndef PATOMIC_HAS_GNU_PRETTY_FUNCTION
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__PRETTY_FUNCTION__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_GNU_PRETTY_FUNCTION @COMPILER_HAS_GNU_PRETTY_FUNCTION@
+#endif
+
+
+#ifndef PATOMIC_HAS_GNU_PRETTY_FUNCTION_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ __PRETTY_FUNCTION__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_GNU_PRETTY_FUNCTION_EXTN @COMPILER_HAS_GNU_PRETTY_FUNCTION_EXTN@
+#endif
+
+
+#ifndef PATOMIC_HAS_NORETURN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '_Noreturn' is available as a function attribute.
+     *
+     * @note
+     *   Usually requires: C11.
+     */
+    #define PATOMIC_HAS_NORETURN @COMPILER_HAS_NORETURN@
+#endif
+
+
+#ifndef PATOMIC_HAS_NORETURN_ATTR
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__attribute__((noreturn))' is available as a function attribute.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_NORETURN_ATTR @COMPILER_HAS_NORETURN_ATTR@
+#endif
+
+
+#ifndef PATOMIC_HAS_NORETURN_DSPC
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__declspec(noreturn)' is available as a function attribute.
+     *
+     * @note
+     *   Usually requires: Microsoft compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_NORETURN_DSPC @COMPILER_HAS_NORETURN_DSPC@
+#endif
+
+
+#ifndef PATOMIC_HAS_MS_ASSUME
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__assume(int)' is available as a function.
+     *
+     * @note
+     *   Usually requires: Microsoft compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_MS_ASSUME @COMPILER_HAS_MS_ASSUME@
+#endif
+
+
+#ifndef PATOMIC_HAS_BUILTIN_UNREACHABLE
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__builtin_unreachable(void)' is available as a function.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_BUILTIN_UNREACHABLE @COMPILER_HAS_BUILTIN_UNREACHABLE@
+#endif
+
+
+#ifndef PATOMIC_HAS_WCHAR_FWIDE
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <wchar.h> header is available and makes 'fwide(FILE*, int)' available
+     *   as a function.
+     *
+     * @note
+     *   Usually requires: C95.
+     */
+    #define PATOMIC_HAS_WCHAR_FWIDE @COMPILER_HAS_WCHAR_FWIDE@
+#endif
+
+
+#ifndef PATOMIC_HAS_WCHAR_FWPRINTF
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <wchar.h> header is available and makes
+     *   'fwprintf(FILE*, const wchar_t*, ...)' available as a function.
+     *
+     * @note
+     *   Usually requires: C95.
+     */
+    #define PATOMIC_HAS_WCHAR_FWPRINTF @COMPILER_HAS_WCHAR_FWPRINTF@
+#endif
+
+
+#endif  /* PATOMIC_GENERATED_CONFIG_H */
diff --git a/cmake/in/version.h.in b/cmake/in/version.h.in
new file mode 100644
index 000000000..7c9f45e0c
--- /dev/null
+++ b/cmake/in/version.h.in
@@ -0,0 +1,152 @@
+#ifndef PATOMIC_GENERATED_API_VERSION_H
+#define PATOMIC_GENERATED_API_VERSION_H
+
+#include <patomic/api/export.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   The full version string the library was built as.
+ *
+ * @note
+ *   The format of this string depends on how the library is built. It will
+ *   always be a valid Semantic Version string.
+ */
+#define PATOMIC_VERSION_STRING "@patomic_VERSION@"
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   The major version the library was built as.
+ */
+#define PATOMIC_VERSION_MAJOR @patomic_VERSION_MAJOR@
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   The minor version the library was built as.
+ */
+#define PATOMIC_VERSION_MINOR @patomic_VERSION_MINOR@
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   The patch version the library was built as.
+ */
+#define PATOMIC_VERSION_PATCH @patomic_VERSION_PATCH@
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   If the library is semver compatible with the requested version.
+ *
+ * @warning
+ *   The special case of major version 0 is not accounted for. Under semver
+ *   rules, minor and patch version differences are incompatible when the major
+ *   version is 0, however this macro may consider them compatible.
+ */
+#define PATOMIC_VERSION_COMPATIBLE_WITH(major, minor, patch) \
+    ( (major) == PATOMIC_VERSION_MAJOR &&                    \
+      (minor) <= PATOMIC_VERSION_MINOR &&                    \
+      ((minor) < PATOMIC_VERSION_MINOR || (patch) <= PATOMIC_VERSION_PATCH) )
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   Provides the full version string the library was built as. This value is
+ *   identical to the PATOMIC_VERSION_STRING macro value.
+ *
+ * @note
+ *   The format of this string depends on how the library is built. It will
+ *   always be a valid Semantic Version string.
+ *
+ * @returns
+ *   The full version string the library was built as.
+ */
+PATOMIC_EXPORT const char *
+patomic_version_string(void);
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   Provides the major version the library was built as. This value is
+ *   identical to the PATOMIC_VERSION_MAJOR macro value.
+ *
+ * @returns
+ *   The major version the library was built as.
+ */
+PATOMIC_EXPORT int
+patomic_version_major(void);
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   Provides the minor version the library was built as. This value is
+ *   identical to the PATOMIC_VERSION_MINOR macro value.
+ *
+ * @returns
+ *   The minor version the library was built as.
+ */
+PATOMIC_EXPORT int
+patomic_version_minor(void);
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   Provides the patch version the library was built as. This value is
+ *   identical to the PATOMIC_VERSION_PATCH macro value.
+ *
+ * @returns
+ *   The patch version the library was built as.
+ */
+PATOMIC_EXPORT int
+patomic_version_patch(void);
+
+
+/**
+ * @addtogroup version
+ *
+ * @brief
+ *   Checks if the version the library was built as is compatible with the
+ *   requested version, using Semantic Versioning. This value is identical to
+ *   the PATOMIC_VERSION_COMPATIBLE_WITH macro value.
+ *
+ * @warning
+ *   The special case of major version 0 is not accounted for. Under semver
+ *   rules, minor and patch version differences are incompatible when the major
+ *   version is 0, however this function may consider them compatible.
+ *
+ * @returns
+ *   If the library is compatible returns 1, otherwise returns 0.
+ */
+PATOMIC_EXPORT int
+patomic_version_compatible_with(int major, int minor, int patch);
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_GENERATED_API_VERSION_H */
diff --git a/docs/style_guide.txt b/docs/style_guide.txt
new file mode 100644
index 000000000..db084c7d1
--- /dev/null
+++ b/docs/style_guide.txt
@@ -0,0 +1,8 @@
+- one line between header guard, includes, and extern C thing
+- two lines between major parts of the file
+- end of header guard shall have a comment of the format /* PATOMIC_NAME_H */
+- how to add more compiler checks?
+
+
+
+- document naming for tests, including DeathTest and SlowTest suffixes
\ No newline at end of file
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
new file mode 100644
index 000000000..fe3307149
--- /dev/null
+++ b/include/CMakeLists.txt
@@ -0,0 +1,2 @@
+# add all subdirectories
+add_subdirectory(patomic)
diff --git a/include/patomic/CMakeLists.txt b/include/patomic/CMakeLists.txt
new file mode 100644
index 000000000..253b11868
--- /dev/null
+++ b/include/patomic/CMakeLists.txt
@@ -0,0 +1,7 @@
+# add all subdirectories
+add_subdirectory(api)
+
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    patomic.h
+)
diff --git a/include/patomic/api/CMakeLists.txt b/include/patomic/api/CMakeLists.txt
new file mode 100644
index 000000000..510078bd0
--- /dev/null
+++ b/include/patomic/api/CMakeLists.txt
@@ -0,0 +1,15 @@
+# add all subdirectories
+add_subdirectory(ops)
+
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    align.h
+    combine.h
+    core.h
+    feature_check.h
+    ids.h
+    memory_order.h
+    ops.h
+    options.h
+    transaction.h
+)
diff --git a/include/patomic/api/align.h b/include/patomic/api/align.h
new file mode 100644
index 000000000..57627ae5a
--- /dev/null
+++ b/include/patomic/api/align.h
@@ -0,0 +1,163 @@
+#ifndef PATOMIC_API_ALIGN_H
+#define PATOMIC_API_ALIGN_H
+
+#include <patomic/api/export.h>
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup align
+ *
+ * @brief
+ *   The alignments required for atomic operations on the specified type to be
+ *   valid.
+ *
+ * @warning
+ *   Not meeting the alignment requirements will result in undefined behaviour.
+ *
+ * @note
+ *   All patomic APIs returning this type guarantee that "recommended" and
+ *   "minimum" are a positive power of 2, and that "minimum" is never larger
+ *   than "recommended". The "size_within" value has no restrictions.
+ *
+ * @details
+ *   Within the semantics of this type, buffers are considered aligned to the
+ *   "recommended" alignment if they meet its alignment requirement.           \n
+ *   If "size_within" is zero, buffers are considered aligned to the "minimum"
+ *   alignment if they meet its alignment requirement.                         \n
+ *   However, if "size_within" is non-zero, then in addition to meeting the
+ *   "minimum" alignment requirement, the buffer would also need to entirely
+ *   reside within a buffer with size and alignment of "size_within".          \n
+ *   The intention of this is to communicate that an object is suitably aligned
+ *   if it does not cross a cache-line boundary.
+ *
+ * @example
+ *   - type: int (size=4, align=4)                                              \n
+ *   - operation: exchange                                                      \n
+ *   - potential assembly (x86): LOCK XCHG, no alignment required               \n
+ *   - atomic if: always                                                        \n
+ *   - example value: {.recommended=4, .minimum=1, .size_within=0}              \n
+ *
+ * @example
+ *   - type: avx_128 (size=16, align=16)                                        \n
+ *   - operation: load                                                          \n
+ *   - potentially assembly (x86) VMOVDQU, no alignment required                \n
+ *   - atomic if: whole object sits in a single cache line (e.g. 64 bytes)      \n
+ *   - example value: {.recommended=16, .minimum=1, .sie_within=4}              \n
+ */
+typedef struct {
+
+    /** @brief Alignment required by the C language (always valid). */
+    size_t recommended;
+
+    /** @brief Alignment required by the architecture (conditionally valid). */
+    size_t minimum;
+
+    /** @brief
+     *   Size and alignment of a hypothetical buffer within which the object
+     *   must reside for "minimum" to be considered valid, unless it is 0 in
+     *   which case "minimum" is always valid.
+     */
+    size_t size_within;
+
+} patomic_align_t;
+
+
+/**
+ * @addtogroup align
+ *
+ * @brief
+ *   Represents the compile-time upper bound of the cache line size for the
+ *   target platform.
+ *
+ * @warning
+ *   Changing this value is NOT an ABI break, with no major version bump needed.
+ *   The benefit is that the value can be updated frequently to more closely
+ *   match the true value on a wider range of platforms as they become known.
+ *
+ * @note
+ *   The value is always a power of 2.
+ */
+#undef PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE
+#define PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE ((size_t) 128)
+
+
+/**
+ * @addtogroup align
+ *
+ * @brief
+ *   Returns the runtime upper bound of the cache line size for the target
+ *   platform; may provide a more accurate value than the corresponding macro.
+ *
+ * @note
+ *   The value will not return a value larger than its unstable macro
+ *   equivalent but may return a smaller value.
+ *
+ * @note
+ *   The value returned is always a power of 2.
+ */
+PATOMIC_EXPORT size_t
+patomic_cache_line_size(void);
+
+
+/**
+ * @addtogroup align
+ *
+ * @brief
+ *   Checks that the pointer meets the recommended alignment according to the
+ *   semantics of patomic_align_t.
+ *
+ * @note
+ *   The check will always fail if the recommended alignment is not a positive
+ *   power of 2.
+ *
+ * @note
+ *   Internal casts may rely on implementation defined behaviour.
+ *
+ * @warning
+ *   A successful check does not imply that the recommended alignment is a valid
+ *   alignment for an object according to the C standard.
+ */
+PATOMIC_EXPORT int
+patomic_align_meets_recommended(
+    const volatile void *ptr,
+    patomic_align_t align
+);
+
+
+/**
+ * @addtogroup align
+ *
+ * @brief
+ *   Checks that the buffer represented by the pointer and its width meets the
+ *   minimum alignment according to the semantics of patomic_align_t.
+ *
+ * @note
+ *   The check will always fail if the minimum alignment is not a positive
+ *   power of 2.
+ *
+ * @note
+ *   Internal casts may rely on implementation defined behaviour.
+ *
+ * @warning
+ *   A successful check does not imply that the minimum alignment is a valid
+ *   alignment for an object according to the C standard.
+ */
+PATOMIC_EXPORT int
+patomic_align_meets_minimum(
+    const volatile void *ptr,
+    patomic_align_t align,
+    size_t width
+);
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_ALIGN_H */
diff --git a/include/patomic/api/combine.h b/include/patomic/api/combine.h
new file mode 100644
index 000000000..00c0fedfc
--- /dev/null
+++ b/include/patomic/api/combine.h
@@ -0,0 +1,81 @@
+#ifndef PATOMIC_API_COMBINE_H
+#define PATOMIC_API_COMBINE_H
+
+#include "core.h"
+
+#include <patomic/api/export.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup combine
+ *
+ * @brief
+ *   Combines two atomic structs with implicit memory order operations. The
+ *   first struct always takes priority, and only missing members are copied
+ *   over from the second struct.
+ *
+ * @details
+ *   Initially, the .ops member may be modified. If it is modified, the .align
+ *   member may also be modified to meet the requirements for all the operations
+ *   now present.
+ *
+ * @note
+ *   It is advisable to sort structs by the .align member when combining more
+ *   than two structs, in order to end up with the least restrictive values for
+ *   the .align member.
+ *
+ * @param priority
+ *   Struct which takes priority if both structs support an operation, and into
+ *   which unsupported operations are added from the other struct.
+ *
+ * @param other
+ *   Struct to combine into priority struct.
+ */
+PATOMIC_EXPORT void
+patomic_combine(
+    patomic_t *priority,
+    const patomic_t *other
+);
+
+
+/**
+ * @addtogroup combine
+ *
+ * @brief
+ *   Combines two atomic structs with explicit memory order operations. The
+ *   first struct always takes priority, and only missing members are copied
+ *   over from the second struct.
+ *
+ * @details
+ *   Initially, the .ops member may be modified. If it is modified, the .align
+ *   member may also be modified to meet the requirements for all the operations
+ *   now present.
+ *
+ * @note
+ *   It is advisable to sort structs by the .align member when combining more
+ *   than two structs, in order to end up with the least restrictive values for
+ *   the .align member.
+ *
+ * @param priority
+ *   Struct which takes priority if both structs support an operation, and into
+ *   which unsupported operations are added from the other struct.
+ *
+ * @param other
+ *   Struct to combine into priority struct.
+ */
+PATOMIC_EXPORT void
+patomic_combine_explicit(
+    patomic_explicit_t *priority,
+    const patomic_explicit_t *other
+);
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_COMBINE_H */
diff --git a/include/patomic/api/core.h b/include/patomic/api/core.h
new file mode 100644
index 000000000..be282f199
--- /dev/null
+++ b/include/patomic/api/core.h
@@ -0,0 +1,78 @@
+#ifndef PATOMIC_API_CORE_H
+#define PATOMIC_API_CORE_H
+
+#include "align.h"
+#include "ops.h"
+#include "transaction.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup patomic
+ *
+ * @brief
+ *   Struct containing all information and functionality required to perform
+ *   atomic operations with implicit memory order.
+ */
+typedef struct {
+
+    /** @brief Atomic operations with implicit memory order. */
+    patomic_ops_t ops;
+
+    /** @brief Alignment requirements for atomic operations. */
+    patomic_align_t align;
+
+} patomic_t;
+
+
+/**
+ * @addtogroup patomic
+ *
+ * @brief
+ *   Struct containing all information and functionality required to perform
+ *   atomic operations with explicit memory order.
+ */
+typedef struct {
+
+    /** @brief Atomic operations with explicit memory order. */
+    patomic_ops_explicit_t ops;
+
+    /** @brief Alignment requirements for atomic operations. */
+    patomic_align_t align;
+
+} patomic_explicit_t;
+
+
+/**
+ * @addtogroup patomic
+ *
+ * @brief
+ *   Struct containing al information and functionality required to perform
+ *   atomic operations implemented using a sequentially consistent transaction.
+ */
+typedef struct {
+
+    /** @brief Atomic operations implemented using a sequentially consistent
+     *         transaction, and non-atomic transaction specific operations. */
+    patomic_ops_transaction_t ops;
+
+    /** @brief Alignment requirements for atomic operations. */
+    patomic_align_t align;
+
+    /** @brief Recommended time and space bounds for atomic operations. */
+    patomic_transaction_recommended_t recommended;
+
+    /** @brief Transaction safe versions of core <string.h> functions. */
+    patomic_transaction_safe_string_t string;
+
+} patomic_transaction_t;
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_CORE_H */
diff --git a/include/patomic/api/feature_check.h b/include/patomic/api/feature_check.h
new file mode 100644
index 000000000..8203095ab
--- /dev/null
+++ b/include/patomic/api/feature_check.h
@@ -0,0 +1,547 @@
+#ifndef PATOMIC_API_FEATURE_CHECK_H
+#define PATOMIC_API_FEATURE_CHECK_H
+
+#include "ops.h"
+
+#include <patomic/api/export.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Enum constants representing operation categories, corresponding to groups
+ *   of operations found in ops structs.
+ *
+ * @note
+ *   Each "opcat" label has a single bit set (except for NONE which has no bits
+ *   set), and each "opcats" label has more than one bit set.
+ */
+typedef enum {
+
+    /** @brief No operations. May be used with any opkind. */
+    patomic_opcat_NONE = 0x0,
+
+    /** @brief Load and store operations. */
+    patomic_opcat_LDST = 0x1,
+
+    /** @brief Exchange and compare-exchange (xchg) operations. */
+    patomic_opcat_XCHG = 0x2,
+
+    /** @brief Bitwise operations. */
+    patomic_opcat_BIT = 0x4,
+
+    /** @brief Binary void (non-fetch) operations. */
+    patomic_opcat_BIN_V = 0x8,
+
+    /** @brief Binary fetch operations. */
+    patomic_opcat_BIN_F = 0x10,
+
+    /** @brief Arithmetic void (non-fetch) operations. */
+    patomic_opcat_ARI_V = 0x20,
+
+    /** @brief Arithmetic fetch operations. */
+    patomic_opcat_ARI_F = 0x40,
+
+    /** @brief Transaction specific special extended operations. */
+    patomic_opcat_TSPEC = 0x80,
+
+    /** @brief Transaction specific flag operations. */
+    patomic_opcat_TFLAG = 0x100,
+
+    /** @brief Transaction specific raw operations. */
+    patomic_opcat_TRAW = 0x200,
+
+    /** @brief Binary operations. */
+    patomic_opcats_BIN = patomic_opcat_BIN_V | patomic_opcat_BIN_F,
+
+    /** @brief Arithmetic operations. */
+    patomic_opcats_ARI = patomic_opcat_ARI_V | patomic_opcat_ARI_F,
+
+    /** @brief All implicit operations. */
+    patomic_opcats_IMPLICIT = patomic_opcat_LDST |
+                              patomic_opcat_XCHG |
+                              patomic_opcat_BIT  |
+                              patomic_opcats_BIN |
+                              patomic_opcats_ARI,
+
+    /** @brief All explicit operations. */
+    patomic_opcats_EXPLICIT = patomic_opcats_IMPLICIT,
+
+    /** @brief All transaction operations. */
+    patomic_opcats_TRANSACTION = patomic_opcats_EXPLICIT |
+                                 patomic_opcat_TSPEC     |
+                                 patomic_opcat_TFLAG     |
+                                 patomic_opcat_TRAW
+
+} patomic_opcat_t;
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Enum constants representing operation kinds, corresponding to a specific
+ *   operation found in an ops struct.
+ *
+ * @note
+ *   Multiple op kinds from different categories may have the same value, and
+ *   are meaningless in isolation. They must be combined with an op category to
+ *   have proper meaning.
+ *
+ * @note
+ *   There is no differentiation between fetch and void (non-fetch) op kinds.
+ *   Instead, the differentiation is made by whether the op kind is used with
+ *   a fetch or void op category.
+ *
+ * @note
+ *   Each "opkind" label has a single bit set (except for NONE which has no
+ *   bits set), and each "opkinds" label has more than one bit set.
+ */
+typedef enum {
+
+    /** @brief No operation. May be used with any opcat. */
+    patomic_opkind_NONE = 0x0,
+
+    /** @brief opcat_LDST: load operation. */
+    patomic_opkind_LOAD = 0x1,
+
+    /** @brief opcat_LDST: store operation. */
+    patomic_opkind_STORE = 0x2,
+
+    /** @brief opcat_LDST: all operations. */
+    patomic_opkinds_LDST = patomic_opkind_LOAD | patomic_opkind_STORE,
+
+    /** @brief opcat_XCHG: exchange operation. */
+    patomic_opkind_EXCHANGE = 0x1,
+
+    /** @brief opcat_XCHG: compare-exchange weak operation. */
+    patomic_opkind_CMPXCHG_WEAK = 0x2,
+
+    /** @brief opcat_XCHG: compare-exchange strong operation. */
+    patomic_opkind_CMPXCHG_STRONG = 0x4,
+
+    /** @brief opcat_XCHG: all operations. */
+    patomic_opkinds_XCHG = patomic_opkind_EXCHANGE     |
+                           patomic_opkind_CMPXCHG_WEAK |
+                           patomic_opkind_CMPXCHG_STRONG,
+
+    /** @brief opcat_BIT or opcat_TFLAG: test operation. */
+    patomic_opkind_TEST = 0x1,
+
+    /** @brief opcat_BIT or opcat_TFLAG: test and set operation. */
+    patomic_opkind_TEST_SET = 0x2,
+
+    /** @brief opcat_BIT: test and reset operation. */
+    patomic_opkind_TEST_RESET = 0x4,
+
+    /** @brief opcat_BIT: test and complement operation. */
+    patomic_opkind_TEST_COMPL = 0x8,
+
+    /** @brief opcat_BIT or opcat_TFLAG: clear operation. */
+    patomic_opkind_CLEAR = 0x10,
+
+    /** @brief opcat_BIT: all operations. */
+    patomic_opkinds_BIT = patomic_opkind_TEST       |
+                          patomic_opkind_TEST_SET   |
+                          patomic_opkind_TEST_RESET |
+                          patomic_opkind_TEST_COMPL,
+
+    /** @brief opcat_TFLAG: all operations. */
+    patomic_opkinds_TFLAG = patomic_opkind_TEST     |
+                            patomic_opkind_TEST_SET |
+                            patomic_opkind_CLEAR,
+
+    /** @brief opcats_BIN: binary OR operation. */
+    patomic_opkind_OR = 0x1,
+
+    /** @brief opcats_BIN: binary XOR operation. */
+    patomic_opkind_XOR = 0x2,
+
+    /** @brief opcats_BIN: binary AND operation. */
+    patomic_opkind_AND = 0x4,
+
+    /** @brief opcats_BIN: binary NOT operation. */
+    patomic_opkind_NOT = 0x8,
+
+    /** @brief opcats_BIN: all operations. */
+    patomic_opkinds_BIN = patomic_opkind_OR  |
+                          patomic_opkind_XOR |
+                          patomic_opkind_AND |
+                          patomic_opkind_NOT,
+
+    /** @brief opcats_ARI: addition operation. */
+    patomic_opkind_ADD = 0x1,
+
+    /** @brief opcats_ARI: subtraction operation. */
+    patomic_opkind_SUB = 0x2,
+
+    /** @brief opcats_ARI: increment operation. */
+    patomic_opkind_INC = 0x4,
+
+    /** @brief opcats_ARI: decrement operation. */
+    patomic_opkind_DEC = 0x8,
+
+    /** @brief opcats_ARI: negation operation. */
+    patomic_opkind_NEG = 0x10,
+
+    /** @brief opcats_ARI: all operations. */
+    patomic_opkinds_ARI = patomic_opkind_ADD |
+                          patomic_opkind_SUB |
+                          patomic_opkind_INC |
+                          patomic_opkind_DEC |
+                          patomic_opkind_NEG,
+
+    /** @brief opcat_TSPEC: double compare-exchange operation. */
+    patomic_opkind_DOUBLE_CMPXCHG = 0x1,
+
+    /** @brief opcat_TSPEC: multi compare-exchange operation. */
+    patomic_opkind_MULTI_CMPXCHG = 0x2,
+
+    /** @brief opcat_TSPEC: generic operation. */
+    patomic_opkind_GENERIC = 0x4,
+
+    /** @brief opcat_TSPEC: generic operation with fallback. */
+    patomic_opkind_GENERIC_WFB = 0x8,
+
+    /** @brief opcat_TSPEC: all operations. */
+    patomic_opkinds_TSPEC = patomic_opkind_DOUBLE_CMPXCHG |
+                            patomic_opkind_MULTI_CMPXCHG  |
+                            patomic_opkind_GENERIC        |
+                            patomic_opkind_GENERIC_WFB,
+
+    /** @brief opcat_TRAW: transaction start operation. */
+    patomic_opkind_TBEGIN = 0x1,
+
+    /** @brief opcat_TRAW: transaction abort operation. */
+    patomic_opkind_TABORT = 0x2,
+
+    /** @brief opcat_TRAW: transaction commit operation. */
+    patomic_opkind_TCOMMIT = 0x4,
+
+    /** @brief opcat_TRAW: transaction test operation. */
+    patomic_opkind_TTEST = 0x8,
+
+    /** @brief opcat_TRAW: all operations. */
+    patomic_opkinds_TRAW = patomic_opkind_TBEGIN  |
+                           patomic_opkind_TABORT  |
+                           patomic_opkind_TCOMMIT |
+                           patomic_opkind_TTEST
+
+} patomic_opkind_t;
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if all the operations in all the categories in a set of opcats are
+ *   supported by the given implicit ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations with implicit memory ordering.
+ *
+ * @param opcats
+ *   One or more patomic_opcat_t flags combined.
+ *
+ * @returns
+ *   The input "opcats" value where each bit corresponding to a supported opcat
+ *   has been cleared. An opcat is supported if all of its operations are
+ *   supported. A return value of 0 means that everything requested is
+ *   supported.
+ *
+ * @note
+ *   Invalid bits in "opcats" which do not correspond to a patomic_opcat_t
+ *   label are ignored and remain set in the return value.
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_all(
+    const patomic_ops_t *ops,
+    unsigned int opcats
+);
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if all the operations in all the categories in a set of opcats are
+ *   supported by the given explicit ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations with explicit memory ordering.
+ *
+ * @param opcats
+ *   One or more patomic_opcat_t flags combined.
+ *
+ * @returns
+ *   The input "opcats" value where each bit corresponding to a supported opcat
+ *   has been cleared. An opcat is supported if all of its operations are
+ *   supported. A return value of 0 means that everything requested is
+ *   supported.
+ *
+ * @note
+ *   Invalid bits in "opcats" which do not correspond to a patomic_opcat_t
+ *   label are ignored and remain set in the return value.
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_all_explicit(
+    const patomic_ops_explicit_t *ops,
+    unsigned int opcats
+);
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if all the operations in all the categories in a set of opcats are
+ *   supported by the given transaction ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations implemented using a sequentially
+ *   consistent transaction, as well as transaction specific operations
+ *
+ * @param opcats
+ *   One or more patomic_opcat_t flags combined.
+ *
+ * @returns
+ *   The input "opcats" value where each bit corresponding to a supported opcat
+ *   has been cleared. An opcat is supported if all of its operations are
+ *   supported. A return value of 0 means that everything requested is
+ *   supported.
+ *
+ * @note
+ *   Invalid bits in "opcats" which do not correspond to a patomic_opcat_t
+ *   label are ignored and remain set in the return value.
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_all_transaction(
+    const patomic_ops_transaction_t *ops,
+    unsigned int opcats
+);
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if any of the operations in all the categories in a set of opcats
+ *   are supported by the given implicit ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations with implicit memory ordering.
+ *
+ * @param opcats
+ *   One or more patomic_opcat_t flags combined.
+ *
+ * @returns
+ *   The input "opcats" value where each bit corresponding to a supported opcat
+ *   has been cleared. An opcat is supported if any of its operations are
+ *   supported. A return value of 0 means that everything requested is
+ *   supported.
+ *
+ * @note
+ *   Invalid bits in "opcats" which do not correspond to a patomic_opcat_t
+ *   label are ignored and remain set in the return value.
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_any(
+    const patomic_ops_t *ops,
+    unsigned int opcats
+);
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if any of the operations in all the categories in a set of opcats
+ *   are supported by the given explicit ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations with explicit memory ordering.
+ *
+ * @param opcats
+ *   One or more patomic_opcat_t flags combined.
+ *
+ * @returns
+ *   The input "opcats" value where each bit corresponding to a supported opcat
+ *   has been cleared. An opcat is supported if any of its operations are
+ *   supported. A return value of 0 means that everything requested is
+ *   supported.
+ *
+ * @note
+ *   Invalid bits in "opcats" which do not correspond to a patomic_opcat_t
+ *   label are ignored and remain set in the return value.
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_any_explicit(
+    const patomic_ops_explicit_t *ops,
+    unsigned int opcats
+);
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if any of the operations in all the categories in a set of opcats
+ *   are supported by the given transaction ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations implemented using a sequentially
+ *   consistent transaction, as well as transaction specific operations
+ *
+ * @param opcats
+ *   One or more patomic_opcat_t flags combined.
+ *
+ * @returns
+ *   The input "opcats" value where each bit corresponding to a supported opcat
+ *   has been cleared. An opcat is supported if any of its operations are
+ *   supported. A return value of 0 means that everything requested is
+ *   supported.
+ *
+ * @note
+ *   Invalid bits in "opcats" which do not correspond to a patomic_opcat_t
+ *   label are ignored and remain set in the return value.
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_any_transaction(
+    const patomic_ops_transaction_t *ops,
+    unsigned int opcats
+);
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if any operations in the set of opkinds in the category opcat are
+ *   supported by the given implicit ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations with implicit memory ordering.
+ *
+ * @param opcat
+ *   Any single patomic_opcat_t flag that has a single bit set.
+ *
+ * @param opkinds
+ *   One or more patomic_opkind_t flags combined.
+ *
+ * @returns
+ *   The input "opkinds" value interpreted according to "opcat" where each bit
+ *   corresponding to a supported opkind has been cleared. A return value of 0
+ *   means that everything requested is supported.
+ *
+ * @note
+ *   Invalid bits in "opkinds" which do not correspond to a patomic_opkind_t
+ *   label are ignored and remain set in the return value.
+ *
+ * @note
+ *   If the "opcat" provided does not apply to the "ops" (e.g. it is
+ *   transaction specific), then "opkinds" is returned directly unmodified.
+ *
+ * @warning
+ *   The "opcat" value MUST have exactly 1 valid bit set. This means that labels
+ *   such as patomic_opcat_NONE and patomic_opcats_* are not allowed. This will
+ *   always be asserted (even if NDEBUG is defined).
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_leaf(
+    const patomic_ops_t *ops,
+    patomic_opcat_t opcat,
+    unsigned int opkinds
+);
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if any operations in the set of opkinds in the category opcat are
+ *   supported by the given explicit ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations with explicit memory ordering.
+ *
+ * @param opcat
+ *   Any single patomic_opcat_t flag that has a single bit set.
+ *
+ * @param opkinds
+ *   One or more patomic_opkind_t flags combined.
+ *
+ * @returns
+ *   The input "opkinds" value interpreted according to "opcat" where each bit
+ *   corresponding to a supported opkind has been cleared. A return value of 0
+ *   means that everything requested is supported.
+ *
+ * @note
+ *   Invalid bits in "opkinds" which do not correspond to a patomic_opkind_t
+ *   label are ignored and remain set in the return value.
+ *
+ * @note
+ *   If the "opcat" provided does not apply to the "ops" (e.g. it is
+ *   transaction specific), then "opkinds" is returned directly unmodified.
+ *
+ * @warning
+ *   The "opcat" value MUST have exactly 1 valid bit set. This means that labels
+ *   such as patomic_opcat_NONE and patomic_opcats_* are not allowed. This will
+ *   always be asserted (even if NDEBUG is defined).
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_leaf_explicit(
+    const patomic_ops_explicit_t *ops,
+    patomic_opcat_t opcat,
+    unsigned int opkinds
+);
+
+
+/**
+ * @addtogroup feature_check
+ *
+ * @brief
+ *   Checks if any operations in the set of opkinds in the category opcat are
+ *   supported by the given transaction ops struct.
+ *
+ * @param ops
+ *   Pointer to a set of atomic operations implemented using a sequentially
+ *   consistent transaction, as well as transaction specific operations
+ *
+ * @param opcat
+ *   Any single patomic_opcat_t flag that has a single bit set.
+ *
+ * @param opkinds
+ *   One or more patomic_opkind_t flags combined.
+ *
+ * @returns
+ *   The input "opkinds" value interpreted according to "opcat" where each bit
+ *   corresponding to a supported opkind has been cleared. A return value of 0
+ *   means that everything requested is supported.
+ *
+ * @note
+ *   Invalid bits in "opkinds" which do not correspond to a patomic_opkind_t
+ *   label are ignored and remain set in the return value.
+ *
+ * @warning
+ *   The "opcat" value MUST have exactly 1 valid bit set. This means that labels
+ *   such as patomic_opcat_NONE and patomic_opcats_* are not allowed. This will
+ *   always be asserted (even if NDEBUG is defined).
+ */
+PATOMIC_EXPORT unsigned int
+patomic_feature_check_leaf_transaction(
+    const patomic_ops_transaction_t *ops,
+    patomic_opcat_t opcat,
+    unsigned int opkinds
+);
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_FEATURE_CHECK_H */
diff --git a/include/patomic/api/ids.h b/include/patomic/api/ids.h
new file mode 100644
index 000000000..768d5bda6
--- /dev/null
+++ b/include/patomic/api/ids.h
@@ -0,0 +1,146 @@
+#ifndef PATOMIC_API_IDS_H
+#define PATOMIC_API_IDS_H
+
+#include <patomic/api/export.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup impl
+ *
+ * @brief
+ *   A set of globally unique constants used to identify which implementation(s)
+ *   to use to provide atomic operations.
+ *
+ * @details
+ *   - each id must have a unique name globally                                 \n
+ *   - each id must have a unique value which is 0 or a positive power of 2     \n
+ *   - the value of each implementation's id does not correspond to its kind    \n
+ *   - the order and value of ids does not confer any ranking or priority       \n
+ *
+ * @note
+ *   Unsigned long macros are used here rather than an enum in order to extend
+ *   the number of implementations we can support (up to 32).
+ *
+ * @note
+ *   The patomic_ids_ALL is all bits set rather than other values combined in
+ *   order to avoid an ABI break if a new id is added.
+ */
+typedef unsigned long patomic_id_t;
+
+/** @brief Value matching any and all implementation ids.
+ *  @note  To get all currently available implementation ids at runtime, you
+ *         can call patomic_get_ids(patomic_kinds_ALL). */
+#define patomic_ids_ALL (~0ul)
+
+/** @brief The id corresponding to the NULL implementation (i.e. none). */
+#define patomic_id_NULL (0ul)
+
+/** @brief The id corresponding to the C standard implementation. */
+#define patomic_id_STDC (1ul << 0ul)
+
+
+/**
+ * @addtogroup impl
+ *
+ * @brief
+ *   A set of globally unique constants used to provide a rough estimate as to
+ *   the runtime overhead of each implementation.
+ *
+ * @details
+ *   Values are ordered in ascending order based on call overhead, under the
+ *   assumption that an inline ASM implementation will have the least overhead
+ *   and an opaque call to a shared library will have the most.
+ *
+ * @warning
+ *   Values are NOT ordered on quality of implementation. E.g. on x86 an ASM
+ *   implementation of atomic_add is free to use a LOCK CMPXCHG loop over
+ *   LOCK ADD.
+ *
+ * @note
+ *   Implementations may have different kinds on different platforms.
+ *
+ * @note
+ *   If an implementation fits into multiple kinds, it should be given the kind
+ *   with the highest value.
+ *
+ * @note
+ *   The BLTN (builtin) kind should only be used where the implementation makes
+ *   a best-faith effort to provide an ASM implementation (e.g. with __atomic),
+ *   otherwise LIB, OS, or DYN should be used.
+ */
+typedef enum {
+
+    /** @brief The implementation kind is unknown. */
+    patomic_kind_UNKN  = 0x0
+
+    /** @brief The implementation calls a dynamically linked opaque function. */
+    ,patomic_kind_DYN  = 0x1
+
+    /** @brief The implementation calls an OS provided function. */
+    ,patomic_kind_OS   = 0x2
+
+    /** @brief The implementation calls a statically linked opaque function. */
+    ,patomic_kind_LIB  = 0x4
+
+    /** @brief The implementation calls a compiler builtin. */
+    ,patomic_kind_BLTN = 0x8
+
+    /** @brief The implementation uses inline assembly. */
+    ,patomic_kind_ASM  = 0x10
+
+    /** @brief Value matching any and all implementation kinds. */
+    ,patomic_kinds_ALL = patomic_kind_DYN  |
+                         patomic_kind_OS   |
+                         patomic_kind_LIB  |
+                         patomic_kind_BLTN |
+                         patomic_kind_ASM
+
+} patomic_kind_t;
+
+
+/**
+ * @addtogroup impl
+ *
+ * @param kinds
+ *   One or more patomic_kind_t flags combined (ORd) together.
+ *
+ * @brief
+ *   Returns all combined ids for all implementations whose kind matches at
+ *   least one bit in kinds.
+ */
+PATOMIC_EXPORT unsigned long
+patomic_get_ids(
+    unsigned int kinds
+);
+
+
+/**
+ * @addtogroup impl
+ *
+ * @param id
+ *   Any single patomic_id_t flag that has a single bit set.
+ *
+ * @brief
+ *   Returns the kind corresponding to the given id, or patomic_kind_UNKN for an
+ *   invalid id or patomic_id_NULL.
+ *
+ * @warning
+ *   The id MUST NOT have more than a single bit set. This means that you cannot
+ *   pass patomic_id_ALL. This will always be asserted, even if NDEBUG is
+ *   defined. It is allowed for the id to have zero bits set.
+ */
+PATOMIC_EXPORT unsigned int
+patomic_get_kind(
+    unsigned long id
+);
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_IDS_H */
diff --git a/include/patomic/api/memory_order.h b/include/patomic/api/memory_order.h
new file mode 100644
index 000000000..6255814cb
--- /dev/null
+++ b/include/patomic/api/memory_order.h
@@ -0,0 +1,188 @@
+#ifndef PATOMIC_API_MEMORY_ORDER_H
+#define PATOMIC_API_MEMORY_ORDER_H
+
+#include <patomic/api/export.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Enum constants specifying how memory accesses are to be ordered around an
+ *   atomic operation.
+ *
+ * @details
+ *   Each enum label's value and semantics are identical to C++11's
+ *   std::memory_order's values and semantics.
+ *
+ * @note
+ *   Consume is only present for compatibility. It is not and will not be
+ *   implemented, and will always be treated as patomic_ACQUIRE.
+ */
+typedef enum {
+    patomic_RELAXED,
+    /** @note Implemented as patomic_ACQUIRE. */
+    patomic_CONSUME,
+    patomic_ACQUIRE,
+    patomic_RELEASE,
+    patomic_ACQ_REL,
+    patomic_SEQ_CST
+} patomic_memory_order_t;
+
+
+/** @addtogroup mem_order
+ *
+ * @brief
+ *   Checks that order has a value corresponding to a label in
+ *   patomic_memory_order_t.
+ */
+#define PATOMIC_IS_VALID_ORDER(order) \
+    ( (order == patomic_RELAXED) ||   \
+      (order == patomic_CONSUME) ||   \
+      (order == patomic_ACQUIRE) ||   \
+      (order == patomic_RELEASE) ||   \
+      (order == patomic_ACQ_REL) ||   \
+      (order == patomic_SEQ_CST) )
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Checks that order has a value corresponding to a label in
+ *   patomic_memory_order_t, and is valid to use for an atomic store operation.
+ */
+#define PATOMIC_IS_VALID_STORE_ORDER(order) \
+    ( (order == patomic_RELAXED) ||         \
+      (order == patomic_RELEASE) ||         \
+      (order == patomic_ACQ_REL) ||         \
+      (order == patomic_SEQ_CST) )
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Checks that order has a value corresponding to a label in
+ *   patomic_memory_order_t, and is valid to use for an atomic load operation.
+ */
+#define PATOMIC_IS_VALID_LOAD_ORDER(order) \
+    ( (order == patomic_RELAXED) ||        \
+      (order == patomic_CONSUME) ||        \
+      (order == patomic_ACQUIRE) ||        \
+      (order == patomic_SEQ_CST) )
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Checks that both succ and fail have a value corresponding to a label in
+ *   patomic_memory_order_t, and fail is not stronger than succ or equal to
+ *   patomic_RELEASE or patomic_ACQ_REL.
+ */
+#define PATOMIC_IS_VALID_FAIL_ORDER(succ, fail) \
+    ( (succ >= fail)               &&           \
+      PATOMIC_IS_VALID_ORDER(succ) &&           \
+      PATOMIC_IS_VALID_LOAD_ORDER(fail) )
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Returns the strictest memory order that would satisfy the checks done by
+ *   PATOMIC_IS_VALID_FAIL_ORDER(succ, <return>).
+ *
+ * @warning
+ *   If an invalid memory order is passed to this macro, it will be returned
+ *   unmodified.
+ */
+#define PATOMIC_CMPXCHG_FAIL_ORDER(succ)                   \
+    ( (succ == patomic_ACQ_REL || succ == patomic_RELEASE) \
+      ? patomic_ACQUIRE                                    \
+      : succ )
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Checks that order has a value corresponding to a label in
+ *   patomic_memory_order_t. This value is identical to the
+ *   PATOMIC_IS_VALID_ORDER macro value.
+ *
+ * @returns If the check succeeds returns 1, else 0.
+ */
+PATOMIC_EXPORT int
+patomic_is_valid_order(int order);
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Checks that order has a value corresponding to a label in
+ *   patomic_memory_order_t, and is valid to use for an atomic store operation.
+ *   This value is identical to the PATOMIC_IS_VALID_STORE_ORDER macro value.
+ *
+ * @returns If the check succeeds returns 1, else 0.
+ */
+PATOMIC_EXPORT int
+patomic_is_valid_store_order(int order);
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Checks that order has a value corresponding to a label in
+ *   patomic_memory_order_t, and is valid to use for an atomic load operation.
+ *   This value is identical to the PATOMIC_IS_VALID_LOAD_ORDER macro value.
+ *
+ * @returns If the check succeeds returns 1, else 0.
+ */
+PATOMIC_EXPORT int
+patomic_is_valid_load_order(int order);
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Checks that both succ and fail have a value corresponding to a label in
+ *   patomic_memory_order_t, and fail is not stronger than succ or equal to
+ *   patomic_RELEASE or patomic_ACQ_REL. This value is identical to the
+ *   PATOMIC_IS_VALID_FAIL_ORDER macro value.
+ *
+ * @returns If the check succeeds returns 1, else 0.
+ */
+PATOMIC_EXPORT int
+patomic_is_valid_fail_order(int succ, int fail);
+
+
+/**
+ * @addtogroup mem_order
+ *
+ * @brief
+ *   Returns the strictest memory order that would satisfy the checks done by
+ *   patomic_is_valid_fail_order(succ, <return>). This value is identical to the
+ *   PATOMIC_CMPXCHG_FAIL_ORDER macro value.
+ *
+ * @warning
+ *   If an invalid memory order is passed to this function, it will be returned
+ *   unmodified.
+ */
+PATOMIC_EXPORT int
+patomic_cmpxchg_fail_order(int succ);
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_MEMORY_ORDER_H */
diff --git a/include/patomic/api/ops.h b/include/patomic/api/ops.h
new file mode 100644
index 000000000..aa1fec1b7
--- /dev/null
+++ b/include/patomic/api/ops.h
@@ -0,0 +1,8 @@
+#ifndef PATOMIC_API_OPS_H
+#define PATOMIC_API_OPS_H
+
+#include "ops/explicit.h"
+#include "ops/implicit.h"
+#include "ops/transaction.h"
+
+#endif  /* PATOMIC_API_OPS_H */
diff --git a/include/patomic/api/ops/CMakeLists.txt b/include/patomic/api/ops/CMakeLists.txt
new file mode 100644
index 000000000..81292a166
--- /dev/null
+++ b/include/patomic/api/ops/CMakeLists.txt
@@ -0,0 +1,6 @@
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    explicit.h
+    implicit.h
+    transaction.h
+)
diff --git a/include/patomic/api/ops/explicit.h b/include/patomic/api/ops/explicit.h
new file mode 100644
index 000000000..371b55b85
--- /dev/null
+++ b/include/patomic/api/ops/explicit.h
@@ -0,0 +1,537 @@
+#ifndef PATOMIC_API_OPS_EXPLICIT_H
+#define PATOMIC_API_OPS_EXPLICIT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic store operation with explicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically stores a desired value into an object.
+ *
+ * @param obj
+ *   Pointer to object into which to atomically store a value.
+ *
+ * @param desired
+ *   Pointer to object holding value to be atomically stored.
+ *
+ * @param order
+ *   Memory order used for atomic operation. Must be a valid store order.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_explicit_store_t) (
+    volatile void *obj,
+    const void *desired,
+    int order
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic load operation with explicit memory order.
+ *
+ * @details
+ *   Atomically loads a value from an object.
+ *
+ * @param obj
+ *   Pointer to object from which a value will be atomically loaded.
+ *
+ * @param order
+ *   Memory order used for atomic operation. Must be a valid load order.
+ *
+ * @param ret
+ *   Pointer to object into which to write the atomically loaded value.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_explicit_load_t) (
+    const volatile void *obj,
+    int order,
+    void *ret
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic exchange operation with explicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically replaces the value of an object with a desired value and returns
+ *   the value the object held previously, in a single read-modify-write atomic
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value to atomically exchange with desired value.
+ *
+ * @param desired
+ *   Pointer to object whose value will replace the existing value of "obj".
+ *
+ * @param order
+ *   Memory order used for atomic operation.
+ *
+ * @param ret
+ *   Pointer to object into which to write the existing value held by "obj".
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_explicit_exchange_t) (
+    volatile void *obj,
+    const void *desired,
+    int order,
+    void *ret
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic compare-exchange (cmpxchg) operation with
+ *   explicit memory order.
+ *
+ * @details
+ *   Atomically compares the value of the object with the value of an expected
+ *   object; if these compare equal, replaces the value of the object with a
+ *   desired value and returns 1, otherwise stores the value of the object into
+ *   the expected object, and returns 0. This is done in a single
+ *   read-modify-write atomic operation if 1 is returned, otherwise this is a
+ *   single atomic read (load) operation.
+ *
+ * @param obj
+ *   Pointer to object whose value to atomically cmpxchg with expected object
+ *   and desired value.
+ *
+ * @param expected
+ *   Pointer to object whose value is compared against the existing value of
+ *   "obj", and into which "obj"'s existing value will be stored if the
+ *   comparison fails.
+ *
+ * @param desired
+ *   Pointer to object whose value will replace the existing value of "obj".
+ *
+ * @param succ
+ *   Memory order used for read-modify-write atomic operation on success path.
+ *
+ * @param fail
+ *   Memory order used for read (load) atomic operation on failure path. Must be
+ *   a valid fail order.
+ *
+ * @returns
+ *   The value 1 if the existing value of "obj" compares equal to the value of
+ *   "expected", otherwise the value 0.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef int (* patomic_opsig_explicit_cmpxchg_t) (
+    volatile void *obj,
+    void *expected,
+    const void *desired,
+    int succ,
+    int fail
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic bit test operation with explicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically tests a single bit from an object, returning 1 if it is set and
+ *   0 if it is not set. The whole object is atomically loaded in order to test
+ *   the bit.
+ *
+ * @param obj
+ *   Pointer to object from which bit will be atomically loaded.
+ *
+ * @param offset
+ *   Zero-based bit index.
+ *
+ * @param order
+ *   Memory order used for atomic operation. Must be a valid load order.
+ *
+ * @returns
+ *   The value of the tested bit.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef int (* patomic_opsig_explicit_test_t) (
+    const volatile void *obj,
+    int offset,
+    int order
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic bit test-and-set operation with explicit
+ *   memory order.
+ *
+ * @details
+ *   Atomically modifies a single bit from an object with an implicitly known
+ *   unary bitwise operation, returning the original value of the bit (0 or 1).
+ *   The whole object is loaded and stored in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object from which bit will be atomically modified.
+ *
+ * @param offset
+ *   Zero-based bit index.
+ *
+ * @param order
+ *   Memory order used for atomic operation.
+ *
+ * @returns
+ *   The original value of the tested bit before modification.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef int (* patomic_opsig_explicit_test_modify_t) (
+    volatile void *obj,
+    int offset,
+    int order
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic fetch binary operation with explicit
+ *   memory order.
+ *
+ * @details
+ *   Atomically loads an object, performs an implicitly known binary operation
+ *   on the value, and stores the result in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value will be atomically modified, and passed as
+ *   first parameter in the binary operation.
+ *
+ * @param arg
+ *   Pointer to object whose value is passed as the second parameter in the
+ *   binary operation.
+ *
+ * @param order
+ *   Memory order used for atomic operation.
+ *
+ * @param ret
+ *   Pointer to object into which to write the original value of "obj" before
+ *   modification.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_explicit_fetch_t) (
+    volatile void *obj,
+    const void *arg,
+    int order,
+    void *ret
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic fetch unary operation with explicit
+ *   memory order.
+ *
+ * @details
+ *   Atomically loads an object, performs an implicitly known unary operation on
+ *   the value, and stores the result in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value will be atomically modified, and passed as
+ *   first parameter in the unary operation.
+ *
+ * @param order
+ *   Memory order used for atomic operation.
+ *
+ * @param ret
+ *   Pointer to object into which to write the original value of "obj" before
+ *   modification.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_explicit_fetch_noarg_t) (
+    volatile void *obj,
+    int order,
+    void *ret
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic binary operation with explicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically loads an object, performs an implicitly known binary operation
+ *   on the value, and stores the result in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value will be atomically modified, and passed as
+ *   first parameter in the binary operation.
+ *
+ * @param arg
+ *   Pointer to object whose value is passed as the second parameter in the
+ *   binary operation.
+ *
+ * @param order
+ *   Memory order used for atomic operation.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_explicit_void_t) (
+    volatile void *obj,
+    const void *arg,
+    int order
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Function signature for an atomic unary operation with explicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically loads an object, performs an implicitly known unary operation on
+ *   the value, and stores the result in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value will be atomically modified, and passed as
+ *   first parameter in the unary operation.
+ *
+ * @param order
+ *   Memory order used for atomic operation.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_explicit_void_noarg_t) (
+    volatile void *obj,
+    int order
+);
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Set of function pointers for atomic arithmetic operations with explicit
+ *   memory order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic addition using two's complement representation with
+     *         explicit memory order. There is no undefined behaviour. */
+    patomic_opsig_explicit_void_t fp_add;
+
+    /** @brief Atomic subtraction using two's complement representation with
+     *         explicit memory order. There is no undefined behaviour. */
+    patomic_opsig_explicit_void_t fp_sub;
+
+    /** @brief Atomic increment using two's complement representation with
+     *         explicit memory order. There is no undefined behaviour. */
+    patomic_opsig_explicit_void_noarg_t fp_inc;
+
+    /** @brief Atomic decrement using two's complement representation with
+     *         explicit memory order. There is no undefined behaviour. */
+    patomic_opsig_explicit_void_noarg_t fp_dec;
+
+    /** @brief Atomic negation using two's complement representation with
+     *         explicit memory order. There is no undefined behaviour. */
+    patomic_opsig_explicit_void_noarg_t fp_neg;
+
+    /** @brief Atomic addition using two's complement representation with
+     *         explicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_explicit_fetch_t fp_fetch_add;
+
+    /** @brief Atomic subtraction using two's complement representation with
+     *         explicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_explicit_fetch_t fp_fetch_sub;
+
+    /** @brief Atomic increment using two's complement representation with
+     *         explicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_explicit_fetch_noarg_t fp_fetch_inc;
+
+    /** @brief Atomic decrement using two's complement representation with
+     *         explicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_explicit_fetch_noarg_t fp_fetch_dec;
+
+    /** @brief Atomic negation using two's complement representation with
+     *         explicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_explicit_fetch_noarg_t fp_fetch_neg;
+
+} patomic_ops_explicit_arithmetic_t;
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Set of function pointers for atomic binary operations with explicit
+ *   memory order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic OR with explicit memory order. */
+    patomic_opsig_explicit_void_t fp_or;
+
+    /** @brief Atomic XOR with explicit memory order. */
+    patomic_opsig_explicit_void_t fp_xor;
+
+    /** @brief Atomic AND with explicit memory order. */
+    patomic_opsig_explicit_void_t fp_and;
+
+    /** @brief Atomic NOT with explicit memory order. */
+    patomic_opsig_explicit_void_noarg_t fp_not;
+
+    /** @brief Atomic OR with explicit memory order, returning original
+     *         value from before operation. */
+    patomic_opsig_explicit_fetch_t fp_fetch_or;
+
+    /** @brief Atomic XOR with explicit memory order, returning original
+     *         value from before operation. */
+    patomic_opsig_explicit_fetch_t fp_fetch_xor;
+
+    /** @brief Atomic AND with explicit memory order, returning original
+     *         value from before operation. */
+    patomic_opsig_explicit_fetch_t fp_fetch_and;
+
+    /** @brief Atomic NOT with explicit memory order, returning original
+     *         value from before operation. */
+    patomic_opsig_explicit_fetch_noarg_t fp_fetch_not;
+
+} patomic_ops_explicit_binary_t;
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Set of function pointers for atomic bitwise operations with explicit
+ *   memory order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic bit test with explicit memory order, returning original
+     *         bit value. */
+    patomic_opsig_explicit_test_t fp_test;
+
+    /** @brief Atomic bit test-and-complement with explicit memory order,
+     *         returning original bit value from before operation. */
+    patomic_opsig_explicit_test_modify_t fp_test_compl;
+
+    /** @brief Atomic bit test-and-set with explicit memory order, returning
+     *         original bit value from before operation. */
+    patomic_opsig_explicit_test_modify_t fp_test_set;
+
+    /** @brief Atomic bit test-and-reset with explicit memory order, returning
+     *         original bit value from before operation. */
+    patomic_opsig_explicit_test_modify_t fp_test_reset;
+
+} patomic_ops_explicit_bitwise_t;
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Set of function pointers for atomic exchange operations with explicit
+ *   memory order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic exchange with explicit memory order. */
+    patomic_opsig_explicit_exchange_t fp_exchange;
+
+    /** @brief Atomic compare-exchange with explicit memory order. Operation may
+     *         spuriously fail and act as if the object's value does not compare
+     *         equal to the expected value. */
+    patomic_opsig_explicit_cmpxchg_t fp_cmpxchg_weak;
+
+    /** @brief Atomic compare-exchange with explicit memory order. Operation
+     *         will never spuriously fail. */
+    patomic_opsig_explicit_cmpxchg_t fp_cmpxchg_strong;
+
+} patomic_ops_explicit_xchg_t;
+
+
+/**
+ * @addtogroup ops.explicit
+ *
+ * @brief
+ *   Set of function pointers for atomic load and store operations and other
+ *   sets of atomic operations, with explicit memory order. Pointers are NULL
+ *   if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic store with explicit memory order. */
+    patomic_opsig_explicit_store_t fp_store;
+
+    /** @brief Atomic load with explicit memory order. */
+    patomic_opsig_explicit_load_t fp_load;
+
+    /** @brief Set of atomic xchg operations with explicit memory order. */
+    patomic_ops_explicit_xchg_t xchg_ops;
+
+    /** @brief Set of atomic bitwise operations with explicit memory order. */
+    patomic_ops_explicit_bitwise_t bitwise_ops;
+
+    /** @brief Set of atomic binary operations with explicit memory order. */
+    patomic_ops_explicit_binary_t binary_ops;
+
+    /** @brief Set of atomic arithmetic operations using two's complement
+     *         representation with explicit memory order. */
+    patomic_ops_explicit_arithmetic_t arithmetic_ops;
+
+} patomic_ops_explicit_t;
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_OPS_EXPLICIT_H */
diff --git a/include/patomic/api/ops/implicit.h b/include/patomic/api/ops/implicit.h
new file mode 100644
index 000000000..c12a40207
--- /dev/null
+++ b/include/patomic/api/ops/implicit.h
@@ -0,0 +1,492 @@
+#ifndef PATOMIC_API_OPS_IMPLICIT_H
+#define PATOMIC_API_OPS_IMPLICIT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic store operation with implicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically stores a desired value into an object.
+ *
+ * @param obj
+ *   Pointer to object into which to atomically store a value.
+ *
+ * @param desired
+ *   Pointer to object holding value to be atomically stored.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_store_t) (
+    volatile void *obj,
+    const void *desired
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic load operation with implicit memory order.
+ *
+ * @details
+ *   Atomically loads a value from an object.
+ *
+ * @param obj
+ *   Pointer to object from which a value will be atomically loaded.
+ *
+ * @param ret
+ *   Pointer to object into which to write the atomically loaded value.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_load_t) (
+    const volatile void *obj,
+    void *ret
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic exchange operation with implicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically replaces the value of an object with a desired value and returns
+ *   the value the object held previously, in a single read-modify-write atomic
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value to atomically exchange with desired value.
+ *
+ * @param desired
+ *   Pointer to object whose value will replace the existing value of "obj".
+ *
+ * @param ret
+ *   Pointer to object into which to write the existing value held by "obj".
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_exchange_t) (
+    volatile void *obj,
+    const void *desired,
+    void *ret
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic compare-exchange (cmpxchg) operation with
+ *   implicit memory order.
+ *
+ * @details
+ *   Atomically compares the value of the object with the value of an expected
+ *   object; if these compare equal, replaces the value of the object with a
+ *   desired value and returns 1, otherwise stores the value of the object into
+ *   the expected object, and returns 0. This is done in a single
+ *   read-modify-write atomic operation if 1 is returned, otherwise this is a
+ *   single atomic read (load) operation.
+ *
+ * @param obj
+ *   Pointer to object whose value to atomically cmpxchg with expected object
+ *   and desired value.
+ *
+ * @param expected
+ *   Pointer to object whose value is compared against the existing value of
+ *   "obj", and into which "obj"'s existing value will be stored if the
+ *   comparison fails.
+ *
+ * @param desired
+ *   Pointer to object whose value will replace the existing value of "obj".
+ *
+ * @returns
+ *   The value 1 if the existing value of "obj" compares equal to the value of
+ *   "expected", otherwise the value 0.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef int (* patomic_opsig_cmpxchg_t) (
+    volatile void *obj,
+    void *expected,
+    const void *desired
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic bit test operation with implicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically tests a single bit from an object, returning 1 if it is set and
+ *   0 if it is not set. The whole object is atomically loaded in order to test
+ *   the bit.
+ *
+ * @param obj
+ *   Pointer to object from which bit will be atomically loaded.
+ *
+ * @param offset
+ *   Zero-based bit index.
+ *
+ * @returns
+ *   The value of the tested bit.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef int (* patomic_opsig_test_t) (
+    const volatile void *obj,
+    int offset
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic bit test-and-set operation with implicit
+ *   memory order.
+ *
+ * @details
+ *   Atomically modifies a single bit from an object with an implicitly known
+ *   unary bitwise operation, returning the original value of the bit (0 or 1).
+ *   The whole object is loaded and stored in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object from which bit will be atomically modified.
+ *
+ * @param offset
+ *   Zero-based bit index.
+ *
+ * @returns
+ *   The original value of the tested bit before modification.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef int (* patomic_opsig_test_modify_t) (
+    volatile void *obj,
+    int offset
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic fetch binary operation with implicit
+ *   memory order.
+ *
+ * @details
+ *   Atomically loads an object, performs an implicitly known binary operation
+ *   on the value, and stores the result in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value will be atomically modified, and passed as
+ *   first parameter in the binary operation.
+ *
+ * @param arg
+ *   Pointer to object whose value is passed as the second parameter in the
+ *   binary operation.
+ *
+ * @param ret
+ *   Pointer to object into which to write the original value of "obj" before
+ *   modification.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_fetch_t) (
+    volatile void *obj,
+    const void *arg,
+    void *ret
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic fetch unary operation with implicit
+ *   memory order.
+ *
+ * @details
+ *   Atomically loads an object, performs an implicitly known unary operation on
+ *   the value, and stores the result in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value will be atomically modified, and passed as
+ *   first parameter in the unary operation.
+ *
+ * @param ret
+ *   Pointer to object into which to write the original value of "obj" before
+ *   modification.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_fetch_noarg_t) (
+    volatile void *obj,
+    void *ret
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic binary operation with implicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically loads an object, performs an implicitly known binary operation
+ *   on the value, and stores the result in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value will be atomically modified, and passed as
+ *   first parameter in the binary operation.
+ *
+ * @param arg
+ *   Pointer to object whose value is passed as the second parameter in the
+ *   binary operation.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_void_t) (
+    volatile void *obj,
+    const void *arg
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Function signature for an atomic unary operation with implicit memory
+ *   order.
+ *
+ * @details
+ *   Atomically loads an object, performs an implicitly known unary operation on
+ *   the value, and stores the result in a single atomic read-modify-write
+ *   operation.
+ *
+ * @param obj
+ *   Pointer to object whose value will be atomically modified, and passed as
+ *   first parameter in the unary operation.
+ *
+ * @note
+ *   Width of all objects is the same, and is known implicitly.
+ */
+typedef void (* patomic_opsig_void_noarg_t) (
+    volatile void *obj
+);
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Set of function pointers for atomic arithmetic operations with implicit
+ *   memory order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic addition using two's complement representation with
+     *         implicit memory order. There is no undefined behaviour. */
+    patomic_opsig_void_t fp_add;
+
+    /** @brief Atomic subtraction using two's complement representation with
+     *         implicit memory order. There is no undefined behaviour. */
+    patomic_opsig_void_t fp_sub;
+
+    /** @brief Atomic increment using two's complement representation with
+     *         implicit memory order. There is no undefined behaviour. */
+    patomic_opsig_void_noarg_t fp_inc;
+
+    /** @brief Atomic decrement using two's complement representation with
+     *         implicit memory order. There is no undefined behaviour. */
+    patomic_opsig_void_noarg_t fp_dec;
+
+    /** @brief Atomic negation using two's complement representation with
+     *         implicit memory order. There is no undefined behaviour. */
+    patomic_opsig_void_noarg_t fp_neg;
+
+    /** @brief Atomic addition using two's complement representation with
+     *         implicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_fetch_t fp_fetch_add;
+
+    /** @brief Atomic subtraction using two's complement representation with
+     *         implicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_fetch_t fp_fetch_sub;
+
+    /** @brief Atomic increment using two's complement representation with
+     *         implicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_fetch_noarg_t fp_fetch_inc;
+
+    /** @brief Atomic decrement using two's complement representation with
+     *         implicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_fetch_noarg_t fp_fetch_dec;
+
+    /** @brief Atomic negation using two's complement representation with
+     *         implicit memory order, returning original value from before
+     *         operation. There is no undefined behaviour. */
+    patomic_opsig_fetch_noarg_t fp_fetch_neg;
+
+} patomic_ops_arithmetic_t;
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Set of function pointers for atomic binary operations with implicit
+ *   memory order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic OR with implicit memory order. */
+    patomic_opsig_void_t fp_or;
+
+    /** @brief Atomic XOR with implicit memory order. */
+    patomic_opsig_void_t fp_xor;
+
+    /** @brief Atomic AND with implicit memory order. */
+    patomic_opsig_void_t fp_and;
+
+    /** @brief Atomic NOT with implicit memory order. */
+    patomic_opsig_void_noarg_t fp_not;
+
+    /** @brief Atomic OR with implicit memory order, returning original
+     *         value from before operation. */
+    patomic_opsig_fetch_t fp_fetch_or;
+
+    /** @brief Atomic XOR with implicit memory order, returning original
+     *         value from before operation. */
+    patomic_opsig_fetch_t fp_fetch_xor;
+
+    /** @brief Atomic AND with implicit memory order, returning original
+     *         value from before operation. */
+    patomic_opsig_fetch_t fp_fetch_and;
+
+    /** @brief Atomic NOT with implicit memory order, returning original
+     *         value from before operation. */
+    patomic_opsig_fetch_noarg_t fp_fetch_not;
+
+} patomic_ops_binary_t;
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Set of function pointers for atomic bitwise operations with implicit
+ *   memory order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic bit test with implicit memory order, returning original
+     *         bit value. */
+    patomic_opsig_test_t fp_test;
+
+    /** @brief Atomic bit test-and-complement with implicit memory order,
+     *         returning original bit value from before operation. */
+    patomic_opsig_test_modify_t fp_test_compl;
+
+    /** @brief Atomic bit test-and-set with implicit memory order, returning
+     *         original bit value from before operation. */
+    patomic_opsig_test_modify_t fp_test_set;
+
+    /** @brief Atomic bit test-and-reset with implicit memory order, returning
+     *         original bit value from before operation. */
+    patomic_opsig_test_modify_t fp_test_reset;
+
+} patomic_ops_bitwise_t;
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Set of function pointers for atomic exchange operations with implicit
+ *   memory order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic exchange with implicit memory order. */
+    patomic_opsig_exchange_t fp_exchange;
+
+    /** @brief Atomic compare-exchange with implicit memory order. Operation may
+     *         spuriously fail and act as if the object's value does not compare
+     *         equal to the expected value. */
+    patomic_opsig_cmpxchg_t fp_cmpxchg_weak;
+
+    /** @brief Atomic compare-exchange with implicit memory order. Operation
+     *         will never spuriously fail. */
+    patomic_opsig_cmpxchg_t fp_cmpxchg_strong;
+
+} patomic_ops_xchg_t;
+
+
+/**
+ * @addtogroup ops.implicit
+ *
+ * @brief
+ *   Set of function pointers for atomic load and store operations and other
+ *   sets of atomic operations, with implicit memory order. Pointers are NULL
+ *   if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic store with implicit memory order. */
+    patomic_opsig_store_t fp_store;
+
+    /** @brief Atomic load with implicit memory order. */
+    patomic_opsig_load_t fp_load;
+
+    /** @brief Set of atomic xchg operations with implicit memory order. */
+    patomic_ops_xchg_t xchg_ops;
+
+    /** @brief Set of atomic bitwise operations with implicit memory order. */
+    patomic_ops_bitwise_t bitwise_ops;
+
+    /** @brief Set of atomic binary operations with implicit memory order. */
+    patomic_ops_binary_t binary_ops;
+
+    /** @brief Set of atomic arithmetic operations using two's complement
+     *         representation with implicit memory order. */
+    patomic_ops_arithmetic_t arithmetic_ops;
+
+} patomic_ops_t;
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_OPS_IMPLICIT_H */
diff --git a/include/patomic/api/ops/transaction.h b/include/patomic/api/ops/transaction.h
new file mode 100644
index 000000000..72f8106ca
--- /dev/null
+++ b/include/patomic/api/ops/transaction.h
@@ -0,0 +1,1098 @@
+#ifndef PATOMIC_API_OPS_TRANSACTION_H
+#define PATOMIC_API_OPS_TRANSACTION_H
+
+#include "../transaction.h"
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic store operation implemented using a
+ *   sequentially consistent transaction.
+ *
+ * @details
+ *   Attempts to atomically store a desired value into bytes, however the
+ *   transaction may fail.
+ *
+ * @param obj
+ *   Pointer to bytes into which to atomically store a value.
+ *
+ * @param desired
+ *   Pointer to bytes holding value to be atomically stored.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef void (* patomic_opsig_transaction_store_t) (
+    volatile void *obj,
+    const void *desired,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic load operation implemented using a
+ *   sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically loads a value from bytes, however the transaction may fail.
+ *
+ * @param obj
+ *   Pointer to bytes from which a value will be atomically loaded.
+ *
+ * @param ret
+ *   Pointer to bytes into which to write the atomically loaded value.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef void (* patomic_opsig_transaction_load_t) (
+    const volatile void *obj,
+    void *ret,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic exchange operation implemented using a
+ *   sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically replaces the value of bytes with a desired value and returns the
+ *   value the bytes held previously, in a single read-modify-write atomic
+ *   operation, however the transaction may fail.
+ *
+ * @param obj
+ *   Pointer to bytes whose value to atomically exchange with desired value.
+ *
+ * @param desired
+ *   Pointer to bytes whose value will replace the existing value of "obj".
+ *
+ * @param ret
+ *   Pointer to bytes into which to write the existing value held by "obj".
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef void (* patomic_opsig_transaction_exchange_t) (
+    volatile void *obj,
+    const void *desired,
+    void *ret,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic compare-exchange (cmpxchg) operation
+ *   implemented using a sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically compares the value of the bytes with the value of expected
+ *   bytes; if these compare equal, replaces the value of the bytes with a
+ *   desired value and returns 1, otherwise stores the value of the bytes into
+ *   the expected bytes, and returns 0. This is done in a single
+ *   read-modify-write atomic operation if 1 is returned, otherwise this is a
+ *   single atomic read (load) operation. The primary transaction may fail, in
+ *   which case the fallback transaction is tried. If either transaction path
+ *   fails, 0 is returned.
+ *
+ * @param obj
+ *   Pointer to bytes whose value to atomically cmpxchg with expected bytes
+ *   and desired value.
+ *
+ * @param expected
+ *   Pointer to bytes whose value is compared against the existing value of
+ *   "obj", and into which "obj"'s existing value will be stored if the
+ *   comparison fails.
+ *
+ * @param desired
+ *   Pointer to bytes whose value will replace the existing value of "obj".
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @returns
+ *   The value 1 if the existing value of "obj" compares equal to the value of
+ *   "expected", otherwise the value 0. If the transaction fails, 0 is returned.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   The parameter "desired" may be passed a default value of 0 or NULL.
+ *   Execution will pass to the fallback transaction.
+ *
+ * @note
+ *   If config.fallback_attempts == 0, the fallback transaction will not be
+ *   attempted. The fallback status will be set to TABORT_EXPLICIT with reason
+ *   0, and fallback_attempts_made to 0. If config.attempts also == 0, then
+ *   parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef int (* patomic_opsig_transaction_cmpxchg_t) (
+    volatile void *obj,
+    void *expected,
+    const void *desired,
+    patomic_transaction_config_wfb_t config,
+    patomic_transaction_result_wfb_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic bit test operation implemented using a
+ *   sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically tests a single bit from bytes, returning 1 if it is set and 0 if
+ *   it is not set. All the bytes are atomically loaded in order to test the
+ *   bit. The transaction may fail, in which case 0 is returned.
+ *
+ * @param obj
+ *   Pointer to bytes from which bit will be atomically loaded.
+ *
+ * @param offset
+ *   Zero-based bit index.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @returns
+ *   The value of the tested bit, or 0 if the transaction failed.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef int (* patomic_opsig_transaction_test_t) (
+    const volatile void *obj,
+    int offset,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic bit test-and-set operation implemented
+ *   using a sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically modifies a single bit from bytes with an implicitly known unary
+ *   bitwise operation, returning the original value of the bit (0 or 1). All
+ *   the bytes are loaded and stored in a single atomic read-modify-write
+ *   operation. The transaction may fail, in which case 0 is returned.
+ *
+ * @param obj
+ *   Pointer to bytes from which bit will be atomically modified.
+ *
+ * @param offset
+ *   Zero-based bit index.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @returns
+ *   The original value of the tested bit before modification, or 0 if the
+ *   transaction failed.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef int (* patomic_opsig_transaction_test_modify_t) (
+    volatile void *obj,
+    int offset,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic fetch binary operation implemented using
+ *   a sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically loads bytes, performs an implicitly known binary operation on
+ *   the value, and stores the result in a single atomic read-modify-write
+ *   operation, however the transaction may fail.
+ *
+ * @param obj
+ *   Pointer to bytes whose value will be atomically modified, and passed as
+ *   first parameter in the binary operation.
+ *
+ * @param arg
+ *   Pointer to bytes whose value is passed as the second parameter in the
+ *   binary operation.
+ *
+ * @param ret
+ *   Pointer to bytes into which to write the original value of "obj" before
+ *   modification.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef void (* patomic_opsig_transaction_fetch_t) (
+    volatile void *obj,
+    const void *arg,
+    void *ret,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic fetch unary operation implemented using a
+ *   sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically loads bytes, performs an implicitly known unary operation on the
+ *   value, and stores the result in a single atomic read-modify-write
+ *   operation, however the transaction may fail.
+ *
+ * @param obj
+ *   Pointer to bytes whose value will be atomically modified, and passed as
+ *   first parameter in the unary operation.
+ *
+ * @param ret
+ *   Pointer to bytes into which to write the original value of "obj" before
+ *   modification.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef void (* patomic_opsig_transaction_fetch_noarg_t) (
+    volatile void *obj,
+    void *ret,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic binary operation implemented using a
+ *   sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically loads bytes, performs an implicitly known binary operation on
+ *   the value, and stores the result in a single atomic read-modify-write
+ *   operation, however the transaction may fail.
+ *
+ * @param obj
+ *   Pointer to bytes whose value will be atomically modified, and passed as
+ *   first parameter in the binary operation.
+ *
+ * @param arg
+ *   Pointer to bytes whose value is passed as the second parameter in the
+ *   binary operation.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef void (* patomic_opsig_transaction_void_t) (
+    volatile void *obj,
+    const void *arg,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic unary operation implemented using a
+ *   sequentially consistent transaction.
+ *
+ * @details
+ *   Atomically loads bytes, performs an implicitly known unary operation on the
+ *   value, and stores the result in a single atomic read-modify-write
+ *   operation, however the transaction may fail.
+ *
+ * @param obj
+ *   Pointer to bytes whose value will be atomically modified, and passed as
+ *   first parameter in the unary operation.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @note
+ *   If config.width == 0, the transaction proceeds as normal with no
+ *   short-circuiting. Parameters (except for "config" and "result") may be
+ *   passed a default value of 0 or NULL.
+ *
+ * @note
+ *   If config.attempts == 0, the transaction will not be attempted. The status
+ *   will be set to TABORT_EXPLICIT with reason 0, and attempts_made to 0.
+ *   Parameters (except for "config" and "result") may be passed a default value
+ *   of 0 or NULL.
+ */
+typedef void (* patomic_opsig_transaction_void_noarg_t) (
+    volatile void *obj,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic double compare-exchange (cmpxchg)
+ *   operation implemented using a sequentially consistent transaction.
+ *
+ * @details
+ *   Equivalent to performing two compare-exchange operations in a single
+ *   atomic transaction, where the primary read-modify-write transaction only
+ *   succeeds if both comparisons succeed. The primary transaction may fail, in
+ *   which case the fallback read-only (load) transaction is tried. If either
+ *   transaction path fails, 0 is returned.
+ *
+ * @param cxa
+ *   Holds pointers to sets of bytes that will be used in first compare-exchange
+ *   operation.
+ *
+ * @param cxb
+ *   Holds pointers to sets of bytes that will be used in second
+ *   compare-exchange operation.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @returns
+ *   The value 1 if both sets of bytes compare equal to their expected values,
+ *   otherwise the value 0. If either transaction fails, 0 is returned.
+ */
+typedef int (* patomic_opsig_transaction_double_cmpxchg_t) (
+    patomic_transaction_cmpxchg_t cxa,
+    patomic_transaction_cmpxchg_t cxb,
+    patomic_transaction_config_wfb_t config,
+    patomic_transaction_result_wfb_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for an atomic multi compare-exchange (cmpxchg)
+ *   operation implemented using a sequentially consistent transaction.
+ *
+ * @details
+ *   Equivalent to performing N compare-exchange operations in a single atomic
+ *   transaction, where the primary read-modify-write transaction only succeeds
+ *   if all N comparisons succeed. The primary transaction may fail, in which
+ *   case the fallback read-only (load) transaction is tried. If either
+ *   transaction path fails, 0 is returned.
+ *
+ * @param cxs_buf
+ *   Array of objects holding sets of bytes that will be used in each
+ *   compare-exchange operation.
+ *
+ * @param cxs_len
+ *   The number of elements in the cxs_buf array.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ *
+ * @returns
+ *   The value 1 if all N sets of bytes compare equal to their expected values,
+ *   otherwise the value 0. If either transaction fails, 0 is returned.
+ */
+typedef int (* patomic_opsig_transaction_multi_cmpxchg_t) (
+    const patomic_transaction_cmpxchg_t *cxs_buf,
+    size_t cxs_len,
+    patomic_transaction_config_wfb_t config,
+    patomic_transaction_result_wfb_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for a generic atomic operation implemented using a
+ *   sequentially consistent transaction.
+ *
+ * @details
+ *   The function "fn" is called inside a transaction and is directly passed
+ *   "ctx", which is not dereferenced, accessed, or modified in any way before
+ *   being passed to the function.
+ *
+ * @param fn
+ *   Function to be called inside transaction.
+ *
+ * @param ctx
+ *   Opaque data to be passed to function.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ */
+typedef void (* patomic_opsig_transaction_generic_t) (
+    void (* fn) (void *),
+    void *ctx,
+    patomic_transaction_config_t config,
+    patomic_transaction_result_t *result
+);
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for a generic atomic operation with a fallback path
+ *   implemented using a sequentially consistent transaction.
+ *
+ * @details
+ *   The function "fn" is called inside a transaction and is directly passed
+ *   "ctx", which is not dereferenced, access, or modified in any way before
+ *   being passed to the function. If this transaction fails, the same is
+ *   attempted with "fallback_fn" and "fallback_ctx". If either transaction
+ *   path fails, 0 is returned.
+ *
+ * @param fn
+ *   Function to be called inside primary transaction.
+ *
+ * @param ctx
+ *   Opaque data to be passed to function called inside primary transaction.
+ *
+ * @param fallback_fn
+ *   Function to be called inside fallback transaction.
+ *
+ * @param fallback_ctx
+ *   Opaque data to be passed to function called inside fallback transaction.
+ *
+ * @param config
+ *   Configuration for transaction.
+ *
+ * @param result
+ *   Pointer to object holding result of transaction, including status code and
+ *   attempts made.
+ */
+typedef int (* patomic_opsig_transaction_generic_wfb_t) (
+    void (* fn) (void *),
+    void *ctx,
+    void (* fallback_fn) (void *),
+    void *fallback_ctx,
+    patomic_transaction_config_wfb_t config,
+    patomic_transaction_result_wfb_t *result
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for atomic flag test operation implemented without a
+ *   transaction with at least patomic_ACQUIRE memory order.
+ *
+ * @details
+ *   This function can potentially be available on platforms where an atomic
+ *   load operation is not.
+ *
+ * @param flag
+ *   Pointer to flag object whose value to test.
+ *
+ * @returns
+ *   The value 0 if the flag object not set, otherwise the value 1.
+ */
+typedef int (* patomic_opsig_transaction_flag_test_t) (
+    const patomic_transaction_flag_t *flag
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for atomic flag test-and-set operation implemented
+ *   without a transaction with at least patomic_ACQ_REL memory order.
+ *
+ * @details
+ *   This function can potentially be available on platforms where an atomic
+ *   exchange operation is not.
+ *
+ * @param flag
+ *   Pointer to flag object whose value to test and set.
+ *
+ * @returns
+ *   The value 0 if the flag object was not set when being tested, otherwise the
+ *   value 1.
+ */
+typedef int (* patomic_opsig_transaction_flag_test_set_t) (
+    patomic_transaction_flag_t *flag
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for atomic flag clear operation implemented without a
+ *   transaction with at least patomic_RELEASE memory order.
+ *
+ * @details
+ *   This function can potentially be available on platforms where an atomic
+ *   exchange operation is not.
+ *
+ * @param flag
+ *   Pointer to flag object whose value to clear.
+ */
+typedef void (* patomic_opsig_transaction_flag_clear_t) (
+    patomic_transaction_flag_t *flag
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for starting a transaction.
+ *
+ * @details
+ *   Starts a transaction. The execution of the transaction will start when this
+ *   function is invoked, and end by returning from this function from the same
+ *   invocation.
+ *
+ * @returns
+ *   A status code which will be 0 if the transaction has been successfully
+ *   committed.
+ *
+ * @note
+ *   On failure, a bitwise-and with patomic_transaction_status_t
+ *   values will tell you the general failure reason. An explicitly passed
+ *   abort reason can be obtained from the return value by passing it to
+ *   patomic_transaction_abort_reason.
+ */
+typedef unsigned int (* patomic_opsig_transaction_tbegin_t) (
+    void
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for aborting a transaction.
+ *
+ * @details
+ *   Explicitly aborts a live transaction with a reason that is passed in the
+ *   status code, which itself will always match patomic_TABORT_EXPLICIT. Only
+ *   the 8 least significant bits of reason are used (if CHAR_BIT happens to be
+ *   larger than 8).
+ *
+ * @warning
+ *   Aborting a nested transaction will abort ALL nested transactions, and
+ *   execution control will pass back to the caller of the outermost tbegin.
+ *
+ * @note
+ *   Calling this outside of an active transaction is a no-op.
+ */
+typedef void (* patomic_opsig_transaction_tabort_t) (
+    unsigned char reason
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for committing a transaction.
+ *
+ * @details
+ *   Commits a transaction, successfully terminating it. Execution control will
+ *   pass back to the caller of tbegin.
+ *
+ * @warning
+ *   Calling this outside of an active transaction results in undefined
+ *   behaviour.
+ */
+typedef void (* patomic_opsig_transaction_tcommit_t) (
+    void
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Function signature for testing if currently executing inside a transaction.
+ *
+ * @returns
+ *   A non-zero value if currently executing inside a transaction, otherwise 0.
+ */
+typedef int (* patomic_opsig_transaction_ttest_t) (
+    void
+);
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Set of function pointers for atomic arithmetic operations implemented using
+ *   a sequentially consistent transaction. Pointers are NULL if operation is
+ *   not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic addition using two's complement representation implemented
+     *         using a sequentially consistent transaction. There is no
+     *         undefined behaviour. */
+    patomic_opsig_transaction_void_t fp_add;
+
+    /** @brief Atomic subtraction using two's complement representation
+     *         implemented using a sequentially consistent transaction. There is
+     *         no undefined behaviour. */
+    patomic_opsig_transaction_void_t fp_sub;
+
+    /** @brief Atomic increment using two's complement representation
+     *         implemented using a sequentially consistent transaction. There is
+     *         no undefined behaviour. */
+    patomic_opsig_transaction_void_noarg_t fp_inc;
+
+    /** @brief Atomic decrement using two's complement representation
+     *         implemented using a sequentially consistent transaction. There is
+     *         no undefined behaviour. */
+    patomic_opsig_transaction_void_noarg_t fp_dec;
+
+    /** @brief Atomic negation using two's complement representation implemented
+     *         using a sequentially consistent transaction. There is no
+     *         undefined behaviour. */
+    patomic_opsig_transaction_void_noarg_t fp_neg;
+
+    /** @brief Atomic addition using two's complement representation implemented
+     *         using a sequentially consistent transaction, returning original
+     *         value from before operation. There is no undefined behaviour. */
+    patomic_opsig_transaction_fetch_t fp_fetch_add;
+
+    /** @brief Atomic subtraction using two's complement representation
+     *         implemented using a sequentially consistent transaction,
+     *         returning original value from before operation. There is no
+     *         undefined behaviour. */
+    patomic_opsig_transaction_fetch_t fp_fetch_sub;
+
+    /** @brief Atomic increment using two's complement representation
+     *         implemented using a sequentially consistent transaction,
+     *         returning original value from before operation. There is no
+     *         undefined behaviour. */
+    patomic_opsig_transaction_fetch_noarg_t fp_fetch_inc;
+
+    /** @brief Atomic decrement using two's complement representation
+     *         implemented using a sequentially consistent transaction,
+     *         returning original value from before operation. There is no
+     *         undefined behaviour. */
+    patomic_opsig_transaction_fetch_noarg_t fp_fetch_dec;
+
+    /** @brief Atomic negation using two's complement representation implemented
+     *         using a sequentially consistent transaction, returning original
+     *         value from before operation. There is no undefined behaviour. */
+    patomic_opsig_transaction_fetch_noarg_t fp_fetch_neg;
+
+} patomic_ops_transaction_arithmetic_t;
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Set of function pointers for atomic binary operations implemented using
+ *   a sequentially consistent transaction. Pointers are NULL if operation is
+ *   not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic OR implemented using a sequentially consistent
+     *         transaction. */
+    patomic_opsig_transaction_void_t fp_or;
+
+    /** @brief Atomic XOR implemented using a sequentially consistent
+     *         transaction. */
+    patomic_opsig_transaction_void_t fp_xor;
+
+    /** @brief Atomic AND implemented using a sequentially consistent
+     *         transaction. */
+    patomic_opsig_transaction_void_t fp_and;
+
+    /** @brief Atomic NOT implemented using a sequentially consistent
+     *         transaction. */
+    patomic_opsig_transaction_void_noarg_t fp_not;
+
+    /** @brief Atomic OR implemented using a sequentially consistent
+     *         transaction, returning original value from before operation. */
+    patomic_opsig_transaction_fetch_t fp_fetch_or;
+
+    /** @brief Atomic XOR implemented using a sequentially consistent
+     *         transaction, returning original value from before operation. */
+    patomic_opsig_transaction_fetch_t fp_fetch_xor;
+
+    /** @brief Atomic AND implemented using a sequentially consistent
+     *         transaction, returning original value from before operation. */
+    patomic_opsig_transaction_fetch_t fp_fetch_and;
+
+    /** @brief Atomic NOT implemented using a sequentially consistent
+     *         transaction, returning original value from before operation. */
+    patomic_opsig_transaction_fetch_t fp_fetch_not;
+
+} patomic_ops_transaction_binary_t;
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Set of function pointers for atomic bitwise operations implemented using
+ *   a sequentially consistent transaction. Pointers are NULL if operation is
+ *   not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic bit test implemented using a sequentially consistent
+     *         transaction, returning original bit value. */
+    patomic_opsig_transaction_test_t fp_test;
+
+    /** @brief Atomic bit test-and-complement implemented using a sequentially
+     *         consistent transaction, returning original bit value from before
+     *         operation. */
+    patomic_opsig_transaction_test_modify_t fp_test_compl;
+
+    /** @brief Atomic bit test-and-set implemented using a sequentially
+     *         consistent transaction, returning original bit value from before
+     *         operation. */
+    patomic_opsig_transaction_test_modify_t fp_test_set;
+
+    /** @brief Atomic bit test-and-reset implemented using a sequentially
+     *         consistent transaction, returning original bit value from before
+     *         operation. */
+    patomic_opsig_transaction_test_modify_t fp_test_reset;
+
+} patomic_ops_transaction_bitwise_t;
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Set of function pointers for atomic exchange operations implemented using
+ *   a sequentially consistent transaction. Pointers are NULL if operation is
+ *   not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic exchange implemented using a sequentially consistent
+     *         transaction. */
+    patomic_opsig_transaction_exchange_t fp_exchange;
+
+    /** @brief Atomic compare-exchange implemented using a sequentially
+     *         consistent transaction. Operation may spuriously fail and act as
+     *         if the bytes' value does not compare equal to the expected
+     *         value. */
+    patomic_opsig_transaction_cmpxchg_t fp_cmpxchg_weak;
+
+    /** @brief Atomic compare-exchange implemented using a sequentially
+     *         consistent transaction. Operation will never spuriously fail.
+     *
+     *  @note This will always be NULL as transactions can always spuriously
+     *        fail. It is kept for consistency with implicit/explicit ops. */
+    patomic_opsig_transaction_cmpxchg_t fp_cmpxchg_strong;
+
+} patomic_ops_transaction_xchg_t;
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Set of function pointers for atomic extended transaction-specific
+ *   operations implemented using a sequentially consistent transaction.
+ *   Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic double compare-exchange implemented using a sequentially
+     *         consistent transaction. */
+    patomic_opsig_transaction_double_cmpxchg_t fp_double_cmpxchg;
+
+    /** @brief Atomic multi compare-exchange implemented using a sequentially
+     *         consistent transaction. */
+    patomic_opsig_transaction_multi_cmpxchg_t fp_multi_cmpxchg;
+
+    /** @brief Atomic generic operation implemented using a sequentially
+     *         consistent transaction. */
+    patomic_opsig_transaction_generic_t fp_generic;
+
+    /** @brief Atomic generic operation with a fallback path implemented using
+     *         a sequentially consistent transaction. */
+    patomic_opsig_transaction_generic_wfb_t fp_generic_wfb;
+
+} patomic_ops_transaction_special_t;
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Set of function pointers for atomic flag operations with fixed memory
+ *   order. Pointers are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic flag test operation with fixed memory order. */
+    patomic_opsig_transaction_flag_test_t fp_test;
+
+    /** @brief Atomic flag test-and-set operation with fixed memory order. */
+    patomic_opsig_transaction_flag_test_set_t fp_test_set;
+
+    /** @brief Atomic flag clear operation with fixed memory order. */
+    patomic_opsig_transaction_flag_clear_t fp_clear;
+
+} patomic_ops_transaction_flag_t;
+
+
+/**
+ * @addtogroup ops.transaction
+ *
+ * @brief
+ *   Set of function pointers for raw transaction-specific operations. Pointers
+ *   are NULL if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Start a transaction. */
+    patomic_opsig_transaction_tbegin_t fp_tbegin;
+
+    /** @brief Explicitly abort a transaction. */
+    patomic_opsig_transaction_tabort_t fp_tabort;
+
+    /** @brief Commit a transaction. */
+    patomic_opsig_transaction_tcommit_t fp_tcommit;
+
+    /** @brief Test if currently executing inside a transaction. */
+    patomic_opsig_transaction_ttest_t fp_ttest;
+
+} patomic_ops_transaction_raw_t;
+
+
+/** @addtogroup ops.transaction
+ *
+ * @brief
+ *   Set of function pointers for atomic load and store operations and other
+ *   sets of atomic operations implemented using a sequentially consistent
+ *   transaction, as well as transaction specific operations. Pointers are NULL
+ *   if operation is not supported.
+ */
+typedef struct {
+
+    /** @brief Atomic store implemented using a sequentially consistent
+     *         transaction. */
+    patomic_opsig_transaction_store_t fp_store;
+
+    /** @brief Atomic load implemented using a sequentially consistent
+     *         transaction. */
+    patomic_opsig_transaction_load_t fp_load;
+
+    /** @brief Set of atomic xchg operations implemented using a sequentially
+     *         consistent transaction. */
+    patomic_ops_transaction_xchg_t xchg_ops;
+
+    /** @brief Set of atomic bitwise operations implemented using a sequentially
+     *         consistent transaction. */
+    patomic_ops_transaction_bitwise_t bitwise_ops;
+
+    /** @brief Set of atomic binary operations implemented using a sequentially
+     *         consistent transaction. */
+    patomic_ops_transaction_binary_t binary_ops;
+
+    /** @brief Set of atomic arithmetic operations using two's complement
+     *         representation implemented using a sequentially consistent
+     *         transaction. */
+    patomic_ops_transaction_arithmetic_t arithmetic_ops;
+
+    /** @brief Set of function pointers for atomic extended
+     *         transaction-specific operations implemented using a sequentially
+     *         consistent transaction. */
+    patomic_ops_transaction_special_t special_ops;
+
+    /** @brief Set of function pointers for atomic flag operations with fixed
+     *         memory order. */
+    patomic_ops_transaction_flag_t flag_ops;
+
+    /** @brief Set of function pointers for raw transaction-specific
+     *         operations. */
+    patomic_ops_transaction_raw_t raw_ops;
+
+} patomic_ops_transaction_t;
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_OPS_TRANSACTION_H */
diff --git a/include/patomic/api/options.h b/include/patomic/api/options.h
new file mode 100644
index 000000000..941f702ed
--- /dev/null
+++ b/include/patomic/api/options.h
@@ -0,0 +1,50 @@
+#ifndef PATOMIC_API_OPTIONS_H
+#define PATOMIC_API_OPTIONS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup options
+ *
+ * @brief
+ *   Enum constants providing hints to implementations that allow them to
+ *   optimize how they implement certain atomic operations.
+ *
+ * @details
+ *   Options do NOT affect the correctness of any implementation. Any atomic
+ *   operation that is obtained both when passing and not passing an option is
+ *   equally correct in terms of thread-safety and memory ordering.             \n
+ *   However, options MAY affect constraints that are unrelated to thread-safety
+ *   and memory ordering, namely alignment requirements and the quality of the
+ *   implementation.
+ *
+ * @warning
+ *   You should be cautious about using an atomic operation obtained without
+ *   an option in the same place as one obtained with the option (and vice
+ *   versa). Options are allowed to relax alignment requirements, and you could
+ *   end up with misaligned memory accesses by mistake.
+ *
+ * @note
+ *   Options are merely hints to an implementation; they may be completely
+ *   ignored.
+ *
+ * @note
+ *   Each "option" label has a single bit set (except for NONE which has no
+ *   bits set), and each "options" label has more than one bit set.
+ */
+typedef enum {
+
+    /** @brief The empty option hinting nothing */
+    patomic_option_NONE = 0x0
+
+} patomic_option_t;
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_OPTIONS_H */
diff --git a/include/patomic/api/transaction.h b/include/patomic/api/transaction.h
new file mode 100644
index 000000000..8ab58c376
--- /dev/null
+++ b/include/patomic/api/transaction.h
@@ -0,0 +1,357 @@
+#ifndef PATOMIC_API_TRANSACTION_H
+#define PATOMIC_API_TRANSACTION_H
+
+#include "align.h"
+
+#include <patomic/api/export.h>
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Writing to this flag when it has been provided to a transaction will cause
+ *   the transaction to abort.
+ *
+ * @details
+ *   Any modification by another thread to any memory in a cache line that is
+ *   being used in a transaction will cause the transaction to abort.
+ */
+typedef volatile unsigned char patomic_transaction_flag_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Ensures that the transaction flag has its own cache line to avoid false
+ *   sharing (which may cause a live transaction to unexpectedly abort).
+ *
+ * @warning
+ *   The value of PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE may change without
+ *   notice, causing an ABI break with this type.
+ *
+ * @note
+ *   You are not required to align/pad your transaction flag.                   \n
+ *   If you align/pad your transaction flag, you are not required to use this
+ *   helper type. E.g. in C11 you may use _Alignas or create your own flag
+ *   holder type.
+ */
+typedef struct {
+    unsigned char _padding_head[PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE - 1];
+    patomic_transaction_flag_t flag;
+    unsigned char _padding_tail[PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE];
+} patomic_transaction_padded_flag_holder_abi_unstable_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Used in double and multi variants of cmpxchg to pass multiple memory
+ *   locations.
+ *
+ * @note
+ *   The byte width of all objects at all the memory locations is obtained
+ *   from an object of type patomic_transaction_config(_wfb)_t which is
+ *   provided separately.
+ */
+typedef struct {
+
+    /** @brief Object on which to perform transaction. */
+    volatile void *obj;
+
+    /** @brief Value obj must have for transaction to succeed. */
+    void *expected;
+
+    /** @brief Value obj will be set to if transaction succeeds. */
+    const void *desired;
+
+} patomic_transaction_cmpxchg_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Used to configure the execution limits of a transaction, pass user-provided
+ *   flags, and determine the byte width of objects at memory locations provided
+ *   by patomic_transaction_cmpxchg_t objects.
+ *
+ * @details
+ *   The flag may be NULL, in which case a locally allocated flag is used.
+ *
+ * @note
+ *   Width is expected to be non-zero, so the zero value is not explicitly
+ *   checked and optimised for, however zero is still a valid value.
+ */
+typedef struct {
+
+    /** @brief Size in bytes of objects to operate on. */
+    size_t width;
+
+    /** @brief Number of attempts to make committing transaction. */
+    unsigned long attempts;
+
+    /** @brief Read from at the start of each transaction attempt. */
+    const patomic_transaction_flag_t *flag_nullable;
+
+} patomic_transaction_config_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Used to configure the execution limits of a transaction, pass user-provided
+ *   flags, and determine the byte width of objects at memory locations provided
+ *   by patomic_transaction_cmpxchg_t objects.
+ *
+ * @details
+ *   The flag and fallback_flag may point to the same flag, or may be NULL, in
+ *   which case a locally allocated flag is used.                               \n
+ *   The flag tends to guard a read-write code path, and the fallback flag tends
+ *   to guard a read-only code path.                                            \n
+ *   With this in mind, it is recommended to use flag as a unique read-write
+ *   lock and fallback_flag as a shared read-only lock.
+ *
+ * @note
+ *   Width is expected to be non-zero, so the zero value is not explicitly
+ *   checked and optimised for, however zero is still a valid value.
+ */
+typedef struct {
+
+    /** @brief Size in bytes of objects to operate on. */
+    size_t width;
+
+    /** @brief Number of attempts to make committing transaction. */
+    unsigned long attempts;
+
+    /** @brief Number of attempts to make committing fallback transaction. */
+    unsigned long fallback_attempts;
+
+    /** @brief Read from at the start of each transaction attempt. */
+    const patomic_transaction_flag_t *flag_nullable;
+
+    /** @brief Read from at the start of each fallback transaction attempt. */
+    const patomic_transaction_flag_t *fallback_flag_nullable;
+
+} patomic_transaction_config_wfb_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   A set of constants used to denote the status of a transaction. The status
+ *   is always 8 significant bits.
+ *
+ * @note
+ *   In transactional operations with a fallback path, an explicit abort will
+ *   immediately shift execution to the fallback path, regardless of whether
+ *   all attempts on the primary path have been exhausted.
+ */
+typedef enum {
+
+    /** @brief The transaction was committed. */
+    patomic_TSUCCESS = 0
+
+    /** @brief The transaction was not committed. */
+    ,patomic_TABORTED = 1
+
+    /** @brief The transaction was explicitly aborted by the user. */
+    ,patomic_TABORT_EXPLICIT = 0x2  | patomic_TABORTED
+
+    /** @brief There was a memory conflict with another thread. */
+    ,patomic_TABORT_CONFLICT = 0x4  | patomic_TABORTED
+
+    /** @brief The transaction accessed too much memory. */
+    ,patomic_TABORT_CAPACITY = 0x8  | patomic_TABORTED
+
+    /** @brief An inner nested transaction aborted. */
+    ,patomic_TABORT_NESTED   = 0x10 | patomic_TABORTED
+
+    /** @brief A debug trap caused the transaction to abort. */
+    ,patomic_TABORT_DEBUG    = 0x20 | patomic_TABORTED
+
+    /** @brief An interrupt caused the transaction to abort. */
+    ,patomic_TABORT_INT      = 0x40 | patomic_TABORTED
+
+} patomic_transaction_status_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Obtains the abort reason from the status of an explicitly aborted
+ *   transaction. If the transaction was not explicitly aborted, returns 0.
+ *
+ * @note
+ *   The abort reason has 8 significant bits.
+ */
+PATOMIC_EXPORT unsigned char
+patomic_transaction_abort_reason(
+    unsigned int status
+);
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Represents the outcome of a transaction operation.
+ *
+ * @note
+ *   If the transaction was configured to run zero attempts, then the status
+ *   will default to patomic_TSUCCESS.
+ */
+typedef struct {
+
+    /** @brief Status from the final attempt at committing the transaction. */
+    unsigned int status;
+
+    /** @brief Attempts made to commit the transaction. */
+    unsigned long attempts_made;
+
+} patomic_transaction_result_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Represents the outcome of a transaction operation with a fallback path.
+ *
+ * @note
+ *   If the transaction was configured to run zero attempts, then the status
+ *   will default to patomic_TSUCCESS.
+ *
+ * @note
+ *   If fallback_attempts_made is zero, the fallback_status will default to
+ *   patomic_TSUCCESS, even if more fallback attempts were configured.
+ */
+typedef struct {
+
+    /** @brief Status from the final attempt at committing the transaction. */
+    unsigned int status;
+
+    /** @brief Status from the final attempt at committing the fallback
+     *         transaction. */
+    unsigned int fallback_status;
+
+    /** @brief Attempts made to commit the transaction. */
+    unsigned long attempts_made;
+
+    /** @brief Attempts made to commit the fallback transaction. */
+    unsigned long fallback_attempts_made;
+
+} patomic_transaction_result_wfb_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Provides the recommended limits for transactions. The limits are generated
+ *   by tests run internally with successively harder values.
+ *
+ * @details
+ *   - "rmw" models cmpxchg's success path (load, compare, store)               \n
+ *   - "load" models cmpxchg's failure path (load)                              \n
+ *   - the tests used to generate these values are run at least once per program
+ *     execution                                                                \n
+ *   - tests may be run multiple times until they succeed internally, with the
+ *     number of times being unspecified                                        \n
+ *   - implementations may cache the values
+ *
+ * @note
+ *   The tests used to generate the values for this type are likely run under
+ *   sterile conditions with no memory contention. They represent the best
+ *   possible outcome, which may not be achievable in real world scenarios.
+ */
+typedef struct {
+
+    /** @brief Test transaction performs the equivalent of a successful
+     *         fp_cmpxchg_weak with an increasing byte width. This value is
+     *         the maximum byte width where the test transaction eventually
+     *         succeeded. */
+    unsigned long max_rmw_memory;
+
+    /** @brief Test transaction performs the equivalent of a successful fp_load
+     *         with an increasing byte width. This value is the maximum byte
+     *         width where the test transaction eventually succeeded. */
+    unsigned long max_load_memory;
+
+    /** @brief The number of attempts taken for the max_rmw_memory test to
+     *         succeed. */
+    unsigned long min_rmw_attempts;
+
+    /** @brief The number of attempts taken for the max_load_memory test to
+     *         succeed. */
+    unsigned long min_load_attempts;
+
+} patomic_transaction_recommended_t;
+
+
+/**
+ * @addtogroup transaction
+ *
+ * @brief
+ *   Transaction safe counterparts to core <string.h> functions.
+ *
+ * @warning
+ *   These functions are **NOT** atomic (which is why they're here are not in
+ *   the header with the rest of the atomic functions). They are designed to be
+ *   used inside transactions.
+ *
+ * @warning
+ *   Compilers may insert calls to <string.h> functions such as memcpy without
+ *   these functions being called explicitly. If "required" is 1, then these
+ *   implicit usages of these functions may cause a transaction to unexpectedly
+ *   abort.
+ *
+ * @details
+ *   - these functions are direct counterparts to their <string.h> variants,
+ *     with identical semantics and constraints                                 \n
+ *   - the <string.h> variants may use instructions which may cause a
+ *     transaction to abort (e.g. vzeroupper on x86_64 in memcmp)               \n
+ *   - these functions are guaranteed not to cause an abort due to the usage of
+ *     such instructions (although they may still cause an abort for other
+ *     reasons, such as accessing too much memory)                              \n
+ *   - these counterparts will typically be significantly faster than a volatile
+ *     char loop
+ */
+typedef struct {
+
+    /** @brief Value is 1 if <string.h> functions may cause a transactional
+     *         abort, and 0 if they're safe to use (in which case the function
+     *         pointers here will point to the <string.h> variants). */
+    int required;
+
+    /** @brief Transaction safe version of <string.h> function memcpy. */
+    void * (* fp_memcpy) (void *dst, const void *src, size_t len);
+
+    /** @brief Transaction safe version of <string.h> function memmove. */
+    void * (* fp_memmove) (void *dst, const void *src, size_t len);
+
+    /** @brief Transaction safe version of <string.h> function memset. */
+    void * (* fp_memset) (void *dst, int value, size_t len);
+
+    /** @brief Transaction safe version of <string.h> function memcmp. */
+    void * (* fp_memcmp) (const void *lhs, const void *rhs, size_t len);
+
+} patomic_transaction_safe_string_t;
+
+
+#ifdef __cplusplus
+}  /* extern "C" */
+#endif
+
+#endif  /* PATOMIC_API_TRANSACTION_H */
diff --git a/include/patomic/patomic.h b/include/patomic/patomic.h
index 2ce79ea60..12f442efb 100644
--- a/include/patomic/patomic.h
+++ b/include/patomic/patomic.h
@@ -1,17 +1,145 @@
 #ifndef PATOMIC_PATOMIC_H
 #define PATOMIC_PATOMIC_H
 
-#include <patomic/patomic_export.h>
+#include "api/align.h"
+#include "api/combine.h"
+#include "api/core.h"
+#include "api/feature_check.h"
+#include "api/ids.h"
+#include "api/memory_order.h"
+#include "api/ops.h"
+#include "api/options.h"
+#include "api/transaction.h"
+
+#include <patomic/api/export.h>
+#include <patomic/api/version.h>
+
+#include <stddef.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 
-PATOMIC_EXPORT int
-patomic_example_add(
-    int a,
-    int b
+/**
+ * @addtogroup patomic
+ *
+ * @brief
+ *   Combines all implementations with implicit memory order matching both kinds
+ *   and ids in an order that yields the least strict alignment requirements,
+ *   with recommended alignment being prioritised over minimum alignment.
+ *
+ * @param byte_width
+ *   Width in bytes of type to support.
+ *
+ * @param order
+ *   Memory order to implicitly use for all atomic operations.
+ *
+ * @param opts
+ *   One or more patomic_option_t flags combined. Passed on to each internal
+ *   implementation to be used in an unspecified manner.
+ *
+ * @param kinds
+ *   One or more patomic_kind_t flags combined.
+ *
+ * @param ids
+ *   One or more patomic_id_t flags combined.
+ *
+ * @returns
+ *   Combined implementations matching both kinds and ids. If no such
+ *   implementations exist, the NULL implementation is returned.
+ *
+ * @note
+ *   The alignment requirements returned by this function will always be valid
+ *   even if no operations are supported.
+ */
+PATOMIC_EXPORT patomic_t
+patomic_create(
+    size_t byte_width,
+    patomic_memory_order_t order,
+    unsigned int opts,
+    unsigned int kinds,
+    unsigned long ids
+);
+
+
+/**
+ * @addtogroup patomic
+ *
+ * @brief
+ *   Combines all implementations with implicit memory order matching both kinds
+ *   and ids in an order that yields the least strict alignment requirements,
+ *   with recommended alignment being prioritised over minimum alignment.
+ *
+ * @param byte_width
+ *   Width in bytes of type to support.
+ *
+ * @param opts
+ *   One or more patomic_option_t flags combined. Passed on to each internal
+ *   implementation to be used in an unspecified manner.
+ *
+ * @param kinds
+ *   One or more patomic_kind_t flags combined.
+ *
+ * @param ids
+ *   One or more patomic_id_t flags combined.
+ *
+ * @returns
+ *   Combined implementations matching both kinds and ids. If no such
+ *   implementations exist, the NULL implementation is returned.
+ *
+ * @note
+ *   The alignment requirements returned by this function will always be valid
+ *   even if no operations are supported.
+ */
+PATOMIC_EXPORT patomic_explicit_t
+patomic_create_explicit(
+    size_t byte_width,
+    unsigned int opts,
+    unsigned int kinds,
+    unsigned long ids
+);
+
+
+/**
+ * @addtogroup patomic
+ *
+ * @brief
+ *   Provides the implementation implemented using a sequentially consistent
+ *   transaction with the most efficient kind that supports at least a single
+ *   operation. If multiple implementations fulfil these requirements, it is
+ *   unspecified which one will be returned.
+ *
+ * @note
+ *   Implementations are not combined because it is too complicated to do
+ *   properly, and because it is not expected that any platform will have more
+ *   than one set of APIs using hardware support for performing lock-free
+ *   transactional operations.
+ *
+ * @param opts
+ *   One or more patomic_option_t flags combined. Passed on to each internal
+ *   implementation to be used in an unspecified manner.
+ *
+ * @param kinds
+ *   One or more patomic_kind_t flags combined.
+ *
+ * @param ids
+ *   One or more patomic_id_t flags combined.
+ *
+ * @returns
+ *   The implementation with the most efficient kind that supports at least a
+ *   single operation. If no such implementations exist, the NULL implementation
+ *   is returned.
+ *
+ * @note
+ *   The alignment requirements returned by this function will always be valid
+ *   even if no operations are supported.
+ */
+PATOMIC_EXPORT patomic_transaction_t
+patomic_create_transaction(
+    unsigned int opts,
+    unsigned int kinds,
+    unsigned long ids
 );
 
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index dd861e061..825ff6018 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,4 +1,10 @@
-target_sources(
-    ${target_name} PRIVATE
-    "patomic.c"
+# add all subdirectories
+add_subdirectory(api)
+add_subdirectory(impl)
+add_subdirectory(include)
+add_subdirectory(stdlib)
+
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    patomic.c
 )
diff --git a/src/api/CMakeLists.txt b/src/api/CMakeLists.txt
new file mode 100644
index 000000000..8aabd87d5
--- /dev/null
+++ b/src/api/CMakeLists.txt
@@ -0,0 +1,11 @@
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    align.c
+    combine.c
+    feature_check_any_all.c
+    feature_check_leaf.c
+    ids.c
+    memory_order.c
+    transaction.c
+    version.c
+)
diff --git a/src/api/align.c b/src/api/align.c
new file mode 100644
index 000000000..749d0df5e
--- /dev/null
+++ b/src/api/align.c
@@ -0,0 +1,122 @@
+#include <patomic/api/align.h>
+#include <patomic/internal/align.h>
+
+#include <patomic/stdlib/math.h>
+#include <patomic/stdlib/stdint.h>
+
+#include <patomic/stdlib/assert.h>
+
+
+size_t
+patomic_cache_line_size(void)
+{
+    return PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE;
+}
+
+
+int
+patomic_align_meets_recommended(
+    const volatile void *const ptr,
+    const patomic_align_t align
+)
+{
+    /* declarations */
+    patomic_intptr_unsigned_t addr = (patomic_intptr_unsigned_t) ptr;
+
+    /* check that recommended alignment is valid */
+    if(!patomic_unsigned_is_pow2(align.recommended))
+    {
+        return 0;
+    }
+
+    /* check that addr pointer is aligned to recommended alignment */
+    return patomic_unsigned_mod_pow2(addr, align.recommended) == 0;
+}
+
+
+int
+patomic_align_meets_minimum(
+    const volatile void *const ptr,
+    const patomic_align_t align,
+    const size_t width
+)
+{
+    /* declarations */
+    patomic_intptr_unsigned_t addr = (patomic_intptr_unsigned_t) ptr;
+
+    /* check that minimum alignment is valid */
+    if(!patomic_unsigned_is_pow2(align.minimum))
+    {
+        return 0;
+    }
+
+    /* check that addr ptr is aligned to minimum alignment */
+    if(patomic_unsigned_mod_pow2(addr, align.minimum) == 0)
+    {
+        /* check if minimum alignment is always valid */
+        if (align.size_within == 0)
+        {
+            return 1;
+        }
+
+        /* make addr value less than size_within */
+        addr = (patomic_intptr_unsigned_t) \
+            patomic_unsigned_mod(addr, align.size_within);
+
+        /* check that buffer starting at addr doesn't extend past size_within */
+        return (addr + width) <= align.size_within;
+    }
+
+    /* addr ptr is not aligned to minimum alignment */
+    return 0;
+}
+
+
+int
+patomic_internal_compare_align(
+    const patomic_align_t lhs,
+    const patomic_align_t rhs
+)
+{
+    /* recommended takes priority over minimum */
+    if (lhs.recommended < rhs.recommended)
+    {
+        return -1;
+    }
+    else if (lhs.recommended > rhs.recommended)
+    {
+        return 1;
+    }
+
+    /* minimum takes priority over size_within */
+    if (lhs.minimum < rhs.minimum)
+    {
+        return -1;
+    }
+    else if (lhs.minimum > rhs.minimum)
+    {
+        return 1;
+    }
+
+    /* prioritize zero size_within, and then largest size_within */
+    if (lhs.size_within == rhs.size_within)
+    {
+        return 0;
+    }
+    else if (lhs.size_within == 0)
+    {
+        return -1;
+    }
+    else if (rhs.size_within == 0)
+    {
+        return 1;
+    }
+    else if (lhs.size_within > rhs.size_within)
+    {
+        return -1;
+    }
+    else /* if (lhs.size_within < rhs.size_within) */
+    {
+        return 1;
+    }
+}
diff --git a/src/api/combine.c b/src/api/combine.c
new file mode 100644
index 000000000..1beb9e5cd
--- /dev/null
+++ b/src/api/combine.c
@@ -0,0 +1,149 @@
+#include <patomic/api/combine.h>
+#include <patomic/internal/combine.h>
+
+
+#define PATOMIC_SET_MAX(ops, other_ops, member) \
+    do {                                        \
+        if (ops->member < other_ops->member)    \
+        {                                       \
+            ops->member = other_ops->member;    \
+        }                                       \
+    }                                           \
+    while (0)
+
+
+#define PATOMIC_SET_MIN_NZ(ops, other_ops, member)                   \
+    do {                                                             \
+        if (other_ops->member != 0)                                  \
+        {                                                            \
+            if (ops->member == 0 || ops->member > other_ops->member) \
+            {                                                        \
+                ops->member = other_ops->member;                     \
+            }                                                        \
+        }                                                            \
+    }                                                                \
+    while (0)
+
+
+#define PATOMIC_COMBINE_ALIGN(core, other_core)                  \
+    do {                                                         \
+        PATOMIC_SET_MAX(core, other_core, align.recommended);    \
+        PATOMIC_SET_MAX(core, other_core, align.minimum);        \
+        PATOMIC_SET_MIN_NZ(core, other_core, align.size_within); \
+    }                                                            \
+    while (0)
+
+
+#define PATOMIC_COPY_IF_NULL(i, core, other_core, member)       \
+    do {                                                        \
+        if (core->member == NULL && other_core->member != NULL) \
+        {                                                       \
+            core->member = other_core->member;                  \
+            ++i;                                                \
+        }                                                       \
+    }                                                           \
+    while (0)
+
+
+#define PATOMIC_COMBINE_OPS(i, core, other_core)                                    \
+    do {                                                                            \
+        /* base */                                                                  \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.fp_store);                    \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.fp_load);                     \
+                                                                                    \
+        /* xchg */                                                                  \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.xchg_ops.fp_exchange);        \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.xchg_ops.fp_cmpxchg_weak);    \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.xchg_ops.fp_cmpxchg_strong);  \
+                                                                                    \
+        /* bitwise */                                                               \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.bitwise_ops.fp_test);         \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.bitwise_ops.fp_test_compl);   \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.bitwise_ops.fp_test_set);     \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.bitwise_ops.fp_test_reset);   \
+                                                                                    \
+        /* binary - void */                                                         \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.binary_ops.fp_or);            \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.binary_ops.fp_xor);           \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.binary_ops.fp_and);           \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.binary_ops.fp_not);           \
+                                                                                    \
+        /* binary - fetch */                                                        \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.binary_ops.fp_fetch_or);      \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.binary_ops.fp_fetch_xor);     \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.binary_ops.fp_fetch_and);     \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.binary_ops.fp_fetch_not);     \
+                                                                                    \
+        /* arithmetic - void */                                                     \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_add);       \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_sub);       \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_inc);       \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_dec);       \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_neg);       \
+                                                                                    \
+        /* arithmetic - fetch */                                                    \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_fetch_add); \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_fetch_sub); \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_fetch_inc); \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_fetch_dec); \
+        PATOMIC_COPY_IF_NULL(i, core, other_core, ops.arithmetic_ops.fp_fetch_neg); \
+    }                                                                               \
+    while (0)
+
+
+void
+patomic_internal_combine(
+    patomic_t *const priority,
+    const patomic_t *const other
+)
+{
+    /* count how many ops were copied over */
+    int copy_count = 0;
+    PATOMIC_COMBINE_OPS(copy_count, priority, other);
+
+    /* only combine alignment if at least one op was copied over */
+    if (copy_count > 0)
+    {
+        PATOMIC_COMBINE_ALIGN(priority, other);
+    }
+}
+
+
+void
+patomic_combine(
+    patomic_t *const priority,
+    const patomic_t *const other
+)
+{
+    /* defer to internal implementation */
+    patomic_internal_combine(priority, other);
+}
+
+
+void
+patomic_internal_combine_explicit(
+    patomic_explicit_t *const priority,
+    const patomic_explicit_t *const other
+)
+{
+    /* count how many ops were copied over */
+    int copy_count = 0;
+    PATOMIC_COMBINE_OPS(copy_count, priority, other);
+
+    /* only combine alignment if at least one op was copied over */
+    if (copy_count > 0)
+    {
+        PATOMIC_COMBINE_ALIGN(priority, other);
+    }
+}
+
+
+void
+patomic_combine_explicit(
+    patomic_explicit_t *const priority,
+    const patomic_explicit_t *const other
+)
+{
+    /* defer to internal implementation */
+    patomic_internal_combine_explicit(priority, other);
+}
diff --git a/src/api/feature_check_any_all.c b/src/api/feature_check_any_all.c
new file mode 100644
index 000000000..21203764e
--- /dev/null
+++ b/src/api/feature_check_any_all.c
@@ -0,0 +1,320 @@
+#include <patomic/api/feature_check.h>
+#include <patomic/internal/feature_check.h>
+
+
+#define PATOMIC_UNSET_OPCAT_LDST(ops, cats, and_or) \
+    do {                                            \
+        if ((cats & patomic_opcat_LDST)             \
+            && ((ops->fp_load  != NULL) and_or      \
+                (ops->fp_store != NULL)))           \
+        {                                           \
+            cats ^= patomic_opcat_LDST;             \
+        }                                           \
+    }                                               \
+    while (0)
+
+
+#define PATOMIC_UNSET_OPCAT_XCHG(ops, cats, and_or)              \
+    do {                                                         \
+        if ((cats & patomic_opcat_XCHG)                          \
+            && ((ops->xchg_ops.fp_exchange       != NULL) and_or \
+                (ops->xchg_ops.fp_cmpxchg_weak   != NULL) and_or \
+                (ops->xchg_ops.fp_cmpxchg_strong != NULL)))      \
+        {                                                        \
+            cats ^= patomic_opcat_XCHG;                          \
+        }                                                        \
+    }                                                            \
+    while (0)
+
+
+#define PATOMIC_UNSET_OPCAT_BIT(ops, cats, and_or)              \
+    do {                                                        \
+        if ((cats & patomic_opcat_BIT)                          \
+            && ((ops->bitwise_ops.fp_test       != NULL) and_or \
+                (ops->bitwise_ops.fp_test_compl != NULL) and_or \
+                (ops->bitwise_ops.fp_test_set   != NULL) and_or \
+                (ops->bitwise_ops.fp_test_reset != NULL)))      \
+        {                                                       \
+            cats ^= patomic_opcat_BIT;                          \
+        }                                                       \
+    }                                                           \
+    while (0)
+
+
+#define PATOMIC_UNSET_OPCAT_BIN(ops, cats, and_or, vf, fp_sep)   \
+    do {                                                         \
+        if ((cats & patomic_opcat_BIN_##vf)                      \
+            && ((ops->binary_ops.fp##fp_sep##or  != NULL) and_or \
+                (ops->binary_ops.fp##fp_sep##xor != NULL) and_or \
+                (ops->binary_ops.fp##fp_sep##and != NULL) and_or \
+                (ops->binary_ops.fp##fp_sep##not != NULL)))      \
+        {                                                        \
+            cats ^= patomic_opcat_BIN_##vf;                      \
+        }                                                        \
+    }                                                            \
+    while (0)
+
+#define PATOMIC_UNSET_OPCAT_BIN_V(ops, cats, and_or) \
+    PATOMIC_UNSET_OPCAT_BIN(ops, cats, and_or, V, _)
+
+#define PATOMIC_UNSET_OPCAT_BIN_F(ops, cats, and_or) \
+    PATOMIC_UNSET_OPCAT_BIN(ops, cats, and_or, F, _fetch_)
+
+
+#define PATOMIC_UNSET_OPCAT_ARI(ops, cats, and_or, vf, fp_sep)       \
+    do {                                                             \
+        if ((cats & patomic_opcat_ARI_##vf)                          \
+            && ((ops->arithmetic_ops.fp##fp_sep##add != NULL) and_or \
+                (ops->arithmetic_ops.fp##fp_sep##sub != NULL) and_or \
+                (ops->arithmetic_ops.fp##fp_sep##inc != NULL) and_or \
+                (ops->arithmetic_ops.fp##fp_sep##dec != NULL) and_or \
+                (ops->arithmetic_ops.fp##fp_sep##neg != NULL)))      \
+        {                                                            \
+            cats ^= patomic_opcat_ARI_##vf;                          \
+        }                                                            \
+    }                                                                \
+    while (0)
+
+#define PATOMIC_UNSET_OPCAT_ARI_V(ops, cats, and_or) \
+    PATOMIC_UNSET_OPCAT_ARI(ops, cats, and_or, V, _)
+
+#define PATOMIC_UNSET_OPCAT_ARI_F(ops, cats, and_or) \
+    PATOMIC_UNSET_OPCAT_ARI(ops, cats, and_or, F, _fetch_)
+
+
+#define PATOMIC_UNSET_OPCAT_TSPEC(ops, cats, and_or)                \
+    do {                                                            \
+        if ((cats & patomic_opcat_TSPEC)                            \
+            && ((ops->special_ops.fp_double_cmpxchg != NULL) and_or \
+                (ops->special_ops.fp_multi_cmpxchg  != NULL) and_or \
+                (ops->special_ops.fp_generic        != NULL) and_or \
+                (ops->special_ops.fp_generic_wfb    != NULL)))      \
+        {                                                           \
+            cats ^= patomic_opcat_TSPEC;                            \
+        }                                                           \
+    }                                                               \
+    while (0)
+
+
+#define PATOMIC_UNSET_OPCAT_TFLAG(ops, cats, and_or)       \
+    do {                                                   \
+        if ((cats & patomic_opcat_TFLAG)                   \
+            && ((ops->flag_ops.fp_test     != NULL) and_or \
+                (ops->flag_ops.fp_test_set != NULL) and_or \
+                (ops->flag_ops.fp_clear    != NULL)))      \
+        {                                                  \
+            cats ^= patomic_opcat_TFLAG;                   \
+        }                                                  \
+    }                                                      \
+    while (0)
+
+
+#define PATOMIC_UNSET_OPCAT_TRAW(ops, cats, and_or)      \
+    do {                                                 \
+        if ((cats & patomic_opcat_TRAW)                  \
+            && ((ops->raw_ops.fp_tbegin  != NULL) and_or \
+                (ops->raw_ops.fp_tabort  != NULL) and_or \
+                (ops->raw_ops.fp_tcommit != NULL) and_or \
+                (ops->raw_ops.fp_ttest != NULL)))        \
+        {                                                \
+            cats ^= patomic_opcat_TRAW;                  \
+        }                                                \
+    }                                                    \
+    while (0)
+
+
+unsigned int
+patomic_feature_check_all(
+    const patomic_ops_t *const ops,
+    unsigned int opcats
+)
+{
+    /* short circuit if possible */
+    if (opcats == 0)
+    {
+        return opcats;
+    }
+
+    /* unset bits for opcats where all ops are present */
+    PATOMIC_UNSET_OPCAT_LDST(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_XCHG(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIT(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIN_V(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIN_F(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_ARI_V(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_ARI_F(ops, opcats, &&);
+
+    /* return updated opcats */
+    return opcats;
+}
+
+
+unsigned int
+patomic_feature_check_all_explicit(
+    const patomic_ops_explicit_t *const ops,
+    unsigned int opcats
+)
+{
+    /* short circuit if possible */
+    if (opcats == 0)
+    {
+        return opcats;
+    }
+
+    /* unset bits for opcats where all ops are present */
+    PATOMIC_UNSET_OPCAT_LDST(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_XCHG(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIT(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIN_V(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIN_F(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_ARI_V(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_ARI_F(ops, opcats, &&);
+
+    /* return updated opcats */
+    return opcats;
+}
+
+
+unsigned int
+patomic_feature_check_all_transaction(
+    const patomic_ops_transaction_t *const ops,
+    unsigned int opcats
+)
+{
+    /* short circuit if possible */
+    if (opcats == 0)
+    {
+        return opcats;
+    }
+
+    /* unset bits for opcats where all ops are present */
+    PATOMIC_UNSET_OPCAT_LDST(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_XCHG(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIT(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIN_V(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_BIN_F(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_ARI_V(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_ARI_F(ops, opcats, &&);
+    /* transaction specific opcats */
+    PATOMIC_UNSET_OPCAT_TSPEC(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_TFLAG(ops, opcats, &&);
+    PATOMIC_UNSET_OPCAT_TRAW(ops, opcats, &&);
+
+    /* return updated opcats */
+    return opcats;
+}
+
+
+unsigned int
+patomic_internal_feature_check_any(
+    const patomic_ops_t *const ops,
+    unsigned int opcats
+)
+{
+    /* short circuit if possible */
+    if (opcats == 0)
+    {
+        return opcats;
+    }
+
+    /* unset bits for opcats where all ops are present */
+    PATOMIC_UNSET_OPCAT_LDST(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_XCHG(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIT(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIN_V(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIN_F(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_ARI_V(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_ARI_F(ops, opcats, ||);
+
+    /* return updated opcats */
+    return opcats;
+}
+
+
+unsigned int
+patomic_feature_check_any(
+    const patomic_ops_t *const ops,
+    const unsigned int opcats
+)
+{
+    /* defer to internal implementation */
+    return patomic_internal_feature_check_any(ops, opcats);
+}
+
+
+unsigned int
+patomic_internal_feature_check_any_explicit(
+    const patomic_ops_explicit_t *const ops,
+    unsigned int opcats
+)
+{
+    /* short circuit if possible */
+    if (opcats == 0)
+    {
+        return opcats;
+    }
+
+    /* unset bits for opcats where all ops are present */
+    PATOMIC_UNSET_OPCAT_LDST(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_XCHG(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIT(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIN_V(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIN_F(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_ARI_V(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_ARI_F(ops, opcats, ||);
+
+    /* return updated opcats */
+    return opcats;
+}
+
+
+unsigned int
+patomic_feature_check_any_explicit(
+    const patomic_ops_explicit_t *const ops,
+    const unsigned int opcats
+)
+{
+    /* defer to internal implementation */
+    return patomic_internal_feature_check_any_explicit(ops, opcats);
+}
+
+
+unsigned int
+patomic_internal_feature_check_any_transaction(
+    const patomic_ops_transaction_t *const ops,
+    unsigned int opcats
+)
+{
+    /* short circuit if possible */
+    if (opcats == 0)
+    {
+        return opcats;
+    }
+
+    /* unset bits for opcats where all ops are present */
+    PATOMIC_UNSET_OPCAT_LDST(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_XCHG(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIT(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIN_V(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_BIN_F(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_ARI_V(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_ARI_F(ops, opcats, ||);
+    /* transaction specific opcats */
+    PATOMIC_UNSET_OPCAT_TSPEC(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_TFLAG(ops, opcats, ||);
+    PATOMIC_UNSET_OPCAT_TRAW(ops, opcats, ||);
+
+    /* return updated opcats */
+    return opcats;
+}
+
+
+unsigned int
+patomic_feature_check_any_transaction(
+    const patomic_ops_transaction_t *const ops,
+    const unsigned int opcats
+)
+{
+    /* defer to internal implementation */
+    return patomic_internal_feature_check_any_transaction(ops, opcats);
+}
diff --git a/src/api/feature_check_leaf.c b/src/api/feature_check_leaf.c
new file mode 100644
index 000000000..4358c3917
--- /dev/null
+++ b/src/api/feature_check_leaf.c
@@ -0,0 +1,245 @@
+#include <patomic/api/feature_check.h>
+
+#include <patomic/macros/unreachable.h>
+#include <patomic/stdlib/assert.h>
+#include <patomic/stdlib/math.h>
+
+
+#define PATOMIC_UNSET_OPKIND(fp_op, kinds, kind)              \
+    do {                                                      \
+        if (fp_op != NULL)                                    \
+        {                                                     \
+            kinds &= ~((unsigned int) patomic_opkind_##kind); \
+        }                                                     \
+    }                                                         \
+    while (0)
+
+
+#define PATOMIC_UNSET_OPKINDS_LDST(ops, kinds)        \
+    PATOMIC_UNSET_OPKIND(ops->fp_load,  kinds, LOAD); \
+    PATOMIC_UNSET_OPKIND(ops->fp_store, kinds, STORE)
+
+
+#define PATOMIC_UNSET_OPKINDS_XCHG(ops, kinds)                                  \
+    PATOMIC_UNSET_OPKIND(ops->xchg_ops.fp_exchange,       kinds, EXCHANGE);     \
+    PATOMIC_UNSET_OPKIND(ops->xchg_ops.fp_cmpxchg_weak,   kinds, CMPXCHG_WEAK); \
+    PATOMIC_UNSET_OPKIND(ops->xchg_ops.fp_cmpxchg_strong, kinds, CMPXCHG_STRONG)
+
+
+#define PATOMIC_UNSET_OPKINDS_BIT(ops, kinds)                                \
+    PATOMIC_UNSET_OPKIND(ops->bitwise_ops.fp_test,       kinds, TEST);       \
+    PATOMIC_UNSET_OPKIND(ops->bitwise_ops.fp_test_compl, kinds, TEST_COMPL); \
+    PATOMIC_UNSET_OPKIND(ops->bitwise_ops.fp_test_set,   kinds, TEST_SET);   \
+    PATOMIC_UNSET_OPKIND(ops->bitwise_ops.fp_test_reset, kinds, TEST_RESET)
+
+
+#define PATOMIC_UNSET_OPKINDS_BIN(ops, kinds, fp_sep)                  \
+    PATOMIC_UNSET_OPKIND(ops->binary_ops.fp##fp_sep##or,  kinds, OR);  \
+    PATOMIC_UNSET_OPKIND(ops->binary_ops.fp##fp_sep##xor, kinds, XOR); \
+    PATOMIC_UNSET_OPKIND(ops->binary_ops.fp##fp_sep##and, kinds, AND); \
+    PATOMIC_UNSET_OPKIND(ops->binary_ops.fp##fp_sep##not, kinds, NOT)
+
+#define PATOMIC_UNSET_OPKINDS_BIN_V(ops, kinds) \
+    PATOMIC_UNSET_OPKINDS_BIN(ops, kinds, _)
+
+#define PATOMIC_UNSET_OPKINDS_BIN_F(ops, kinds) \
+    PATOMIC_UNSET_OPKINDS_BIN(ops, kinds, _fetch_)
+
+
+#define PATOMIC_UNSET_OPKINDS_ARI(ops, kinds, fp_sep)                      \
+    PATOMIC_UNSET_OPKIND(ops->arithmetic_ops.fp##fp_sep##add, kinds, ADD); \
+    PATOMIC_UNSET_OPKIND(ops->arithmetic_ops.fp##fp_sep##sub, kinds, SUB); \
+    PATOMIC_UNSET_OPKIND(ops->arithmetic_ops.fp##fp_sep##inc, kinds, INC); \
+    PATOMIC_UNSET_OPKIND(ops->arithmetic_ops.fp##fp_sep##dec, kinds, DEC); \
+    PATOMIC_UNSET_OPKIND(ops->arithmetic_ops.fp##fp_sep##neg, kinds, NEG)
+
+#define PATOMIC_UNSET_OPKINDS_ARI_V(ops, kinds) \
+    PATOMIC_UNSET_OPKINDS_ARI(ops, kinds, _)
+
+#define PATOMIC_UNSET_OPKINDS_ARI_F(ops, kinds) \
+    PATOMIC_UNSET_OPKINDS_ARI(ops, kinds, _fetch_)
+
+
+#define PATOMIC_UNSET_OPKINDS_TSPEC(ops, kinds)                                      \
+    PATOMIC_UNSET_OPKIND(ops->special_ops.fp_double_cmpxchg, kinds, DOUBLE_CMPXCHG); \
+    PATOMIC_UNSET_OPKIND(ops->special_ops.fp_multi_cmpxchg,  kinds, MULTI_CMPXCHG);  \
+    PATOMIC_UNSET_OPKIND(ops->special_ops.fp_generic,        kinds, GENERIC);        \
+    PATOMIC_UNSET_OPKIND(ops->special_ops.fp_generic_wfb,    kinds, GENERIC_WFB)
+
+
+#define PATOMIC_UNSET_OPKINDS_TFLAG(ops, kinds)                       \
+    PATOMIC_UNSET_OPKIND(ops->flag_ops.fp_test,     kinds, TEST);     \
+    PATOMIC_UNSET_OPKIND(ops->flag_ops.fp_test_set, kinds, TEST_SET); \
+    PATOMIC_UNSET_OPKIND(ops->flag_ops.fp_clear,    kinds, CLEAR)
+
+
+#define PATOMIC_UNSET_OPKINDS_TRAW(ops, kinds)                     \
+    PATOMIC_UNSET_OPKIND(ops->raw_ops.fp_tbegin,  kinds, TBEGIN);  \
+    PATOMIC_UNSET_OPKIND(ops->raw_ops.fp_tabort,  kinds, TABORT);  \
+    PATOMIC_UNSET_OPKIND(ops->raw_ops.fp_tcommit, kinds, TCOMMIT); \
+    PATOMIC_UNSET_OPKIND(ops->raw_ops.fp_ttest,   kinds, TTEST)
+
+
+#define PATOMIC_CASE_UNSET_OPKINDS(cat, ops, kinds) \
+    case patomic_opcat_##cat:                       \
+        PATOMIC_UNSET_OPKINDS_##cat(ops, opkinds);  \
+        break
+
+
+unsigned int
+patomic_feature_check_leaf(
+    const patomic_ops_t *const ops,
+    const patomic_opcat_t opcat,
+    unsigned int opkinds
+)
+{
+    /* ensure that a single opcat is being checked */
+    patomic_assert_always(patomic_unsigned_is_pow2(opcat));
+
+    /* short circuit if possible */
+    if (opkinds == 0)
+    {
+        return opkinds;
+    }
+
+    /* check which opcat's opkinds we want to unset */
+    switch (opcat)
+    {
+        /* unset bit for each opkind where op is present */
+        PATOMIC_CASE_UNSET_OPKINDS(LDST,  ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(XCHG,  ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIT,   ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIN_V, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIN_F, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(ARI_V, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(ARI_F, ops, opkinds);
+
+        /* unsupported opcats do not modify anything */
+        case patomic_opcat_TSPEC:
+        case patomic_opcat_TFLAG:
+        case patomic_opcat_TRAW:
+        default:
+            break;
+
+        /* opcats with zero or multiple bits set should be unreachable */
+        case patomic_opcat_NONE:
+        case patomic_opcats_BIN:
+        case patomic_opcats_ARI:
+        case patomic_opcats_IMPLICIT:
+        /* case patomic_opcats_EXPLICIT: (==IMPLICIT) */
+        case patomic_opcats_TRANSACTION:
+            PATOMIC_UNREACHABLE();
+#if PATOMIC_UNREACHABLE_IS_VCZ
+            break;
+#endif
+    }
+
+    /* return updated opkinds */
+    return opkinds;
+}
+
+
+unsigned int
+patomic_feature_check_leaf_explicit(
+    const patomic_ops_explicit_t *const ops,
+    const patomic_opcat_t opcat,
+    unsigned int opkinds
+)
+{
+    /* ensure that a single opcat is being checked */
+    patomic_assert_always(patomic_unsigned_is_pow2(opcat));
+
+    /* short circuit if possible */
+    if (opkinds == 0)
+    {
+        return opkinds;
+    }
+
+    /* check which opcat's opkinds we want to unset */
+    switch (opcat)
+    {
+        /* unset bit for each opkind where op is present */
+        PATOMIC_CASE_UNSET_OPKINDS(LDST,  ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(XCHG,  ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIT,   ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIN_V, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIN_F, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(ARI_V, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(ARI_F, ops, opkinds);
+
+        /* unsupported opcats do not modify anything */
+        case patomic_opcat_TSPEC:
+        case patomic_opcat_TFLAG:
+        case patomic_opcat_TRAW:
+        default:
+            break;
+
+        /* opcats with zero or multiple bits set should be unreachable */
+        case patomic_opcat_NONE:
+        case patomic_opcats_BIN:
+        case patomic_opcats_ARI:
+        case patomic_opcats_IMPLICIT:
+        /* case patomic_opcats_EXPLICIT: (==IMPLICIT) */
+        case patomic_opcats_TRANSACTION:
+            PATOMIC_UNREACHABLE();
+#if PATOMIC_UNREACHABLE_IS_VCZ
+            break;
+#endif
+    }
+
+    /* return updated opkinds */
+    return opkinds;
+}
+
+
+unsigned int
+patomic_feature_check_leaf_transaction(
+    const patomic_ops_transaction_t *const ops,
+    const patomic_opcat_t opcat,
+    unsigned int opkinds
+)
+{
+    /* ensure that a single opcat is being checked */
+    patomic_assert_always(patomic_unsigned_is_pow2(opcat));
+
+    /* short circuit if possible */
+    if (opkinds == 0)
+    {
+        return opkinds;
+    }
+
+    /* check which opcat's opkinds we want to unset */
+    switch (opcat)
+    {
+        /* unset bit for each opkind where op is present */
+        PATOMIC_CASE_UNSET_OPKINDS(LDST,  ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(XCHG,  ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIT,   ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIN_V, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(BIN_F, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(ARI_V, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(ARI_F, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(TSPEC, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(TFLAG, ops, opkinds);
+        PATOMIC_CASE_UNSET_OPKINDS(TRAW,  ops, opkinds);
+
+        /* unsupported opcats do not modify anything */
+        default:
+            break;
+
+        /* opcats with zero or multiple bits set should be unreachable */
+        case patomic_opcat_NONE:
+        case patomic_opcats_BIN:
+        case patomic_opcats_ARI:
+        case patomic_opcats_IMPLICIT:
+        /* case patomic_opcats_EXPLICIT: (==IMPLICIT) */
+        case patomic_opcats_TRANSACTION:
+            PATOMIC_UNREACHABLE();
+#if PATOMIC_UNREACHABLE_IS_VCZ
+            break;
+#endif
+    }
+
+    /* return updated opkinds */
+    return opkinds;
+}
diff --git a/src/api/ids.c b/src/api/ids.c
new file mode 100644
index 000000000..958956397
--- /dev/null
+++ b/src/api/ids.c
@@ -0,0 +1,58 @@
+#include "../impl/register.h"
+
+#include <patomic/api/ids.h>
+
+#include <patomic/stdlib/assert.h>
+#include <patomic/stdlib/math.h>
+
+
+unsigned long
+patomic_get_ids(
+    const unsigned int kinds
+)
+{
+    /* declarations */
+    patomic_impl_t const *begin = patomic_impl_register;
+    patomic_impl_t const *const end = begin + PATOMIC_IMPL_REGISTER_SIZE;
+    unsigned long ids = 0ul;
+
+    /* combine ids */
+    for (; begin != end; ++begin)
+    {
+        const unsigned int kind = (unsigned int) begin->kind;
+        const unsigned long id = (unsigned long) begin->id;
+        if (kind & kinds)
+        {
+            ids |= id;
+        }
+    }
+
+    /* result */
+    return ids;
+}
+
+
+unsigned int
+patomic_get_kind(
+    const unsigned long id
+)
+{
+    /* declarations */
+    patomic_impl_t const *begin = patomic_impl_register;
+    patomic_impl_t const *const end = begin + PATOMIC_IMPL_REGISTER_SIZE;
+
+    /* always assert because we have no other way to surface error */
+    patomic_assert_always(patomic_unsigned_is_pow2_or_zero(id));
+
+    /* find kind */
+    for (; begin != end; ++begin)
+    {
+        if (begin->id == id)
+        {
+            return (unsigned int) begin->kind;
+        }
+    }
+
+    /* didn't find kind */
+    return patomic_kind_UNKN;
+}
diff --git a/src/api/memory_order.c b/src/api/memory_order.c
new file mode 100644
index 000000000..144942279
--- /dev/null
+++ b/src/api/memory_order.c
@@ -0,0 +1,35 @@
+#include <patomic/api/memory_order.h>
+
+
+int
+patomic_is_valid_order(const int order)
+{
+    return PATOMIC_IS_VALID_ORDER(order);
+}
+
+
+int
+patomic_is_valid_store_order(const int order)
+{
+    return PATOMIC_IS_VALID_STORE_ORDER(order);
+}
+
+
+int patomic_is_valid_load_order(const int order)
+{
+    return PATOMIC_IS_VALID_LOAD_ORDER(order);
+}
+
+
+int
+patomic_is_valid_fail_order(const int succ, const int fail)
+{
+    return PATOMIC_IS_VALID_FAIL_ORDER(succ, fail);
+}
+
+
+int
+patomic_cmpxchg_fail_order(const int succ)
+{
+    return PATOMIC_CMPXCHG_FAIL_ORDER(succ);
+}
diff --git a/src/api/transaction.c b/src/api/transaction.c
new file mode 100644
index 000000000..af3703b06
--- /dev/null
+++ b/src/api/transaction.c
@@ -0,0 +1,24 @@
+#include <patomic/api/transaction.h>
+
+
+unsigned char
+patomic_transaction_abort_reason(
+    const unsigned int status
+)
+{
+    /* declarations */
+    unsigned int kind;
+    unsigned int reason;
+
+    /* first 8 bits are the kind of status */
+    /* check that explicit abort happened */
+    kind = status & 0xffU;
+    if (kind != patomic_TABORT_EXPLICIT)
+    {
+        return 0;
+    }
+
+    /* next 8 bits are the abort reason */
+    reason = (status >> 8U) & 0xffU;
+    return (unsigned char) reason;
+}
diff --git a/src/api/version.c b/src/api/version.c
new file mode 100644
index 000000000..b69fd08ba
--- /dev/null
+++ b/src/api/version.c
@@ -0,0 +1,40 @@
+#include <patomic/api/version.h>
+
+
+const char *
+patomic_version_string(void)
+{
+    return PATOMIC_VERSION_STRING;
+}
+
+
+int
+patomic_version_major(void)
+{
+    return PATOMIC_VERSION_MAJOR;
+}
+
+
+int
+patomic_version_minor(void)
+{
+    return PATOMIC_VERSION_MINOR;
+}
+
+
+int
+patomic_version_patch(void)
+{
+    return PATOMIC_VERSION_PATCH;
+}
+
+
+int
+patomic_version_compatible_with(
+    const int major,
+    const int minor,
+    const int patch
+)
+{
+    return PATOMIC_VERSION_COMPATIBLE_WITH(major, minor, patch);
+}
diff --git a/src/impl/CMakeLists.txt b/src/impl/CMakeLists.txt
new file mode 100644
index 000000000..dd79bc131
--- /dev/null
+++ b/src/impl/CMakeLists.txt
@@ -0,0 +1,7 @@
+# add all subdirectories
+add_subdirectory(null)
+
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    register.h
+)
diff --git a/src/impl/null/CMakeLists.txt b/src/impl/null/CMakeLists.txt
new file mode 100644
index 000000000..4ad072c42
--- /dev/null
+++ b/src/impl/null/CMakeLists.txt
@@ -0,0 +1,5 @@
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    null.h
+    null.c
+)
diff --git a/src/impl/null/null.c b/src/impl/null/null.c
new file mode 100644
index 000000000..1d4f682c7
--- /dev/null
+++ b/src/impl/null/null.c
@@ -0,0 +1,69 @@
+#include "null.h"
+
+#include <patomic/macros/ignore_unused.h>
+
+
+patomic_t
+patomic_impl_create_null(
+    const size_t byte_width,
+    const patomic_memory_order_t order,
+    const unsigned int opts
+)
+{
+    /* zero all fields */
+    patomic_t impl = {0};
+
+    /* ignore parameters */
+    PATOMIC_IGNORE_UNUSED(byte_width);
+    PATOMIC_IGNORE_UNUSED(order);
+    PATOMIC_IGNORE_UNUSED(opts);
+
+    /* set a valid minimal alignment */
+    impl.align.recommended = 1;
+    impl.align.minimum = 1;
+
+    /* return */
+    return impl;
+}
+
+
+patomic_explicit_t
+patomic_impl_create_explicit_null(
+    const size_t byte_width,
+    const unsigned int opts
+)
+{
+    /* zero all fields */
+    patomic_explicit_t impl = {0};
+
+    /* ignore parameters */
+    PATOMIC_IGNORE_UNUSED(byte_width);
+    PATOMIC_IGNORE_UNUSED(opts);
+
+    /* set a valid minimal alignment */
+    impl.align.recommended = 1;
+    impl.align.minimum = 1;
+
+    /* return */
+    return impl;
+}
+
+
+patomic_transaction_t
+patomic_impl_create_transaction_null(
+    const unsigned int opts
+)
+{
+    /* zero all fields */
+    patomic_transaction_t impl = {0};
+
+    /* ignore parameters */
+    PATOMIC_IGNORE_UNUSED(opts);
+
+    /* set a valid minimal alignment */
+    impl.align.recommended = 1;
+    impl.align.minimum = 1;
+
+    /* return */
+    return impl;
+}
diff --git a/src/impl/null/null.h b/src/impl/null/null.h
new file mode 100644
index 000000000..719d1a969
--- /dev/null
+++ b/src/impl/null/null.h
@@ -0,0 +1,78 @@
+#ifndef PATOMIC_IMPL_NULL_H
+#define PATOMIC_IMPL_NULL_H
+
+#include <patomic/patomic.h>
+
+#include <stddef.h>
+
+
+/**
+ * @addtogroup impl.null
+ *
+ * @brief
+ *   No operations are supported.
+ *
+ * @param byte_width
+ *   Value is ignored.
+ *
+ * @param order
+ *   Value is ignored.
+ *
+ * @param opts
+ *   Value is ignored.
+ *
+ * @return
+ *   Implementation where no operations are supported and alignment requirements
+ *   are the minimum possible.
+ */
+patomic_t
+patomic_impl_create_null(
+    size_t byte_width,
+    patomic_memory_order_t order,
+    unsigned int opts
+);
+
+
+/**
+ * @addtogroup impl.null
+ *
+ * @brief
+ *   No operations are supported.
+ *
+ * @param byte_width
+ *   Value is ignored.
+ *
+ * @param opts
+ *   Value is ignored.
+ *
+ * @return
+ *   Implementation where no operations are supported and alignment requirements
+ *   are the minimum possible.
+ */
+patomic_explicit_t
+patomic_impl_create_explicit_null(
+    size_t byte_width,
+    unsigned int opts
+);
+
+
+/**
+ * @addtogroup impl.null
+ *
+ * @brief
+ *   No operations are supported.
+ *
+ * @param opts
+ *   Value is ignored.
+ *
+ * @return
+ *   Implementation where no operations are supported and alignment requirements
+ *   are the minimum possible.
+ */
+patomic_transaction_t
+patomic_impl_create_transaction_null(
+    unsigned int opts
+);
+
+
+#endif  /* PATOMIC_IMPL_NULL_H */
diff --git a/src/impl/register.h b/src/impl/register.h
new file mode 100644
index 000000000..a77ac9f84
--- /dev/null
+++ b/src/impl/register.h
@@ -0,0 +1,86 @@
+#ifndef PATOMIC_REGISTER_H
+#define PATOMIC_REGISTER_H
+
+#include "null/null.h"
+
+#include <patomic/patomic.h>
+
+
+/**
+ * @addtogroup register
+ *
+ * @brief
+ *   Public "interface" through which the rest of this library obtains
+ *   information and functionality from each internal implementation.
+ */
+typedef struct {
+
+    /** @brief Unique ID corresponding only to this implementation. */
+    patomic_id_t id;
+
+    /** @brief Estimate of implementation's runtime overhead. */
+    patomic_kind_t kind;
+
+    /** @brief Function called directly by patomic_create to obtain atomic
+     *         operations with implicit memory order from this
+     *         implementation. */
+    patomic_t (* fp_create) (size_t, patomic_memory_order_t, unsigned int);
+
+    /** @brief Function called directly by patomic_create_explicit to obtain
+     *         atomic operations with explicit memory order from this
+     *         implementation. */
+    patomic_explicit_t (* fp_create_explicit) (size_t, unsigned int);
+
+    /** @brief Function called directly by patomic_create_transaction to obtain
+     *         atomic operations implemented using a sequentially consistent
+     *         transaction from this implementation. */
+    patomic_transaction_t (* fp_create_transaction) (unsigned int);
+
+} patomic_impl_t;
+
+
+/**
+ * @addtogroup register
+ *
+ * @brief
+ *   Array containing set of all existing implementations, regardless of whether
+ *   they are supported on the current platform.
+ *
+ * @warning
+ *   Each implementation element must have a unique ID, which may not have more
+ *   than a single bit set.
+ *
+ * @note
+ *   An implementation may use the NULL implementation's creation functions if
+ *   it cannot create any implicit, explicit, or transaction operations.
+ */
+static const patomic_impl_t
+patomic_impl_register[] = {
+    {
+        patomic_id_NULL,
+        patomic_kind_UNKN,
+        patomic_impl_create_null,
+        patomic_impl_create_explicit_null,
+        patomic_impl_create_transaction_null
+    },
+    {
+        patomic_id_STDC,
+        patomic_kind_BLTN,
+        patomic_impl_create_null,
+        patomic_impl_create_explicit_null,
+        patomic_impl_create_transaction_null
+    }
+};
+
+
+/**
+ * @addtogroup register
+ *
+ * @brief
+ *   Number of elements in patomic_impl_register array.
+ */
+#define PATOMIC_IMPL_REGISTER_SIZE \
+    ((sizeof patomic_impl_register) / sizeof(patomic_impl_t))
+
+
+#endif  /* PATOMIC_REGISTER_H */
diff --git a/src/include/CMakeLists.txt b/src/include/CMakeLists.txt
new file mode 100644
index 000000000..fe3307149
--- /dev/null
+++ b/src/include/CMakeLists.txt
@@ -0,0 +1,2 @@
+# add all subdirectories
+add_subdirectory(patomic)
diff --git a/src/include/patomic/CMakeLists.txt b/src/include/patomic/CMakeLists.txt
new file mode 100644
index 000000000..787e688b6
--- /dev/null
+++ b/src/include/patomic/CMakeLists.txt
@@ -0,0 +1,9 @@
+# add all subdirectories
+add_subdirectory(internal)
+add_subdirectory(macros)
+add_subdirectory(stdlib)
+
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    config.h
+)
diff --git a/src/include/patomic/config.h b/src/include/patomic/config.h
new file mode 100644
index 000000000..2d23a5c1b
--- /dev/null
+++ b/src/include/patomic/config.h
@@ -0,0 +1,460 @@
+#ifndef PATOMIC_CONFIG_H
+#define PATOMIC_CONFIG_H
+
+
+/*
+ * Some of the following macros are generated by CMake at build time. CMake
+ * generates the <patomic/_config.h> file which is included below.
+ *
+ * If you are building this project without CMake, you have three options:
+ * 1. Provide your own version of <patomic/_config.h>.
+ * 2. Modify the defaults for those macros in this file and remove the include.
+ * 3. Define those macros via compiler flags and remove the include.
+ *
+ * Some macros may error if predefined as a safety measure, requiring option 2.
+ */
+
+#include <patomic/_config.h>
+
+
+/*
+ * SAFE CONSTANTS
+ * ==============
+ *
+ * The following macros may be modified and overridden safely, At worst, this
+ * may cause a compile-time warning/error or limit runtime functionality
+ * exposed by this library.
+ *
+ * The @note contains expected requirements for the functionality specified by
+ * the macro, as a hint to users who may try to manually set their values.
+ */
+
+#ifndef PATOMIC_HAS_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__' is available as a keyword.
+     *
+     * @note
+     *   Usually required: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_EXTN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_STDINT_H
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stdint.h> header is available.
+     *
+     * @note
+     *   Usually requires: C99.
+     */
+    #define PATOMIC_HAS_STDINT_H 0
+#endif
+
+
+#ifndef PATOMIC_HAS_WCHAR_H
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <wchar.h> header is available.
+     *
+     * @note
+     *   Usually requires: C95.
+     */
+    #define PATOMIC_HAS_WCHAR_H 0
+#endif
+
+
+#ifndef PATOMIC_HAS_LONG_LONG
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   'long long' is available as a type.
+     *
+     * @note
+     *   Usually requires: C99.
+     */
+    #define PATOMIC_HAS_LONG_LONG 0
+#endif
+
+
+#ifndef PATOMIC_HAS_LONG_LONG_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ long long' is available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_LONG_LONG_EXTN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_MS_INT128
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__int128' is available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_MS_INT128 0
+#endif
+
+
+#ifndef PATOMIC_HAS_MS_INT128_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ __int128' is available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_MS_INT128_EXTN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_STDINT_INTPTR
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stdint.h> header is available and makes 'intptr_t' available as a
+     *   type.
+     *
+     * @note
+     *   Usually requires: C99 (but the type is optional even then).
+     */
+    #define PATOMIC_HAS_STDINT_INTPTR 0
+#endif
+
+
+#ifndef PATOMIC_HAS_STDINT_INTPTR_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stdint.h> header is available and makes '__extension__ intptr_t'
+     *   available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_STDINT_INTPTR_EXTN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_STDDEF_INTPTR
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stddef.h> header is available and makes 'intptr_t' available as a
+     *   type.
+     *
+     * @note
+     *   Usually requires: old compilers not supporting <stdint.h> (pre-C99).
+     *   Microsoft puts intptr_t here in old versions of msvc.
+     */
+    #define PATOMIC_HAS_STDDEF_INTPTR 0
+#endif
+
+
+#ifndef PATOMIC_HAS_STDDEF_INTPTR_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <stddef.h> header is available and makes '__extension__ intptr_t'
+     *   available as a type.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_STDDEF_INTPTR_EXTN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_FUNC
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__func__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: C99.
+     */
+    #define PATOMIC_HAS_FUNC 0
+#endif
+
+
+#ifndef PATOMIC_HAS_FUNC_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ __func__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_FUNC_EXTN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_GNU_FUNCTION
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__FUNCTION__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_GNU_FUNCTION 0
+#endif
+
+
+#ifndef PATOMIC_HAS_GNU_FUNCTION_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ __FUNCTION__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_GNU_FUNCTION_EXTN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_GNU_PRETTY_FUNCTION
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__PRETTY_FUNCTION__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_GNU_PRETTY_FUNCTION 0
+#endif
+
+
+#ifndef PATOMIC_HAS_GNU_PRETTY_FUNCTION_EXTN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__extension__ __PRETTY_FUNCTION__' is available as a variable.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_GNU_PRETTY_FUNCTION_EXTN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_NORETURN
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '_Noreturn' is available as a function attribute.
+     *
+     * @note
+     *   Usually requires: C11.
+     */
+    #define PATOMIC_HAS_NORETURN 0
+#endif
+
+
+#ifndef PATOMIC_HAS_NORETURN_ATTR
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__attribute__((noreturn))' is available as a function attribute.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_NORETURN_ATTR 0
+#endif
+
+
+#ifndef PATOMIC_HAS_NORETURN_DSPC
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__declspec(noreturn)' is available as a function attribute.
+     *
+     * @note
+     *   Usually requires: Microsoft compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_NORETURN_DSPC 0
+#endif
+
+
+#ifndef PATOMIC_HAS_MS_ASSUME
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__assume(int)' is available as a function.
+     *
+     * @note
+     *   Usually requires: Microsoft compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_MS_ASSUME 0
+#endif
+
+
+#ifndef PATOMIC_HAS_BUILTIN_UNREACHABLE
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   '__builtin_unreachable(void)' is available as a function.
+     *
+     * @note
+     *   Usually requires: GNU compatible(-ish) compiler.
+     */
+    #define PATOMIC_HAS_BUILTIN_UNREACHABLE 0
+#endif
+
+
+#ifndef PATOMIC_HAS_WCHAR_FWIDE
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <wchar.h> header is available and makes 'fwide(FILE*, int)' available
+     *   as a function.
+     *
+     * @note
+     *   Usually requires: C95.
+     */
+    #define PATOMIC_HAS_WCHAR_FWIDE 0
+#endif
+
+
+#ifndef PATOMIC_HAS_WCHAR_FWPRINTF
+    /**
+     * @addtogroup config.safe
+     *
+     * @brief
+     *   <wchar.h> header is available and makes
+     *   'fwprintf(FILE*, const wchar_t*, ...)' available as a function.
+     *
+     * @note
+     *   Usually requires: C95.
+     */
+    #define PATOMIC_HAS_WCHAR_FWPRINTF 0
+#endif
+
+
+/*
+ * UNSAFE CONSTANTS
+ * ================
+ *
+ * The following macros should never be modified unless cross-compiling for a
+ * target platform which does not define them to have the same value as the host
+ * platform (and for some reason the compiler defines them with the host
+ * platform's value, likely a compiler bug).
+ *
+ * Changing these macros incorrectly WILL result in a library which exhibits
+ * undefined behaviour and will likely function incorrectly.
+ *
+ * For this reason, they cannot be predefined; they can only be explicitly
+ * modified in this file.
+ */
+
+#include <limits.h>
+
+
+#ifdef PATOMIC_HAS_IR_SIGN_MAGNITUDE
+    #undef PATOMIC_HAS_IR_SIGN_MAGNITUDE
+    #error "Integer representation macro values MUST be changed in source"
+#endif
+/**
+ * @addtogroup config.unsafe
+ *
+ * @brief
+ *   Integer representation on the target platform is sign-magnitude.
+ *
+ * @warning
+ *   This macro cannot be overridden by predefining it; it must be manually
+ *   modified in the source file.
+ */
+#define PATOMIC_HAS_IR_SIGN_MAGNITUDE \
+    ((-INT_MAX == INT_MIN) && ((-1 & 1) != 0))
+
+
+#ifdef PATOMIC_HAS_IR_ONES_COMPL
+    #undef PATOMIC_HAS_IR_ONES_COMPL
+    #error "Integer representation macro values MUST be changed in source"
+#endif
+/**
+ * @addtogroup config.unsafe
+ *
+ * @brief
+ *   Integer representation on the target platform is one's complement.
+ *
+ * @warning
+ *   This macro cannot be overridden by predefining it; it must be manually
+ *   modified in the source file.
+ */
+#define PATOMIC_HAS_IR_ONES_COMPL \
+    ((-INT_MAX == INT_MIN) && ((-1 & 1) == 0))
+
+
+#ifdef PATOMIC_HAS_IR_TWOS_COMPL
+    #undef PATOMIC_HAS_IR_TWOS_COMPL
+    #error "Integer representation macro values MUST be changed in source"
+#endif
+/**
+ * @addtogroup config.unsafe
+ *
+ * @brief
+ *   Integer representation on the target platform is two's complement.
+ *
+ * @warning
+ *   This macro cannot be overridden by predefining it; it must be manually
+ *   modified in the source file.
+ */
+#define PATOMIC_HAS_IR_TWOS_COMPL \
+    (-INT_MAX != INT_MIN)
+
+
+/* check that exactly one integer representation is supported */
+#if ( PATOMIC_HAS_IR_SIGN_MAGNITUDE + \
+      PATOMIC_HAS_IR_ONES_COMPL     + \
+      PATOMIC_HAS_IR_TWOS_COMPL ) > 1
+    #error "Multiple integer representations are configured as supported"
+#elif ( PATOMIC_HAS_IR_SIGN_MAGNITUDE + \
+        PATOMIC_HAS_IR_ONES_COMPL     + \
+        PATOMIC_HAS_IR_TWOS_COMPL ) < 1
+    #error "No integer representation is configured as supported"
+#endif
+
+
+#endif  /* PATOMIC_CONFIG_H */
diff --git a/src/include/patomic/internal/CMakeLists.txt b/src/include/patomic/internal/CMakeLists.txt
new file mode 100644
index 000000000..180d26106
--- /dev/null
+++ b/src/include/patomic/internal/CMakeLists.txt
@@ -0,0 +1,6 @@
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    align.h
+    combine.h
+    feature_check.h
+)
diff --git a/src/include/patomic/internal/align.h b/src/include/patomic/internal/align.h
new file mode 100644
index 000000000..3c142066f
--- /dev/null
+++ b/src/include/patomic/internal/align.h
@@ -0,0 +1,27 @@
+#ifndef PATOMIC_INTERNAL_ALIGN_H
+#define PATOMIC_INTERNAL_ALIGN_H
+
+#include <patomic/api/core.h>
+
+
+/**
+ * @addtogroup internal
+ *
+ * @brief
+ *   Compares two patomic_align_t objects.
+ *
+ * @details
+ *   Priority is given to "recommended", then "minimum", then "size_within".
+ *
+ * @return
+ *   Returns -1 if lhs is a looser requirement than rhs, 1 if lhs is a stricter
+ *   requirement than rhs, otherwise 0.
+ */
+int
+patomic_internal_compare_align(
+    patomic_align_t lhs,
+    patomic_align_t rhs
+);
+
+
+#endif  /* PATOMIC_INTERNAL_ALIGN_H */
diff --git a/src/include/patomic/internal/combine.h b/src/include/patomic/internal/combine.h
new file mode 100644
index 000000000..826a4eaf3
--- /dev/null
+++ b/src/include/patomic/internal/combine.h
@@ -0,0 +1,41 @@
+#ifndef PATOMIC_INTERNAL_COMBINE_H
+#define PATOMIC_INTERNAL_COMBINE_H
+
+#include <patomic/api/core.h>
+
+
+/**
+ * @addtogroup internal
+ *
+ * @brief
+ *   Internal implementation of patomic_combine which is not exported.
+ *
+ * @note
+ *   Called internally instead of exported version to avoid PLT indirection
+ *   when building as a shared library.
+ */
+void
+patomic_internal_combine(
+    patomic_t *priority,
+    const patomic_t *other
+);
+
+
+/**
+ * @addtogroup internal
+ *
+ * @brief
+ *   Internal implementation of patomic_combine_explicit which is not exported.
+ *
+ * @note
+ *   Called internally instead of exported version to avoid PLT indirection
+ *   when building as a shared library.
+ */
+void
+patomic_internal_combine_explicit(
+    patomic_explicit_t *priority,
+    const patomic_explicit_t *other
+);
+
+
+#endif  /* PATOMIC_INTERNAL_COMBINE_H */
diff --git a/src/include/patomic/internal/feature_check.h b/src/include/patomic/internal/feature_check.h
new file mode 100644
index 000000000..ed19c655a
--- /dev/null
+++ b/src/include/patomic/internal/feature_check.h
@@ -0,0 +1,61 @@
+#ifndef PATOMIC_INTERNAL_FEATURE_CHECK
+#define PATOMIC_INTERNAL_FEATURE_CHECK
+
+#include <patomic/api/feature_check.h>
+
+
+/**
+ * @addtogroup internal
+ *
+ * @brief
+ *   Internal implementation of patomic_feature_check_any which is not
+ *   exported.
+ *
+ * @note
+ *   Called internally instead of exported version to avoid PLT indirection
+ *   when building as a shared library.
+ */
+unsigned int
+patomic_internal_feature_check_any(
+    const patomic_ops_t *ops,
+    unsigned int opcats
+);
+
+
+/**
+ * @addtogroup internal
+ *
+ * @brief
+ *   Internal implementation of patomic_feature_check_any_explicit which is not
+ *   exported.
+ *
+ * @note
+ *   Called internally instead of exported version to avoid PLT indirection
+ *   when building as a shared library.
+ */
+unsigned int
+patomic_internal_feature_check_any_explicit(
+    const patomic_ops_explicit_t *ops,
+    unsigned int opcats
+);
+
+
+/**
+ * @addtogroup internal
+ *
+ * @brief
+ *   Internal implementation of patomic_feature_check_any_transaction which is
+ *   not exported.
+ *
+ * @note
+ *   Called internally instead of exported version to avoid PLT indirection
+ *   when building as a shared library.
+ */
+unsigned int
+patomic_internal_feature_check_any_transaction(
+    const patomic_ops_transaction_t *ops,
+    unsigned int opcats
+);
+
+
+#endif  /* PATOMIC_INTERNAL_FEATURE_CHECK */
diff --git a/src/include/patomic/macros/CMakeLists.txt b/src/include/patomic/macros/CMakeLists.txt
new file mode 100644
index 000000000..c84c8114e
--- /dev/null
+++ b/src/include/patomic/macros/CMakeLists.txt
@@ -0,0 +1,7 @@
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    func_name.h
+    ignore_unused.h
+    noreturn.h
+    unreachable.h
+)
diff --git a/src/include/patomic/macros/func_name.h b/src/include/patomic/macros/func_name.h
new file mode 100644
index 000000000..b5d1a6f9c
--- /dev/null
+++ b/src/include/patomic/macros/func_name.h
@@ -0,0 +1,35 @@
+#ifndef PATOMIC_FUNC_NAME
+
+#include <patomic/config.h>
+
+/* used internally */
+#undef PATOMIC_FUNC_NAME_
+
+#if defined(__FUNCSIG__)
+    #define PATOMIC_FUNC_NAME_ __FUNCSIG__
+#elif PATOMIC_HAS_GNU_PRETTY_FUNCTION
+    #define PATOMIC_FUNC_NAME_ __PRETTY_FUNCTION__
+#elif PATOMIC_HAS_GNU_PRETTY_FUNCTION_EXTN
+    #define PATOMIC_FUNC_NAME_ __extension__ __PRETTY_FUNCTION__
+#elif PATOMIC_HAS_GNU_FUNCTION
+    #define PATOMIC_FUNC_NAME_ __FUNCTION__
+#elif PATOMIC_HAS_GNU_FUNCTION_EXTN
+    #define PATOMIC_FUNC_NAME_ __extension__ __FUNCTION__
+#elif PATOMIC_HAS_FUNC
+    #define PATOMIC_FUNC_NAME_ __func__
+#elif PATOMIC_HAS_FUNC_EXTN
+    #define PATOMIC_FUNC_NAME_ __extension__ __func__
+#else
+    #define PATOMIC_FUNC_NAME_ "(unknown function name)"
+#endif
+
+/**
+ * @addtogroup macros
+ *
+ * @brief
+ *   The identifier of the variable containing the function name in the current
+ *   scope.
+ */
+#define PATOMIC_FUNC_NAME PATOMIC_FUNC_NAME_
+
+#endif  /* PATOMIC_FUNC_NAME */
diff --git a/src/include/patomic/macros/ignore_unused.h b/src/include/patomic/macros/ignore_unused.h
new file mode 100644
index 000000000..9c6520208
--- /dev/null
+++ b/src/include/patomic/macros/ignore_unused.h
@@ -0,0 +1,11 @@
+#ifndef PATOMIC_IGNORE_UNUSED
+
+/**
+* @addtogroup macros
+ *
+ * @brief
+ *   Suppresses compiler warnings on unused expressions.
+*/
+#define PATOMIC_IGNORE_UNUSED(expr) ((void) (expr))
+
+#endif  /* PATOMIC_IGNORE_UNUSED */
diff --git a/src/include/patomic/macros/noreturn.h b/src/include/patomic/macros/noreturn.h
new file mode 100644
index 000000000..3831a8cd0
--- /dev/null
+++ b/src/include/patomic/macros/noreturn.h
@@ -0,0 +1,27 @@
+#ifndef PATOMIC_NORETURN
+
+#include <patomic/config.h>
+
+/* used internally */
+#undef PATOMIC_NORETURN_
+
+#if PATOMIC_HAS_NORETURN
+    #define PATOMIC_NORETURN_ _Noreturn
+#elif PATOMIC_HAS_NORETURN_ATTR
+    #define PATOMIC_NORETURN_ __attribute__((noreturn))
+#elif PATOMIC_HAS_NORETURN_DSPC
+    #define PATOMIC_NORETURN_ __declspec(noreturn)
+#else
+    #define PATOMIC_NORETURN_
+#endif
+
+/**
+ * @addtogroup macros
+ *
+ * @brief
+ *   Function attribute to declare that the function shall not return. This must
+ *   come after the static qualifier.
+ */
+#define PATOMIC_NORETURN PATOMIC_NORETURN_
+
+#endif  /* PATOMIC_NORETURN */
diff --git a/src/include/patomic/macros/unreachable.h b/src/include/patomic/macros/unreachable.h
new file mode 100644
index 000000000..d079c9604
--- /dev/null
+++ b/src/include/patomic/macros/unreachable.h
@@ -0,0 +1,41 @@
+#ifndef PATOMIC_UNREACHABLE
+
+#include <patomic/config.h>
+
+/* used internally */
+#undef PATOMIC_UNREACHABLE_
+#undef PATOMIC_UNREACHABLE_IS_VCZ_
+
+/* availability flag */
+#undef PATOMIC_UNREACHABLE_IS_VCZ
+
+#if PATOMIC_HAS_MS_ASSUME
+    #define PATOMIC_UNREACHABLE_() __assume(0)
+    #define PATOMIC_UNREACHABLE_IS_VCZ_ 0
+#elif PATOMIC_HAS_BUILTIN_UNREACHABLE
+    #define PATOMIC_UNREACHABLE_() __builtin_unreachable()
+    #define PATOMIC_UNREACHABLE_IS_VCZ_ 0
+#else
+    #define PATOMIC_UNREACHABLE_() ((void) 0)
+    #define PATOMIC_UNREACHABLE_IS_VCZ_ 1
+#endif
+
+/**
+ * @addtogroup macros
+ *
+ * @brief
+ *   The expression informs the compiler that this code path is unreachable.
+ *   Invokes undefined behaviour.
+ */
+#define PATOMIC_UNREACHABLE() PATOMIC_UNREACHABLE_()
+
+/**
+ * @addtogroup macros
+ *
+ * @brief
+ *   Boolean flag is set if PATOMIC_UNREACHABLE has no available implementation
+ *   and is implemented as a void cast zero ((void) 0).
+ */
+#define PATOMIC_UNREACHABLE_IS_VCZ PATOMIC_UNREACHABLE_IS_VCZ_
+
+#endif  /* PATOMIC_UNREACHABLE */
diff --git a/src/include/patomic/stdlib/CMakeLists.txt b/src/include/patomic/stdlib/CMakeLists.txt
new file mode 100644
index 000000000..07899238d
--- /dev/null
+++ b/src/include/patomic/stdlib/CMakeLists.txt
@@ -0,0 +1,8 @@
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    abort.h
+    assert.h
+    math.h
+    sort.h
+    stdint.h
+)
diff --git a/src/include/patomic/stdlib/abort.h b/src/include/patomic/stdlib/abort.h
new file mode 100644
index 000000000..92a0aa2c2
--- /dev/null
+++ b/src/include/patomic/stdlib/abort.h
@@ -0,0 +1,21 @@
+#ifndef PATOMIC_STDLIB_ABORT_H
+#define PATOMIC_STDLIB_ABORT_H
+
+#include <patomic/macros/noreturn.h>
+
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Calls abort().
+ *
+ * @note
+ *   Exists so that we can mock this in unit tests with functionality which
+ *   does not terminate execution.
+ */
+PATOMIC_NORETURN void
+patomic_abort(void);
+
+
+#endif  /* PATOMIC_STDLIB_ABORT_H */
diff --git a/src/include/patomic/stdlib/assert.h b/src/include/patomic/stdlib/assert.h
new file mode 100644
index 000000000..aeb1f8c26
--- /dev/null
+++ b/src/include/patomic/stdlib/assert.h
@@ -0,0 +1,76 @@
+#ifdef PATOMIC_STDLIB_ASSERT_H
+    #undef PATOMIC_STDLIB_ASSERT_H
+    #undef patomic_assert
+    #undef patomic_assert_always
+    #undef patomic_assert_unreachable
+#endif
+#define PATOMIC_STDLIB_ASSERT_H
+
+#include <patomic/macros/func_name.h>
+#include <patomic/macros/ignore_unused.h>
+#include <patomic/macros/noreturn.h>
+#include <patomic/macros/unreachable.h>
+
+
+PATOMIC_NORETURN void
+__patomic_assert_fail(
+    const char *expr,
+    const char *file,
+    const char *func,
+    unsigned int line
+);
+
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Will always fatally assert if the expression evaluates as false, even if
+ *   NDEBUG is defined.
+ *
+ * @param expr
+ *   Runtime expression to evaluate as a boolean value.
+ */
+#define patomic_assert_always(expr) (PATOMIC_IGNORE_UNUSED((expr) || \
+    (__patomic_assert_fail(#expr, __FILE__, PATOMIC_FUNC_NAME, __LINE__), 0)))
+
+
+#if defined(NDEBUG) && !defined(NNDEBUG)
+    #define patomic_assert_(expr) (PATOMIC_IGNORE_UNUSED(0))
+#else
+    #define patomic_assert_(expr) patomic_assert_always(expr)
+#endif
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Will fatally assert if the expression evaluates as false. If NDEBUG is
+ *   defined and NNDEBUG is not defined, no evaluation will take place and this
+ *   is a no-op.
+ *
+ * @param expr
+ *   Runtime expression to evaluate as a boolean value.
+ */
+#define patomic_assert(expr) patomic_assert_(expr)
+
+
+#if defined(NDEBUG) && !defined(NNDEBUG)
+    #define patomic_assert_unreachable_(expr) (PATOMIC_IGNORE_UNUSED((expr || \
+        (PATOMIC_UNREACHABLE(), 0)))
+#else
+    #define patomic_assert_unreachable_(expr) patomic_assert_always(expr)
+#endif
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Will fatally assert if the expression evaluates as false. If NDEBUG is
+ *   defined and NNDEBUG is not defined, the expression is instead marked as
+ *   unreachable.
+ *
+ * @param expr
+ *   Runtime expression to evaluate as a boolean value.
+ */
+#define patomic_assert_unreachable(expr) patomic_assert_unreachable_(expr)
diff --git a/src/include/patomic/stdlib/math.h b/src/include/patomic/stdlib/math.h
new file mode 100644
index 000000000..7171b6123
--- /dev/null
+++ b/src/include/patomic/stdlib/math.h
@@ -0,0 +1,77 @@
+#ifndef PATOMIC_STDLIB_MATH_H
+#define PATOMIC_STDLIB_MATH_H
+
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Checks if 'x' is 0 or a power of 2.
+ *
+ * @param x
+ *   An object of unsigned integer type holding a non-negative value.
+ *
+ * @return
+ *   The value 1 if 'x' is 0 or a power of 2, otherwise the value 0.
+ */
+#define patomic_unsigned_is_pow2_or_zero(x) \
+    (((x) & ((x) - 1u)) == 0)
+
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Checks if 'x' is a power of 2.
+ *
+ * @param x
+ *   An object of unsigned integer type holding a non-negative value.
+ *
+ * @return
+ *   The value 1 if 'x' is a power of 2, otherwise the value 0.
+ */
+#define patomic_unsigned_is_pow2(x) \
+    (((x) != 0) && patomic_unsigned_is_pow2_or_zero(x))
+
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Computes 'x % y' assuming that 'y' is a power of 2.
+ *
+ * @param x
+ *   An object of unsigned integer type holding a non-negative value.
+ *
+ * @param y
+ *   An object of unsigned integer type holding a positive power of 2 value.
+ *
+ * @return
+ *   The non-negative value 'x % y' of unsigned integer type.
+ */
+#define patomic_unsigned_mod_pow2(x, y) \
+    ((x) & ((y) - 1u))
+
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Computes 'x % y' with no assumptions about their values.
+ *
+ * @param x
+ *   An object of unsigned integer type holding a non-negative value.
+ *
+ * @param y
+ *   An object of unsigned integer type holding a positive non-zero value.
+ *
+ * @return
+ *   A non-negative value 'x % y' of unsigned integer type.
+ */
+#define patomic_unsigned_mod(x, y)         \
+    (patomic_unsigned_is_pow2_or_zero(y) ? \
+     patomic_unsigned_mod_pow2(x, y)     : \
+     ((x) % (y + 0u)))
+
+
+#endif  /* PATOMIC_STDLIB_MATH_H */
diff --git a/src/include/patomic/stdlib/sort.h b/src/include/patomic/stdlib/sort.h
new file mode 100644
index 000000000..e81730717
--- /dev/null
+++ b/src/include/patomic/stdlib/sort.h
@@ -0,0 +1,42 @@
+#ifndef PATOMIC_STDLIB_SORT_H
+#define PATOMIC_STDLIB_SORT_H
+
+#include <stdlib.h>
+
+
+/**
+ * @addtogroup stdlib
+ *
+ * @brief
+ *   Sorts the given array pointed to by "ptr" in ascending order. The array
+ *   contains "count" elements of "size" bytes. Function pointed to by "comp"
+ *   is used for object comparison.
+ *
+ * @param ptr
+ *   Pointer to the array to sort.
+ *
+ * @param count
+ *   Number of elements in the array.
+ *
+ * @param size
+ *   Size of each element in the array in bytes.
+ *
+ * @param comp
+ *   Comparison function which returns a negative integer value if the first
+ *   argument is less than the second, a positive integer value if the first
+ *   argument is greater than the second and zero if the arguments are
+ *   equivalent.
+ *
+ * @note
+ *   The interface is identical to C90's qsort.
+ */
+void
+patomic_array_sort(
+    void *ptr,
+    size_t count,
+    size_t size,
+    int (*comp)(const void*, const void*)
+);
+
+
+#endif  /* PATOMIC_STDLIB_SORT_H */
diff --git a/src/include/patomic/stdlib/stdint.h b/src/include/patomic/stdlib/stdint.h
new file mode 100644
index 000000000..4a1603abc
--- /dev/null
+++ b/src/include/patomic/stdlib/stdint.h
@@ -0,0 +1,59 @@
+#ifndef PATOMIC_STDLIB_STDINT_H
+#define PATOMIC_STDLIB_STDINT_H
+
+#include <patomic/config.h>
+
+#include <stddef.h>
+#if PATOMIC_HAS_STDINT_H
+    #include <stdint.h>
+#endif
+
+
+#if PATOMIC_HAS_LONG_LONG
+    typedef signed long long   patomic_llong_signed_t;
+    typedef unsigned long long patomic_llong_unsigned_t;
+    #define PATOMIC_STDINT_HAS_LLONG 1
+#elif PATOMIC_HAS_LONG_LONG_EXTN
+    __extension__ typedef signed long long   patomic_llong_signed_t;
+    __extension__ typedef unsigned long long patomic_llong_unsigned_t;
+    #define PATOMIC_STDINT_HAS_LLONG 1
+#else
+    #define PATOMIC_STDINT_HAS_LLONG 0
+#endif
+
+
+#if PATOMIC_HAS_MS_INT128
+    typedef signed __int128   patomic_int128_signed_t;
+    typedef unsigned __int128 patomic_int128_unsigned_t;
+    #define PATOMIC_STDINT_HAS_INT128 1
+#elif PATOMIC_HAS_MS_INT128_EXTN
+    __extension__ typedef signed __int128   patomic_int128_signed_t;
+    __extension__ typedef unsigned __int128 patomic_int128_unsigned_t;
+    #define PATOMIC_STDINT_HAS_INT128 1
+#else
+    #define PATOMIC_STDINT_HAS_INT128 0
+#endif
+
+
+#if PATOMIC_HAS_STDINT_INTPTR || PATOMIC_HAS_STDDEF_INTPTR
+    typedef intptr_t  patomic_intptr_signed_t;
+    typedef uintptr_t patomic_intptr_unsigned_t;
+#elif PATOMIC_HAS_STDINT_INTPTR_EXTN || PATOMIC_HAS_STDDEF_INTPTR_EXTN
+    __extension__ typedef intptr_t  patomic_intptr_signed_t;
+    __extension__ typedef uintptr_t patomic_intptr_unsigned_t;
+#elif defined(__INTPTR_TYPE__) && defined(__UINTPTR_TYPE__)
+    typedef __INTPTR_TYPE__  patomic_intptr_signed_t;
+    typedef __UINTPTR_TYPE__ patomic_intptr_unsigned_t;
+#elif PATOMIC_STDINT_HAS_I128
+    typedef patomic_i128_signed_t   patomic_intptr_signed_t;
+    typedef patomic_i128_unsigned_t patomic_intptr_unsigned_t;
+#elif PATOMIC_STDINT_HAS_LLONG
+    typedef patomic_llong_signed_t   patomic_intptr_signed_t;
+    typedef patomic_llong_unsigned_t patomic_intptr_unsigned_t;
+#else
+    typedef signed long   patomic_intptr_signed_t;
+    typedef unsigned long patomic_intptr_unsigned_t;
+#endif
+
+
+#endif  /* PATOMIC_STDLIB_STDINT_H */
diff --git a/src/patomic.c b/src/patomic.c
index 404519e55..dd6c71c90 100644
--- a/src/patomic.c
+++ b/src/patomic.c
@@ -1,11 +1,189 @@
 #include <patomic/patomic.h>
 
+#include <patomic/internal/align.h>
+#include <patomic/internal/combine.h>
+#include <patomic/internal/feature_check.h>
 
-PATOMIC_EXPORT int
-patomic_example_add(
-    int a,
-    int b
+#include <patomic/stdlib/sort.h>
+
+#include "impl/register.h"
+
+
+static int
+compare_implicit(
+    const void *const lhs_void,
+    const void *const rhs_void
+)
+{
+    /* convert to non-void types */
+    const patomic_t lhs = *(const patomic_t *const) lhs_void;
+    const patomic_t rhs = *(const patomic_t *const) rhs_void;
+
+    /* defer to internal comparison function */
+    return patomic_internal_compare_align(lhs.align, rhs.align);
+}
+
+
+static int
+compare_explicit(
+    const void *const lhs_void,
+    const void *const rhs_void
 )
 {
-    return a + b;
+    /* convert to non-void types */
+    const patomic_explicit_t lhs = *(const patomic_explicit_t *const) lhs_void;
+    const patomic_explicit_t rhs = *(const patomic_explicit_t *const) rhs_void;
+
+    /* defer to internal comparison function */
+    return patomic_internal_compare_align(lhs.align, rhs.align);
+}
+
+
+patomic_t
+patomic_create(
+    const size_t byte_width,
+    const patomic_memory_order_t order,
+    const unsigned int opts,
+    const unsigned int kinds,
+    const unsigned long ids
+)
+{
+    /* declare variables */
+    const unsigned int opcats = ~0u;
+    patomic_t ret;
+    patomic_t objs[PATOMIC_IMPL_REGISTER_SIZE];
+    patomic_t *begin = objs;
+    patomic_t *end   = objs;
+    size_t i;
+
+    /* fill array with implementations */
+    for (i = 0; i < PATOMIC_IMPL_REGISTER_SIZE; ++i)
+    {
+        if ( ((unsigned long) patomic_impl_register[i].id & ids) &&
+             ((unsigned int)  patomic_impl_register[i].kind & kinds))
+        {
+            /* only add to array if some operation is supported */
+            *end = patomic_impl_register[i].fp_create(byte_width, order, opts);
+            if (opcats != patomic_internal_feature_check_any(&end->ops, opcats))
+            {
+                ++end;
+            }
+        }
+    }
+
+    /* sort implementations by alignment */
+    patomic_array_sort(
+        begin,
+        (size_t) (end - begin),
+        sizeof(patomic_t),
+        &compare_implicit
+    );
+
+    /* combine implementations */
+    ret = patomic_impl_create_null(byte_width, order, opts);
+    for (; begin != end; ++begin)
+    {
+        patomic_internal_combine(&ret, begin);
+    }
+
+    return ret;
+}
+
+
+patomic_explicit_t
+patomic_create_explicit(
+    const size_t byte_width,
+    const unsigned int opts,
+    const unsigned int kinds,
+    const unsigned long ids
+)
+{
+    /* declare variables */
+    const unsigned int opcats = ~0u;
+    patomic_explicit_t ret;
+    patomic_explicit_t objs[PATOMIC_IMPL_REGISTER_SIZE];
+    patomic_explicit_t *begin = objs;
+    patomic_explicit_t *end   = objs;
+    size_t i;
+
+    /* fill array with implementations */
+    for (i = 0; i < PATOMIC_IMPL_REGISTER_SIZE; ++i)
+    {
+        if ( ((unsigned long) patomic_impl_register[i].id & ids) &&
+             ((unsigned int)  patomic_impl_register[i].kind & kinds))
+        {
+            /* only add to array if some operation is supported */
+            *end = patomic_impl_register[i].fp_create_explicit(byte_width, opts);
+            if (opcats != patomic_internal_feature_check_any_explicit(&end->ops, opcats))
+            {
+                ++end;
+            }
+        }
+    }
+
+    /* sort implementations by alignment */
+    patomic_array_sort(
+        begin,
+        (size_t) (end - begin),
+        sizeof(patomic_explicit_t),
+        &compare_explicit
+    );
+
+    /* combine implementations */
+    ret = patomic_impl_create_explicit_null(byte_width, opts);
+    for (; begin != end; ++begin)
+    {
+        patomic_internal_combine_explicit(&ret, begin);
+    }
+
+    return ret;
+}
+
+
+patomic_transaction_t
+patomic_create_transaction(
+    const unsigned int opts,
+    const unsigned int kinds,
+    const unsigned long ids
+)
+{
+    /* declare variables */
+    const unsigned int opcats = ~0u;
+    patomic_kind_t last_kind = patomic_kind_UNKN;
+    patomic_transaction_t ret;
+    patomic_transaction_t objs[PATOMIC_IMPL_REGISTER_SIZE];
+    patomic_transaction_t *begin = objs;
+    patomic_transaction_t *end   = objs;
+    size_t i;
+
+    /* fill array with implementations */
+    for (i = 0; i < PATOMIC_IMPL_REGISTER_SIZE; ++i)
+    {
+        if( ((unsigned long) patomic_impl_register[i].id & ids) &&
+            ((unsigned int)  patomic_impl_register[i].kind & kinds))
+        {
+            /* only add to array if some operation is supported */
+            ret = patomic_impl_register[i].fp_create_transaction(opts);
+            if(opcats != patomic_internal_feature_check_any_transaction(&ret.ops, opcats))
+            {
+                /* ignore previous implementations if current one has a better kind */
+                if (patomic_impl_register[i].kind > last_kind)
+                {
+                    end = objs;
+                }
+                last_kind = patomic_impl_register[i].kind;
+                *end++ = ret;
+            }
+        }
+    }
+
+    /* get first implementation available */
+    if (begin != end)
+    {
+        return objs[0];
+    }
+    else
+    {
+        return patomic_impl_create_transaction_null(opts);
+    }
 }
diff --git a/src/stdlib/CMakeLists.txt b/src/stdlib/CMakeLists.txt
new file mode 100644
index 000000000..a051df5fe
--- /dev/null
+++ b/src/stdlib/CMakeLists.txt
@@ -0,0 +1,6 @@
+# add directory files to target
+target_sources(${target_name} PRIVATE
+    abort.c
+    assert.c
+    sort.c
+)
diff --git a/src/stdlib/abort.c b/src/stdlib/abort.c
new file mode 100644
index 000000000..b1d70f732
--- /dev/null
+++ b/src/stdlib/abort.c
@@ -0,0 +1,10 @@
+#include <patomic/stdlib/abort.h>
+
+#include <stdlib.h>
+
+
+void
+patomic_abort(void)
+{
+    abort();
+}
diff --git a/src/stdlib/assert.c b/src/stdlib/assert.c
new file mode 100644
index 000000000..82ed5d9fb
--- /dev/null
+++ b/src/stdlib/assert.c
@@ -0,0 +1,95 @@
+#ifdef _MSC_VER
+    #define _CRT_SECURE_NO_WARNINGS
+#endif
+
+
+#include <patomic/stdlib/abort.h>
+#include <patomic/stdlib/assert.h>
+
+#include <patomic/macros/ignore_unused.h>
+#include <patomic/macros/unreachable.h>
+
+#include <patomic/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+
+/* check if we can make use of wide functionality */
+#define PATOMIC_ASSERT_WIDE \
+    PATOMIC_HAS_WCHAR_FWIDE && PATOMIC_HAS_WCHAR_FWPRINTF
+#if PATOMIC_ASSERT_WIDE
+    #include <wchar.h>
+#endif
+
+
+static void
+patomic_assert_fxprint(
+    FILE *stream,
+    const char *expr,
+    const char *file,
+    const char *func,
+    const unsigned int line
+)
+{
+#if PATOMIC_ASSERT_WIDE
+    /* print to a wide stream if conversion to narrow isn't possible */
+    if (fwide(stream, -1) > 0)
+    {
+        /* print assertion */
+        const int _ = fwprintf(
+#ifdef _MSC_VER
+            /* msvc uses %S for narrow strings in wide functions */
+            stream, L"%S:%u: %S: Assertion `%S` failed.\n",
+#else
+            /* standard treats %s as normal even in wide functions */
+            stream, L"%s:%u: %s: Assertion `%s` failed.\n",
+#endif
+            file, line, func, expr
+        );
+        PATOMIC_IGNORE_UNUSED(_);
+    }
+    /* print to a narrow stream */
+    else
+    {
+#endif
+        /* print assertion */
+        PATOMIC_IGNORE_UNUSED(fprintf(
+            stream, "%s:%u: %s: Assertion `%s` failed.\n",
+            file, line, func, expr
+        ));
+#if PATOMIC_ASSERT_WIDE
+    }
+#endif
+}
+
+
+void
+__patomic_assert_fail(
+    const char *expr,
+    const char *file,
+    const char *func,
+    const unsigned int line
+)
+{
+    /* attempt to make stderr unbuffered */
+    int _;
+    _ = setvbuf(stderr, NULL, _IONBF, 0);
+    PATOMIC_IGNORE_UNUSED(_);
+
+    /* print assertion to stderr */
+    patomic_assert_fxprint(stderr, expr, file, func, line);
+
+    /* flush stderr in case it is still buffered */
+    _ = fflush(stderr);
+    PATOMIC_IGNORE_UNUSED(_);
+
+    /* failed assertions should not return */
+    patomic_abort();
+
+    /* if unreachable is vcz ((void) 0), we may get a compiler warning for
+     * unreachable code */
+#if !PATOMIC_UNREACHABLE_IS_VCZ
+    PATOMIC_UNREACHABLE();
+#endif
+}
diff --git a/src/stdlib/sort.c b/src/stdlib/sort.c
new file mode 100644
index 000000000..32d341660
--- /dev/null
+++ b/src/stdlib/sort.c
@@ -0,0 +1,13 @@
+#include <patomic/stdlib/sort.h>
+
+
+void
+patomic_array_sort(
+    void *ptr,
+    size_t count,
+    size_t size,
+    int (*comp)(const void*, const void*)
+)
+{
+    qsort(ptr, count, size, comp);
+}


Test patch:
diff --git a/.github/workflows/_reusable-test-native.yml b/.github/workflows/_reusable-test-native.yml
index 1bd95a31d..fd4f97e4d 100644
--- a/.github/workflows/_reusable-test-native.yml
+++ b/.github/workflows/_reusable-test-native.yml
@@ -117,7 +117,7 @@ jobs:
       - name: Restore Cached GoogleTest
         if: env.SKIP_JOB != 'true'
         id: cache-googletest
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: ${{ env.ROOT_PATH }}/googletest/build/install
           key: googletest-${{ env.UNIQUE_ID }}
@@ -164,12 +164,12 @@ jobs:
         if: env.SKIP_JOB != 'true'
         continue-on-error: true
         env:
-          LABEL_REGEX: ${{ matrix.kind == 'coverage' && '^(ut)$' || '^(.*)$' }}
+          CTEST_SELECT: ${{ matrix.kind == 'coverage' && '--tests-regex "^(Ut|BtApi)"' || '--label-regex "^(.*)$"' }}
         run: |
           cd ${{ env.ROOT_PATH }}
           
           cd patomic/build
-          ctest --label-regex "${{ env.LABEL_REGEX }}" --verbose --output-junit Testing/Temporary/results.xml --build-config ${{ env.CMAKE_BUILD_TYPE }} .
+          ctest ${{ env.CTEST_SELECT }} --verbose --output-junit Testing/Temporary/results.xml --build-config ${{ env.CMAKE_BUILD_TYPE }} .
 
       - name: Prepare Test Results
         if: env.SKIP_JOB != 'true'
@@ -181,9 +181,9 @@ jobs:
 
       - name: Upload Test Results
         if: env.SKIP_JOB != 'true'
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: test-results
+          name: test-results-${{ env.UNIQUE_ID }}
           path: ${{ env.ROOT_PATH }}/upload/test/
 
       - name: Generate Lcov Tracefile and Root Path File (clang)
@@ -207,7 +207,7 @@ jobs:
 
           # merge coverage output from all tests
           # use find because bash on GitHub Actions currently does not support '**'
-          find test/working -type f -name "*.profraw" -print0 | xargs -0 ${prefix}llvm-profdata merge -output=patomic.profdata
+          find test/kind -type f -name "*.profraw" -print0 | xargs -0 ${prefix}llvm-profdata merge -output=patomic.profdata
           
           # convert profdata to lcov tracefile, and copy to upload
           lib_files=(libpatomic.*)  # matches shared/static lib files and symlinks
@@ -245,7 +245,7 @@ jobs:
 
       - name: Upload Internal Coverage Results
         if: env.SKIP_JOB != 'true' && matrix.kind == 'coverage'
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: test-coverage-internal
+          name: test-coverage-internal-${{ env.UNIQUE_ID }}
           path: ${{ env.ROOT_PATH }}/upload/cov/
diff --git a/.github/workflows/_reusable-test-qemu.yml b/.github/workflows/_reusable-test-qemu.yml
index 0b48097fa..8db91a382 100644
--- a/.github/workflows/_reusable-test-qemu.yml
+++ b/.github/workflows/_reusable-test-qemu.yml
@@ -106,10 +106,12 @@ jobs:
       CMAKE_PRESET: ${{ matrix.preset }}-${{ matrix.kind }}
       CMAKE_BUILD_TYPE: ${{ ( matrix.kind == 'ansi' || matrix.kind == 'warning' ) && 'Release' || 'Debug' }}
       # apparently not possible to skip job using matrix context, so this is the next best thing
+      # fixme: hppa-shared-coverage tests segfault (GHI #25)
       SKIP_JOB: >-
         ${{ (
           ( matrix.compiler == 'clang' && inputs.skip_llvm ) ||
-          ( matrix.compiler == 'gcc' && inputs.skip_gcc )
+          ( matrix.compiler == 'gcc' && inputs.skip_gcc )    ||
+          ( 'hppa' == inputs.architecture && 'shared' == matrix.build_shared && 'coverage' == matrix.kind )
         ) }}
       # necessary because publish-coverage job expects everything to have the same root path
       ROOT_PATH: /Users/${{ matrix.kind }}
@@ -148,7 +150,7 @@ jobs:
       - name: Restore Cached Sysroot (with GoogleTest)
         if: env.SKIP_JOB != 'true'
         id: cache-sysroot
-        uses: actions/cache@v3
+        uses: actions/cache@v4
         with:
           path: ${{ env.ROOT_PATH }}/sysroot
           key: sysroot-${{ env.UNIQUE_ID }}
@@ -201,12 +203,12 @@ jobs:
         if: env.SKIP_JOB != 'true'
         continue-on-error: true
         env:
-          LABEL_REGEX: ${{ matrix.kind == 'coverage' && '^(ut)$' || '^(.*)$' }}
+          CTEST_SELECT: ${{ matrix.kind == 'coverage' && '--tests-regex "^(Ut|BtApi)"' || '--label-regex "^(.*)$"' }}
         run: |
           cd ${{ env.ROOT_PATH }}
           
           cd patomic/build
-          ctest --label-regex "${{ env.LABEL_REGEX }}" --verbose --output-junit Testing/Temporary/results.xml .
+          ctest ${{ env.CTEST_SELECT }} --verbose --output-junit Testing/Temporary/results.xml .
 
       - name: Prepare Test Results
         if: env.SKIP_JOB != 'true'
@@ -218,9 +220,9 @@ jobs:
 
       - name: Upload Test Results
         if: env.SKIP_JOB != 'true'
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: test-results
+          name: test-results-${{ env.UNIQUE_ID }}
           path: ${{ env.ROOT_PATH }}/upload/test/
 
       - name: Generate Lcov Tracefile and Root Path File (clang)
@@ -237,7 +239,7 @@ jobs:
 
           # merge coverage output from all tests
           # use find because bash on GitHub Actions currently does not support '**'
-          find test/working -type f -name "*.profraw" -print0 | xargs -0 llvm-profdata merge -output=patomic.profdata
+          find test/kind -type f -name "*.profraw" -print0 | xargs -0 llvm-profdata merge -output=patomic.profdata
           
           # convert profdata to lcov tracefile, and copy to upload
           lib_files=(libpatomic.*)  # matches shared/static lib files and symlinks
@@ -270,7 +272,7 @@ jobs:
 
       - name: Upload Internal Coverage Results
         if: env.SKIP_JOB != 'true' && matrix.kind == 'coverage'
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
-          name: test-coverage-internal
+          name: test-coverage-internal-${{ env.UNIQUE_ID }}
           path: ${{ env.ROOT_PATH }}/upload/cov/
\ No newline at end of file
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 104b86925..c71b95461 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -139,8 +139,15 @@ jobs:
       - test-qemu
 
     steps:
+      - name: Merge Test Result Artifacts
+        uses: actions/upload-artifact/merge@v4
+        with:
+          name: test-results
+          pattern: test-results-*
+          delete-merged: true
+
       - name: Download Test Result Artifacts
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
           name: test-results
           path: test-results/
@@ -154,6 +161,7 @@ jobs:
           files: test-results/**/*.xml
 
   publish-coverage:
+    if: false
     runs-on: ubuntu-latest
     needs:
       - test-native
@@ -169,8 +177,15 @@ jobs:
           sudo apt install binutils  # for c++filt
           sudo apt install lcov
 
+      - name: Merge Test Coverage Artifacts
+        uses: actions/upload-artifact/merge@v4
+        with:
+          name: test-coverage-internal
+          pattern: test-coverage-internal-*
+          delete-merged: true
+
       - name: Download Test Coverage Artifacts
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@v4
         with:
           name: test-coverage-internal
           path: test-coverage-internal/
@@ -219,10 +234,13 @@ jobs:
           mkdir test-coverage
           
           # keep only coverage data relating to patomic source files
+          # the include tracefile may be empty with clang, hence ignoring the errors
           find ./test-coverage-internal -mindepth 1 -maxdepth 1 -type d -not -path '*/.*' -print0 | while IFS= read -r -d '' dir; do
             mv "${dir}/patomic.lcov" "${dir}/patomic.lcov.old"
             root_path=$(cat "${dir}/patomic.rootpath")
-            lcov --output-file "${dir}/patomic.lcov" --extract "${dir}/patomic.lcov.old" "${root_path}/patomic/src/*"
+            lcov --output-file "${dir}/patomic.lcov.include" --extract "${dir}/patomic.lcov.old" "${root_path}/patomic/include/*" --ignore-errors empty,unused
+            lcov --output-file "${dir}/patomic.lcov.src" --extract "${dir}/patomic.lcov.old" "${root_path}/patomic/src/*"
+            lcov --output-file "${dir}/patomic.lcov" --add-tracefile "${dir}/patomic.lcov.include" --add-tracefile "${dir}/patomic.lcov.src" --ignore-errors empty
           done
           
           # generate html files for each separate compilation
@@ -244,7 +262,7 @@ jobs:
           genhtml --output-directory "./test-coverage/universal" --title "patomic-universal" --show-details --num-spaces 4 --legend --demangle-cpp --precision 2 ./test-coverage-internal/universal/patomic.lcov
 
       - name: Upload Coverage HTML Results
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: test-coverage
           path: test-coverage/
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 739c8e374..35039faf6 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -9,27 +9,37 @@ project(
     LANGUAGES CXX
 )
 
+include(cmake/ProjectIsTopLevel.cmake)
 include(cmake/OptionVariables.cmake)
+include(cmake/CreateTest.cmake)
 
 
 # ---- Dependencies ----
 
-include(cmake/ProjectIsTopLevel.cmake)
-
 # need to enable testing ourselves if we're the root project
 if(PROJECT_IS_TOP_LEVEL)
     enable_testing()
 endif()
 
-# patomic is not a required dependency
-if(NOT TARGET patomic::patomic)
-    find_package(patomic QUIET)
-endif()
-
 # get GTest
 find_package(GTest REQUIRED)
 include(GoogleTest)
 
+# check patomic target is available
+if(NOT TARGET patomic::patomic)
+    message(FATAL_ERROR "Target patomic::patomic required to build tests, not available.")
+endif()
+
+# check patomic dir variables are set
+if(NOT PATOMIC_BINARY_DIR)
+    message(FATAL_ERROR "Variable PATOMIC_BINARY_DIR required to build tests, not set.")
+elseif(NOT PATOMIC_SOURCE_DIR)
+    message(FATAL_ERROR "Variable PATOMIC_SOURCE_DIR required to build tests, not set.")
+endif()
+
+
+# ---- Fix GoogleTest ----
+
 # sometimes this variable is not correctly set by GTest's find_package
 if(NOT GTEST_INCLUDE_DIRS)
     if("${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.20.0")
@@ -53,9 +63,8 @@ find_library(libatomic NAMES atomic atomic.so.1 libatomic.so.1)
 # necessary for using GTest on platforms without atomic operations where
 # libatomic is not automatically linked (e.g. clang-15 for arm-linux-gnueabi)
 if(libatomic)
-    set_property(
-        TARGET GTest::gtest
-        APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${libatomic}"
+    target_link_libraries(GTest::gtest
+        INTERFACE "${libatomic}"
     )
 endif()
 
@@ -63,7 +72,8 @@ endif()
 # ---- Declare Tests ----
 
 # base target that indirectly depends on all tests
-add_custom_target(patomic-test)
+set(test_target_name "patomic-test")
+add_custom_target(${test_target_name})
 
 # always add include and src; used by and linked to all test targets
 # must be added first to create targets that we use when creating test targets
@@ -72,10 +82,3 @@ add_subdirectory(src)
 
 # add directory with all test kinds
 add_subdirectory(kind)
-
-
-# ---- Support Packaging Library ----
-
-if(PROJECT_IS_TOP_LEVEL AND NOT CMAKE_SKIP_INSTALL_RULES)
-    include(CPack)
-endif()
diff --git a/test/cmake/CreateTest.cmake b/test/cmake/CreateTest.cmake
index acd2c9eba..9e5a2124a 100644
--- a/test/cmake/CreateTest.cmake
+++ b/test/cmake/CreateTest.cmake
@@ -18,21 +18,13 @@
 # - each test (not target) will have ${kind} appended to its LABELS property
 # - main use case is for code coverage to be able to only run unit tests
 #
-# Working Directory (CTest):
-# - each test target has its working directory set to ${PROJECT_BINARY_DIR}/working/${kind}/${name}
-# - if a multi-config generator is used, 'working' is replaced with 'working/$<CONFIG>'
-# - reasoning:
-#   - coverage files generated by executables may overwrite existing coverage files (e.g. with clang)
-#   - this is an issue if multiple test executables exist in the same directory
-#   - this solution solves that by running each executable in its own directory
-#
 # Hierarchy:
 # - patomic-test -> base custom target & component (build/install) for all tests
 # - patomic-test-${kind} -> custom target & component (build install) for all tests of a specific kind
 # - patomic-test-${kind}-${name} -> executable target for a single test
 #
 # _create_test(
-#     BT|UT <name>
+#     BT|ST|UT <name>
 #     [INCLUDE <item>...]
 #     [SOURCE <item>...]
 #     [LINK <item>...]
@@ -94,7 +86,7 @@ function(_create_test)
     endif()
 
     # check there are no leftover arguments
-    if(DEFINED ARG_UNPARSED_ARGUMENTS)
+    if(ARG_UNPARSED_ARGUMENTS)
         message(WARNING "The following arguments were not recognised when invoking '${func_name}': ${ARG_UNPARSED_ARGUMENTS}")
         set(args_valid FALSE)
     endif()
@@ -108,7 +100,7 @@ function(_create_test)
     # create test target
 
     # setup
-    set(base_target patomic-test)              # patomic-test
+    set(base_target ${test_target_name})       # patomic-test
     set(parent_target ${base_target}-${kind})  # patomic-test-bt
     set(target ${parent_target}-${name})       # patomic-test-bt-SomeExample
 
@@ -121,23 +113,20 @@ function(_create_test)
     add_executable(${target})
 
     # add sources to target
-    target_sources(
-        ${target} PRIVATE
+    target_sources(${target} PRIVATE
         ${ARG_SOURCE}
     )
 
     # add include directories
-    target_include_directories(
-        ${target} PRIVATE
-        "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
+    # include/ is added as part of patomic-test-include target
+    target_include_directories(${target} PRIVATE
         ${ARG_INCLUDE}
     )
 
     # add /src and /include sources that apply to all test targets
-    target_link_libraries(
-        ${target} PRIVATE
-        patomic-test-include
-        patomic-test-src
+    target_link_libraries(${target} PRIVATE
+        ${test_target_name}-include
+        ${test_target_name}-src
     )
 
     # update dependencies list directly because we use it in Windows PATH stuff later
@@ -153,49 +142,45 @@ function(_create_test)
     endif()
 
     # link dependencies (all tests use GTest framework)
-    target_link_libraries(
-        ${target} PRIVATE
+    target_link_libraries(${target} PRIVATE
         ${ARG_LINK}
     )
 
     # require C++14 as minimum
-    target_compile_features(
-        ${target} PRIVATE
+    target_compile_features(${target} PRIVATE
         cxx_std_14
     )
 
     # set macro to know which test kind code is part of
     string(TOUPPER "${kind}" kind_upper)
-    target_compile_definitions(
-        ${target} PRIVATE
+    target_compile_definitions(${target} PRIVATE
         "PATOMIC_TEST_KIND_${kind_upper}=1"
     )
 
     # set binary name instead of using default
-    set_target_properties(
-        ${target} PROPERTIES
+    set_target_properties(${target} PROPERTIES
         OUTPUT_NAME "${name}"
     )
 
 
     # register test with GTest/CTest and parent target
 
-    # setup path differently depending on generator
-    get_property(is_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
-    set(test_working_dir "${PROJECT_BINARY_DIR}/working")
-    if(is_multi_config)
-        set(test_working_dir "${test_working_dir}/$<CONFIG>/${kind}/${name}")
-    else()
-        set(test_working_dir "${test_working_dir}/${kind}/${name}")
-    endif()
-
     # must be run in same directory scope as target
     gtest_add_tests(
         TARGET ${target}
         TEST_LIST added_tests
-        WORKING_DIRECTORY "${test_working_dir}"
     )
 
+    # when compiling with LLVM, use unique name for each test's coverage file
+    # otherwise they will all have the same name and overwrite each other
+    # since the variable is prefixed with LLVM, no harm in always setting it
+    foreach(test IN LISTS added_tests)
+        string(MAKE_C_IDENTIFIER "${test}" safe_test_name)
+        set_tests_properties("${test}" PROPERTIES
+            ENVIRONMENT "LLVM_PROFILE_FILE=${safe_test_name}.profraw"
+        )
+    endforeach()
+
     # add label to tests so ctest can run them by kind
     foreach(test IN LISTS added_tests)
         set_property(
@@ -204,14 +189,7 @@ function(_create_test)
         )
     endforeach()
 
-    # custom target to make sure the working directory exists for the test
-    add_custom_target(
-        ${target}-create-working-dir
-        COMMAND "${CMAKE_COMMAND}" -E make_directory "${test_working_dir}"
-    )
-
     # set build dependencies
-    add_dependencies(${target} ${target}-create-working-dir)
     add_dependencies(${parent_target} ${target})
 
 
@@ -222,7 +200,7 @@ function(_create_test)
 
         # get paths to all shared library dependencies (DLLs)
         # this should just be patomic and gtest
-        set(deps_paths )
+        unset(deps_paths)
         foreach(dep_target IN LISTS ARG_LINK)
             # This will fail if passed a link option that isn't a target
             # This is intentional; don't do that.
@@ -245,8 +223,7 @@ function(_create_test)
 
         # modify environment variable for each test so that CTest can find DLLs
         foreach(test IN LISTS added_tests)
-            set_tests_properties(
-                "${test}" PROPERTIES
+            set_tests_properties("${test}" PROPERTIES
                 ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:${deps_paths}"
             )
         endforeach()
@@ -358,14 +335,15 @@ function(create_ut)
         UT      ${ARG_NAME}
         SOURCE  ${ARG_SOURCE}
         INCLUDE
-            "$<BUILD_INTERFACE:${PATOMIC_BINARY_DIR}/include>"
             "$<BUILD_INTERFACE:${PATOMIC_SOURCE_DIR}/include>"
+            "$<BUILD_INTERFACE:${PATOMIC_BINARY_DIR}/include>"
             "$<BUILD_INTERFACE:${PATOMIC_SOURCE_DIR}/src/include>"
+            "$<BUILD_INTERFACE:${PATOMIC_BINARY_DIR}/src/include>"
             ${ARG_INCLUDE}
     )
 
     # visibility macros will break UTs on Windows
-    set(target_name patomic-test-ut-${ARG_NAME})
+    set(target_name ${test_target_name}-ut-${ARG_NAME})
     target_compile_definitions(${target_name} PRIVATE PATOMIC_STATIC_DEFINE)
 
 endfunction()
diff --git a/test/cmake/OptionVariables.cmake b/test/cmake/OptionVariables.cmake
index e05f963a2..79dcc12cc 100644
--- a/test/cmake/OptionVariables.cmake
+++ b/test/cmake/OptionVariables.cmake
@@ -1,13 +1,15 @@
+include(GNUInstallDirs)
+
 # ---- Options Summary ----
 
-# -----------------------------------------------------------------------
-# | Option                                | Availability   | Default    |
-# |=======================================|================|============|
-# | CMAKE_INSTALL_TESTDIR (unofficial)    | Always         | share/test |
-# |---------------------------------------|----------------|------------|
-# | PATOMIC_CREATE_TEST_TARGETS_MATCHING  | Always         | ^(.*)$     |
-# | PATOMIC_WINDOWS_MODIFY_CTEST_PATH_ENV | Always (3.22+) | ON         |
-# -----------------------------------------------------------------------
+# ----------------------------------------------------------------------------------------------
+# | Option                                | Availability   | Default                           |
+# |=======================================|================|===================================|
+# | CMAKE_INSTALL_TESTDIR (unofficial)    | Always         | ${CMAKE_INSTALL_DATAROOTDIR}/test |
+# |---------------------------------------|----------------|-----------------------------------|
+# | PATOMIC_CREATE_TEST_TARGETS_MATCHING  | Always         | ^(.*)$                            |
+# | PATOMIC_WINDOWS_MODIFY_CTEST_PATH_ENV | Always (3.22+) | ON                                |
+# ----------------------------------------------------------------------------------------------
 
 
 # ---- Test Install Directory ----
@@ -16,17 +18,35 @@
 # they're executables.
 # This is undesirable, so this variable exists to override the install location
 # of test binaries separately.
-# It's not prefixed with PATOMIC_ because it's ok for it to be shared and
+# It's not project-specific because it's ok for it to be shared and
 # overridden by parent projects.
-# Note: this is not an official CMake variable
 # The variable type is STRING rather than PATH, because otherwise passing
 # -DCMAKE_INSTALL_TESTDIR=share/test on the command line would expand to an
 # absolute path with the base being the current CMake directory, leading to
 # unexpected errors.
+#
+# Note: this is not an official CMake variable
 set(
-    CMAKE_INSTALL_TESTDIR "share/test"
-    CACHE STRING "(unofficial) Default test install location"
+    CMAKE_INSTALL_TESTDIR "${CMAKE_INSTALL_DATAROOTDIR}/test"
+    CACHE STRING "(unofficial) CMake top level test location relative to the install prefix"
+)
+# set it back to a PATH type for GUI assistance
+set_property(CACHE CMAKE_INSTALL_TESTDIR PROPERTY TYPE PATH)
+# depends on CMAKE_INSTALL_DATAROOTDIR which is marked as advanced in GNUInstallDirs
+mark_as_advanced(CMAKE_INSTALL_TESTDIR)
+# generate absolute path version
+set(dir "DATAROOTDIR")  # use DATAROOTDIR since that forms the root of CMAKE_INSTALL_TESTDIR
+set(dir_param )
+if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.20)
+    set(dir_param "${dir}")
+endif()
+GNUInstallDirs_get_absolute_install_dir(
+    CMAKE_INSTALL_FULL_TESTDIR
+    CMAKE_INSTALL_TESTDIR
+    ${dir_param}
 )
+unset(dir_param)
+unset(dir)
 
 
 # ---- Test Build Selection ----
@@ -61,5 +81,6 @@ elseif(PATOMIC_WINDOWS_MODIFY_CTEST_PATH_ENV)
         WARNING
         "Option 'PATOMIC_WINDOWS_MODIFY_CTEST_PATH_ENV' for 'patomic_test' requires CMake 3.22+, currently running ${CMAKE_VERSION}, option is disabled"
     )
-    set(PATOMIC_WINDOWS_MODIFY_CTEST_PATH_ENV )
-endif()
\ No newline at end of file
+    unset(CACHE PATOMIC_WINDOWS_MODIFY_CTEST_PATH_ENV)
+    unset(PATOMIC_WINDOWS_MODIFY_CTEST_PATH_ENV)
+endif()
diff --git a/test/include/CMakeLists.txt b/test/include/CMakeLists.txt
index 6205c9ebb..9995062a2 100644
--- a/test/include/CMakeLists.txt
+++ b/test/include/CMakeLists.txt
@@ -1,15 +1,18 @@
-# fine with CML file here because include isn't installed for tests
-
 # create interface target that is automatically linked to all tests
-# this is solely so that IDEs understand that these are source files
-# include directories are set in test creation
-add_library(
-    patomic-test-include INTERFACE
-    # .
+add_library(${test_target_name}-include INTERFACE)
+
+# add include directory paths
+target_include_directories(${test_target_name}-include INTERFACE
+    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
+    "$<BUILD_INTERFACE:${PATOMIC_SOURCE_DIR}/include>"
+    "$<BUILD_INTERFACE:${PATOMIC_BINARY_DIR}/include>"
+    "$<BUILD_INTERFACE:${GTEST_INCLUDE_DIRS}>"
 )
 
 # require C++14 as minimum
-target_compile_features(
-    patomic-test-include INTERFACE
+target_compile_features(${test_target_name}-include INTERFACE
     cxx_std_14
 )
+
+# add all subdirectories
+add_subdirectory(test)
diff --git a/test/include/test/CMakeLists.txt b/test/include/test/CMakeLists.txt
new file mode 100644
index 000000000..a1b47bba4
--- /dev/null
+++ b/test/include/test/CMakeLists.txt
@@ -0,0 +1,2 @@
+# add all subdirectories
+add_subdirectory(common)
diff --git a/test/include/test/common/CMakeLists.txt b/test/include/test/common/CMakeLists.txt
new file mode 100644
index 000000000..acbc9adf8
--- /dev/null
+++ b/test/include/test/common/CMakeLists.txt
@@ -0,0 +1,9 @@
+# add directory files to target
+target_sources(${test_target_name}-include INTERFACE
+    align.hpp
+    compare.hpp
+    death.hpp
+    make_ops.hpp
+    math.hpp
+    utility.hpp
+)
diff --git a/test/include/test/common/align.hpp b/test/include/test/common/align.hpp
new file mode 100644
index 000000000..4731a1b50
--- /dev/null
+++ b/test/include/test/common/align.hpp
@@ -0,0 +1,50 @@
+#ifndef PATOMIC_TEST_COMMON_ALIGN_HPP
+#define PATOMIC_TEST_COMMON_ALIGN_HPP
+
+#include <cstddef>
+
+namespace test
+{
+
+
+/// @brief
+///   Returns a pointer to memory inside the buffer aligned at least to "align"
+///   where pointer + "size" does not extend outside the buffer, or nullptr if
+///   no such pointer exists.
+///
+/// @note
+///   This function supports invalid alignments which are not powers of 2. If
+///   "align" is zero, "buf_ptr" is returned directly as long as "size" is not
+///   larger than "buf_size" (in which case nullptr is returned).
+void *
+aligned_pointer(
+    void *buf_ptr, std::size_t buf_size, std::size_t align, std::size_t size
+) noexcept;
+
+
+/// @brief
+///   Returns a pointer to memory inside the buffer aligned at least to "align"
+///   where pointer + "size" does not extend outside the buffer, or nullptr if
+///   no such pointer exists.
+///
+/// @note
+///   This function supports invalid alignments which are not powers of 2. If
+///   "align" is zero, "buf_ptr" is returned directly as long as "size" is not
+///   larger than "buf_size" (in which case nullptr is returned).
+const void *
+aligned_pointer(
+    const void *buf_ptr, std::size_t buf_size, std::size_t align,
+    std::size_t size
+) noexcept;
+
+
+/// @brief
+///   Provides the runtime alignment of the pointed-to object. This will always
+///   be a positive power of 2.
+std::size_t
+runtime_alignof(const void *ptr) noexcept;
+
+
+}  // namespace test
+
+#endif  // PATOMIC_TEST_COMMON_ALIGN_HPP
diff --git a/test/include/test/common/compare.hpp b/test/include/test/common/compare.hpp
new file mode 100644
index 000000000..31cf4df76
--- /dev/null
+++ b/test/include/test/common/compare.hpp
@@ -0,0 +1,16 @@
+#ifndef PATOMIC_TEST_COMMON_COMPARE_HPP
+#define PATOMIC_TEST_COMMON_COMPARE_HPP
+
+
+#include <patomic/api/align.h>
+
+
+bool
+operator==(const patomic_align_t& lhs, const patomic_align_t& rhs) noexcept;
+
+
+bool
+operator!=(const patomic_align_t& lhs, const patomic_align_t& rhs) noexcept;
+
+
+#endif  // PATOMIC_TEST_COMMON_COMPARE_HPP
diff --git a/test/include/test/common/death.hpp b/test/include/test/common/death.hpp
new file mode 100644
index 000000000..d10ca2bff
--- /dev/null
+++ b/test/include/test/common/death.hpp
@@ -0,0 +1,36 @@
+#ifndef PATOMIC_TEST_COMMON_KILLED_BY_HPP
+#define PATOMIC_TEST_COMMON_KILLED_BY_HPP
+
+#include <gtest/gtest.h>
+
+
+namespace test
+{
+
+
+/// @brief
+///   Helper type which can be passed by a matcher to any GTest macro which
+///   passes an exit code.
+#if defined(GTEST_OS_WINDOWS) || defined(GTEST_OS_FUCHSIA)
+#define PATOMIC_GTEST_HAS_KILLED_BY_SIGNAL 0
+using KilledByAbortPredicateType = testing::ExitedWithCode;
+#else
+#define PATOMIC_GTEST_HAS_KILLED_BY_SIGNAL 1
+using KilledByAbortPredicateType = testing::KilledBySignal;
+#endif
+
+
+/// @brief
+///   Produces a predicate that checks if an int exit code is what would be
+///   expected from a process killed by abort() (which may ask the runtime to
+///   kill it in a variety of ways).
+///
+/// @note
+///   Predicate is callable with the signature bool()(int).
+KilledByAbortPredicateType
+KilledByAbort();
+
+
+}  // namespace test
+
+#endif  // PATOMIC_TEST_COMMON_KILLED_BY_HPP
diff --git a/test/include/test/common/make_ops.hpp b/test/include/test/common/make_ops.hpp
new file mode 100644
index 000000000..abe8c1dfd
--- /dev/null
+++ b/test/include/test/common/make_ops.hpp
@@ -0,0 +1,345 @@
+#ifndef PATOMIC_TEST_PATOMIC_MAKE_OPS_HPP
+#define PATOMIC_TEST_PATOMIC_MAKE_OPS_HPP
+
+#include <patomic/api/core.h>
+#include <patomic/api/feature_check.h>
+#include <patomic/api/ops.h>
+
+#include <array>
+#include <string>
+#include <vector>
+
+
+namespace test
+{
+
+
+/// @brief
+///   Helper enum to use as a template parameter.
+enum class ops_domain
+{
+    IMPLICIT,
+    EXPLICIT,
+    TRANSACTION
+};
+
+
+/// @brief
+///   Convert enum labels to strings.
+std::string
+to_string(ops_domain domain);
+
+
+/// @brief
+///   Helper type to convert from domain to patomic ops types.
+template <ops_domain D>
+struct ops_types;
+
+template <>
+struct ops_types<ops_domain::IMPLICIT>
+{
+    static constexpr unsigned int full_opcat = patomic_opcats_IMPLICIT;
+
+    using base_t = patomic_t;
+    using ldst_t = patomic_ops_t;
+    using xchg_t = patomic_ops_xchg_t;
+    using bitwise_t = patomic_ops_bitwise_t;
+    using binary_t = patomic_ops_binary_t;
+    using arithmetic_t = patomic_ops_arithmetic_t;
+};
+
+template <>
+struct ops_types<ops_domain::EXPLICIT>
+{
+    static constexpr unsigned int full_opcat = patomic_opcats_EXPLICIT;
+
+    using base_t = patomic_explicit_t;
+    using ldst_t = patomic_ops_explicit_t;
+    using xchg_t = patomic_ops_explicit_xchg_t;
+    using bitwise_t = patomic_ops_explicit_bitwise_t;
+    using binary_t = patomic_ops_explicit_binary_t;
+    using arithmetic_t = patomic_ops_explicit_arithmetic_t;
+};
+
+template <>
+struct ops_types<ops_domain::TRANSACTION>
+{
+    static constexpr unsigned int full_opcat = patomic_opcats_TRANSACTION;
+
+    using base_t = patomic_transaction_t;
+    using ldst_t = patomic_ops_transaction_t;
+    using xchg_t = patomic_ops_transaction_xchg_t;
+    using bitwise_t = patomic_ops_transaction_bitwise_t;
+    using binary_t = patomic_ops_transaction_binary_t;
+    using arithmetic_t = patomic_ops_transaction_arithmetic_t;
+    using special_t = patomic_ops_transaction_special_t;
+    using flag_t = patomic_ops_transaction_flag_t;
+    using raw_t = patomic_ops_transaction_raw_t;
+};
+
+
+/// @brief
+///   Helper struct to indicate if any/all function pointers are set in the ops
+///   member.
+template <class T>
+struct ops_any_all
+{
+    T ops {};
+    bool any {};
+    bool all {};
+    unsigned int opkinds {};
+};
+
+
+/// @brief
+///   Helper struct to indicate if any/all function pointers are set in the ops
+///   member. Differentiates between void and fetch operations.
+template <class T>
+struct ops_any_all_vf
+{
+    T ops {};
+    bool any_void {};
+    bool any_fetch {};
+    bool all_void {};
+    bool all_fetch {};
+    unsigned int opkinds_void {};
+    unsigned int opkinds_fetch {};
+};
+
+
+/// @brief
+///   Create a list of all valid patomic_opcat_t values with exactly zero or
+///   one bits set.
+std::vector<patomic_opcat_t>
+make_opcats_all_solo();
+
+
+/// @brief
+///   Create a list of all valid patomic_opcat_t values with more than one bit
+///   set.
+std::vector<patomic_opcat_t>
+make_opcats_all_combined();
+
+
+/// @brief
+///   Create a list of all valid patomic_opkind_t values with exactly zero or
+///   one bits set.
+std::vector<patomic_opkind_t>
+make_opkinds_all_solo();
+
+
+/// @brief
+///   Create a list of all valid patomic_opkind_t values with more than one bit
+///   set.
+std::vector<patomic_opkind_t>
+make_opkinds_all_combined();
+
+
+/// @brief
+///   Create a patomic_ops*_t object where all members are set to a provided
+///   value.
+template <ops_domain D>
+typename ops_types<D>::ldst_t
+make_ops_all_nonnull(void(*nonnull_value)()) noexcept;
+
+
+/// @brief
+///   Create a patomic_ops*_t object where all members are set to a non-null
+///   value.
+template <ops_domain D>
+typename ops_types<D>::ldst_t
+make_ops_all_nonnull() noexcept;
+
+
+/// @brief
+///   Create a set of patomic_ops*_t objects with all combinations of fp_store
+///   and fp_load members set to a provided value. All other members are null.
+template <ops_domain D>
+std::vector<ops_any_all<typename ops_types<D>::ldst_t>>
+make_ops_ldst_combinations(void(*nonnull_value)());
+
+
+/// @brief
+///   Create a set of patomic_ops*_t objects with all combinations of fp_store
+///   and fp_load members set to null and non-null values. All other members
+///   are null.
+template <ops_domain D>
+std::vector<ops_any_all<typename ops_types<D>::ldst_t>>
+make_ops_ldst_combinations();
+
+
+/// @brief
+///   Create an array of fp_store and fp_load members in a patomic_ops*_t
+///   object, with the types cast to void(*)().
+template <ops_domain D>
+std::array<void(*)(), 2>
+make_ops_ldst_array(const typename ops_types<D>::ldst_t& ldst) noexcept;
+
+
+/// @brief
+///   Create a set of patomic_ops*_xchg_t objects with all combinations of
+///   members set to a provided value. All other members are null.
+template <ops_domain D>
+std::vector<ops_any_all<typename ops_types<D>::xchg_t>>
+make_ops_xchg_combinations(void(*nonnull_value)());
+
+
+/// @brief
+///   Create a set of patomic_ops*_xchg_t objects with all combinations of
+///   members set to null and non-null values.
+template <ops_domain D>
+std::vector<ops_any_all<typename ops_types<D>::xchg_t>>
+make_ops_xchg_combinations();
+
+
+/// @brief
+///   Create an array of members in a patomic_ops*_xchg_t object, with the
+///   types cast to void(*)().
+template <ops_domain D>
+std::array<void(*)(), 3>
+make_ops_xchg_array(const typename ops_types<D>::xchg_t& xchg) noexcept;
+
+
+/// @brief
+///   Create a set of patomic_ops*_bitwise_t objects with all combinations of
+///   members set to a provided value. All other members are null.
+template <ops_domain D>
+std::vector<ops_any_all<typename ops_types<D>::bitwise_t>>
+make_ops_bitwise_combinations(void(*nonnull_value)());
+
+
+/// @brief
+///   Create a set of patomic_ops*_bitwise_t objects with all combinations of
+///   members set to null and non-null values.
+template <ops_domain D>
+std::vector<ops_any_all<typename ops_types<D>::bitwise_t>>
+make_ops_bitwise_combinations();
+
+
+/// @brief
+///   Create an array of members in a patomic_ops*_bitwise_t object, with the
+///   types cast to void(*)().
+template <ops_domain D>
+std::array<void(*)(), 4>
+make_ops_bitwise_array(const typename ops_types<D>::bitwise_t& bitwise) noexcept;
+
+
+/// @brief
+///   Create a set of patomic_ops*_binary_t objects with all combinations of
+///   void and fetch members set to a provided value. All other members are
+///   null.
+template <ops_domain D>
+std::vector<ops_any_all_vf<typename ops_types<D>::binary_t>>
+make_ops_binary_combinations(void(*nonnull_value)());
+
+
+/// @brief
+///   Create a set of patomic_ops*_binary_t objects with all combinations of
+///   void and fetch members set to null and non-null values.
+template <ops_domain D>
+std::vector<ops_any_all_vf<typename ops_types<D>::binary_t>>
+make_ops_binary_combinations();
+
+
+/// @brief
+///   Create an array of members in a patomic_ops*_binary_t object, with the
+///   types cast to void(*)().
+template <ops_domain D>
+std::array<void(*)(), 8>
+make_ops_binary_array(const typename ops_types<D>::binary_t& binary) noexcept;
+
+
+/// @brief
+///   Create a set of patomic_ops*_arithmetic_t objects with all combinations of
+///   void and fetch members set to a provided value. All other members are
+///   null.
+template <ops_domain D>
+std::vector<ops_any_all_vf<typename ops_types<D>::arithmetic_t>>
+make_ops_arithmetic_combinations(void(*nonnull_value)());
+
+
+/// @brief
+///   Create a set of patomic_ops*_arithmetic_t objects with all combinations of
+///   void and fetch members set to null and non-null values.
+template <ops_domain D>
+std::vector<ops_any_all_vf<typename ops_types<D>::arithmetic_t>>
+make_ops_arithmetic_combinations();
+
+
+/// @brief
+///   Create an array of members in a patomic_ops*_arithmetic_t object, with the
+///   types cast to void(*)().
+template <ops_domain D>
+std::array<void(*)(), 10>
+make_ops_arithmetic_array(const typename ops_types<D>::arithmetic_t& arithmetic) noexcept;
+
+
+/// @brief
+///   Create a set of patomic_ops_transaction_special_t objects with all
+///   combinations of members set to a provided value. All other members are
+///   null.
+std::vector<ops_any_all<patomic_ops_transaction_special_t>>
+make_ops_special_combinations_transaction(void(*nonnull_value)());
+
+
+/// @brief
+///   Create a set of patomic_ops_transaction_special_t objects with all
+///   combinations of members set to null and non-null values.
+std::vector<ops_any_all<patomic_ops_transaction_special_t>>
+make_ops_special_combinations_transaction();
+
+
+/// @brief
+///   Create an array of members in a patomic_ops_transaction_special_t object,
+///   with the types cast to void(*)().
+std::array<void(*)(), 4>
+make_ops_special_array_transaction(const patomic_ops_transaction_special_t& special) noexcept;
+
+
+/// @brief
+///   Create a set of patomic_ops_transaction_flag_t objects with all
+///   combinations of members set to a provided value. All other members are
+///   null.
+std::vector<ops_any_all<patomic_ops_transaction_flag_t>>
+make_ops_flag_combinations_transaction(void(*nonnull_value)());
+
+
+/// @brief
+///   Create a set of patomic_ops_transaction_flag_t objects with all
+///   combinations of members set to null and non-null values.
+std::vector<ops_any_all<patomic_ops_transaction_flag_t>>
+make_ops_flag_combinations_transaction();
+
+
+/// @brief
+///   Create an array of members in a patomic_ops_transaction_flag_t object,
+///   with the types cast to void(*)().
+std::array<void(*)(), 3>
+make_ops_flag_array_transaction(const patomic_ops_transaction_flag_t& flag) noexcept;
+
+
+/// @brief
+///   Create a set of patomic_ops_transaction_raw_t objects with all
+///   combinations of members set to a provided value. All other members are
+///   null.
+std::vector<ops_any_all<patomic_ops_transaction_raw_t>>
+make_ops_raw_combinations_transaction(void(*nonnull_value)());
+
+
+/// @brief
+///   Create a set of patomic_ops_transaction_raw_t objects with all
+///   combinations of members set to null and non-null values.
+std::vector<ops_any_all<patomic_ops_transaction_raw_t>>
+make_ops_raw_combinations_transaction();
+
+
+/// @brief
+///   Create an array of members in a patomic_ops_transaction_raw_t object,
+///   with the types cast to void(*)().
+std::array<void(*)(), 4>
+make_ops_raw_array_transaction(const patomic_ops_transaction_raw_t& raw) noexcept;
+
+
+}  // namespace test
+
+#endif  // PATOMIC_TEST_PATOMIC_MAKE_OPS_HPP
diff --git a/test/include/test/common/math.hpp b/test/include/test/common/math.hpp
new file mode 100644
index 000000000..224950cdf
--- /dev/null
+++ b/test/include/test/common/math.hpp
@@ -0,0 +1,29 @@
+#ifndef PATOMIC_TEST_COMMON_MATH_HPP
+#define PATOMIC_TEST_COMMON_MATH_HPP
+
+#include <type_traits>
+
+namespace test
+{
+
+
+/// @brief
+///   Checks if a value is a positive power of 2.
+template <class T, std::enable_if_t<std::is_integral<T>::value, int> = 0>
+constexpr bool
+is_positive_pow2(const T value) noexcept
+{
+    // check value is positive
+    if (value <= 0)
+    {
+        return false;
+    }
+
+    // check value is a power of 2
+    return (value & (value - 1)) == 0;
+}
+
+
+}  // namespace test
+
+#endif  // PATOMIC_TEST_COMMON_MATH_HPP
diff --git a/test/include/test/common/utility.hpp b/test/include/test/common/utility.hpp
new file mode 100644
index 000000000..10fbc9b70
--- /dev/null
+++ b/test/include/test/common/utility.hpp
@@ -0,0 +1,39 @@
+#ifndef PATOMIC_TEST_COMMON_UTILITY_HPP
+#define PATOMIC_TEST_COMMON_UTILITY_HPP
+
+#include <utility>
+#include <type_traits>
+
+namespace test
+{
+
+
+/// @brief
+///   Type is implicitly convertible to any other type.
+template <class T>
+class convertible_to_any
+{
+public:
+    /// @brief
+    ///    Public constructor to provide value which will be converted.
+    explicit constexpr convertible_to_any(const T& value)
+        noexcept(std::is_nothrow_copy_constructible<T>::value)
+        : m_val(value)
+    {}
+
+    /// @brief
+    ///   Implicit conversion operator.
+    template <class U>
+    constexpr operator U() const noexcept
+    {
+        return reinterpret_cast<U>(m_val);
+    }
+
+private:
+    T m_val {};
+};
+
+
+}  // namespace test
+
+#endif  // PATOMIC_TEST_COMMON_UTILITY_HPP
diff --git a/test/kind/CMakeLists.txt b/test/kind/CMakeLists.txt
index 6a2917e2c..0ce819231 100644
--- a/test/kind/CMakeLists.txt
+++ b/test/kind/CMakeLists.txt
@@ -1,20 +1,4 @@
-# only add BTs if patomic target is available
-if(TARGET patomic::patomic)
-    add_subdirectory(bt)
-    message(STATUS "Enabled binary tests")
-else()
-    message(STATUS "Skipping binary tests; patomic target not available")
-endif()
-
-# always STs since they don't require patomic in any way
+# add all subdirectories
+add_subdirectory(bt)
 add_subdirectory(st)
-message(STATUS "Enabled system tests")
-
-# only add UTs if patomic files are available
-# these are currently set by patomic before including this project
-if(PATOMIC_BINARY_DIR AND PATOMIC_SOURCE_DIR)
-    add_subdirectory(ut)
-    message(STATUS "Enabled unit tests")
-else()
-    message(STATUS "Skipping unit tests; not building as sub-project of patomic")
-endif()
+add_subdirectory(ut)
diff --git a/test/kind/bt/CMakeLists.txt b/test/kind/bt/CMakeLists.txt
index cd4a859cb..dae0aee52 100644
--- a/test/kind/bt/CMakeLists.txt
+++ b/test/kind/bt/CMakeLists.txt
@@ -1,10 +1,7 @@
-include(../../cmake/CreateTest.cmake)
-
-
 # ---- Setup Tests ----
 
-add_custom_target(patomic-test-bt)
-add_dependencies(patomic-test patomic-test-bt)
+add_custom_target(${test_target_name}-bt)
+add_dependencies(${test_target_name} ${test_target_name}-bt)
 
-create_bt(NAME example_add SOURCE example_add.cpp)
-create_bt(NAME example_sub SOURCE example_sub.cpp)
+# add all subdirectories
+add_subdirectory(api)
diff --git a/test/kind/bt/api/CMakeLists.txt b/test/kind/bt/api/CMakeLists.txt
new file mode 100644
index 000000000..a4b3d669e
--- /dev/null
+++ b/test/kind/bt/api/CMakeLists.txt
@@ -0,0 +1,50 @@
+# ---- Create Tests ----
+
+create_bt(
+    NAME BtApiAlign
+    SOURCE
+        align.cpp
+)
+
+create_bt(
+    NAME BtApiCombine
+    SOURCE
+        combine.cpp
+)
+
+create_bt(
+    NAME BtApiFeatureCheck
+    SOURCE
+        feature_check_any_all.cpp
+        feature_check_leaf.cpp
+)
+
+create_bt(
+    NAME BtApiIds
+    SOURCE
+        ids.cpp
+)
+
+create_bt(
+    NAME BtApiMemoryOrder
+    SOURCE
+        memory_order.cpp
+)
+
+create_bt(
+    NAME BtApiOptions
+    SOURCE
+        options.cpp
+)
+
+create_bt(
+    NAME BtApiTransaction
+    SOURCE
+        transaction.cpp
+)
+
+create_bt(
+    NAME BtApiVersion
+    SOURCE
+        version.cpp
+)
diff --git a/test/kind/bt/api/align.cpp b/test/kind/bt/api/align.cpp
new file mode 100644
index 000000000..3fe96cb15
--- /dev/null
+++ b/test/kind/bt/api/align.cpp
@@ -0,0 +1,400 @@
+#include <patomic/api/align.h>
+
+#include <test/common/align.hpp>
+#include <test/common/math.hpp>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <cstddef>
+
+
+/// @brief Helper type that is over aligned.
+struct OverAlignedBuffer
+{
+    static constexpr auto align =
+        std::max(alignof(std::max_align_t), static_cast<std::size_t>(64U));
+    alignas(align) unsigned char data[align] {};
+    size_t size { align };
+};
+
+
+/// @brief Test fixture.
+class BtApiAlign : public testing::Test
+{};
+
+/// @brief Test fixture for experimental tests, testing functionality which is
+///        not required by the public API.
+class BtApiAlign_Experimental : public testing::Test
+{};
+
+
+/// @brief PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE is a positive power of 2.
+TEST_F(BtApiAlign, max_cache_line_size_macro_abi_unstable_is_pow2)
+{
+    // setup
+    constexpr auto macro_unstable = PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE;
+
+    // test
+    EXPECT_TRUE(test::is_positive_pow2(macro_unstable));
+}
+
+
+/// @brief Return value of patomic_max_cache_line_size() is a positive power
+///        of 2.
+TEST_F(BtApiAlign, max_cache_line_size_fn_is_pow2)
+{
+    // setup
+    const auto fnval = patomic_cache_line_size();
+
+    // test
+    EXPECT_TRUE(test::is_positive_pow2(fnval));
+}
+
+/// @brief Return value of patomic_max_cache_line_size() compares less than or
+///        equal to PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE.
+TEST_F(BtApiAlign, max_cache_line_size_fn_cmp_le_unstable_macro)
+{
+    // setup
+    constexpr auto macro_unstable = PATOMIC_MAX_CACHE_LINE_SIZE_ABI_UNSTABLE;
+    const auto fnval = patomic_cache_line_size();
+
+    // test
+    EXPECT_LE(fnval, macro_unstable);
+}
+
+
+/// @brief The check patomic_align_meets_recommended(...) fails when
+///        "recommended" is zero.
+TEST_F(BtApiAlign, meets_recommended_fails_recommended_is_zero)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    constexpr patomic_align_t align {};
+    const void *ptr = buf.data;
+
+    // test
+    EXPECT_EQ(0, align.recommended);
+    EXPECT_FALSE(patomic_align_meets_recommended(ptr, align));
+}
+
+/// @brief The check patomic_align_meets_recommended(...) fails when
+///        "recommended" is not a positive power of 2.
+TEST_F(BtApiAlign, meets_recommended_fails_recommended_non_pow2)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    constexpr patomic_align_t align { 3, 0, 0 };
+    const void *ptr = test::aligned_pointer(
+        buf.data, buf.size, align.recommended, 1);
+
+    // test
+    // recommended is positive but not a power of 2
+    EXPECT_NE(0, align.recommended);
+    EXPECT_FALSE(test::is_positive_pow2(align.recommended));
+    // pointer is aligned to recommended (don't use runtime_alignof because non-pow2)
+    ASSERT_NE(ptr, nullptr);
+    EXPECT_EQ(0, reinterpret_cast<std::uintptr_t>(ptr) % align.recommended);
+    // check fails
+    EXPECT_FALSE(patomic_align_meets_recommended(ptr, align));
+}
+
+/// @brief The check patomic_align_meets_recommended(...) fails when
+///        the given pointer's alignment is less than "recommended".
+TEST_F(BtApiAlign, meets_recommended_fails_cmp_gt_pointer_align)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    const patomic_align_t align { test::runtime_alignof(ptr) * 2, 0, 0 };
+
+    // test
+    // recommended is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.recommended));
+    // pointer is aligned less than recommended
+    EXPECT_LT(test::runtime_alignof(ptr), align.recommended);
+    // check fails
+    EXPECT_FALSE(patomic_align_meets_recommended(ptr, align));
+
+}
+
+/// @brief The check patomic_align_meets_recommended(...) succeeds when
+///        the given pointer's alignment equals "recommended".
+TEST_F(BtApiAlign, meets_recommended_succeeds_cmp_eq_pointer_align)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    const patomic_align_t align { test::runtime_alignof(ptr), 0, 0 };
+
+    // test
+    // recommended is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.recommended));
+    // pointer is aligned to exactly recommended
+    EXPECT_EQ(test::runtime_alignof(ptr), align.recommended);
+    // check succeeds
+    EXPECT_TRUE(patomic_align_meets_recommended(ptr, align));
+}
+
+/// @brief The check patomic_align_meets_recommended(...) succeeds when
+///        the given pointer's alignment exceeds "recommended".
+TEST_F(BtApiAlign, meets_recommended_succeeds_cmp_lt_pointer_align)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    const patomic_align_t align { test::runtime_alignof(ptr) / 2, 0, 0 };
+
+    // test
+    // recommended is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.recommended));
+    // pointer is aligned more than recommended
+    EXPECT_GT(test::runtime_alignof(ptr), align.recommended);
+    // check succeeds
+    EXPECT_TRUE(patomic_align_meets_recommended(ptr, align));
+}
+
+/// @brief The check patomic_align_meets_recommended(...) always succeeds when
+///        the given pointer is null.
+///
+/// @note  This test is experimental, and not an actual requirement of the API.
+TEST_F(BtApiAlign_Experimental, meets_recommended_succeeds_pointer_is_null)
+{
+    // setup
+    constexpr patomic_align_t align { 32768U, 0, 0 };
+    const void *ptr = nullptr;
+
+    // test
+    EXPECT_TRUE(test::is_positive_pow2(align.recommended));
+    EXPECT_TRUE(patomic_align_meets_recommended(ptr, align));
+}
+
+
+/// @brief The check patomic_align_meets_minimum(...) fails when "minimum" is
+///        zero.
+TEST_F(BtApiAlign, meets_minimum_fails_minimum_is_zero)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    constexpr patomic_align_t align {};
+
+    // test
+    EXPECT_EQ(0, align.minimum);
+    EXPECT_EQ(0, align.size_within);
+    EXPECT_FALSE(patomic_align_meets_minimum(buf.data, align, 1));
+}
+
+/// @brief The check patomic_align_meets_minimum(...) fails when "minimum" is
+///        not a positive power of 2.
+TEST_F(BtApiAlign, meets_minimum_fails_minimum_non_pow2)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    constexpr patomic_align_t align { 0, 3, 0 };
+    const void *ptr = test::aligned_pointer(
+        buf.data, buf.size, align.minimum, 1);
+
+    // test
+    // minimum is positive but not a power of 2
+    EXPECT_NE(0, align.minimum);
+    EXPECT_FALSE(test::is_positive_pow2(align.minimum));
+    EXPECT_EQ(0, align.size_within);
+    // pointer is aligned to minimum (don't use runtime_alignof because non-pow2)
+    ASSERT_NE(ptr, nullptr);
+    EXPECT_EQ(0, reinterpret_cast<std::uintptr_t>(ptr) % align.minimum);
+    // check fails
+    EXPECT_FALSE(patomic_align_meets_minimum(ptr, align, 1));
+}
+
+/// @brief The check patomic_align_meets_minimum(...) fails when the given
+///        pointer's alignment is less than "minimum".
+TEST_F(BtApiAlign, meets_minimum_fails_cmp_gt_pointer_align)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    const patomic_align_t align { 0, test::runtime_alignof(ptr) * 2, 0 };
+
+    // test
+    // minimum is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    EXPECT_EQ(0, align.size_within);
+    // pointer is aligned to less than minimum
+    EXPECT_LT(test::runtime_alignof(ptr), align.minimum);
+    // check fails
+    EXPECT_FALSE(patomic_align_meets_minimum(ptr, align, 1));
+}
+
+/// @brief The check patomic_align_meets_minimum(...) succeeds when the given
+///        pointer's alignment equals "minimum", and "size_within" is zero.
+TEST_F(BtApiAlign, meets_minimum_succeeds_cmp_eq_pointer_align)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    const patomic_align_t align { 0, test::runtime_alignof(ptr), 0 };
+
+    // test
+    // minimum is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    EXPECT_EQ(0, align.size_within);
+    // pointer is aligned to at least minimum
+    EXPECT_EQ(test::runtime_alignof(ptr), align.minimum);
+    // check succeeds
+    EXPECT_TRUE(patomic_align_meets_minimum(ptr, align, 1));
+}
+
+/// @brief The check patomic_align_meets_minimum(...) succeeds when the given
+///        pointer's alignment exceeds "minimum", and "size_within" is zero.
+TEST_F(BtApiAlign, meets_minimum_succeeds_cmp_lt_pointer_align)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    const patomic_align_t align { 0, test::runtime_alignof(ptr) / 2, 0 };
+
+    // test
+    // minimum is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    EXPECT_EQ(0, align.size_within);
+    // pointer is aligned to at least minimum
+    EXPECT_GT(test::runtime_alignof(ptr), align.minimum);
+    // check succeeds
+    EXPECT_TRUE(patomic_align_meets_minimum(ptr, align, 1));
+}
+
+/// @brief The check patomic_align_meets_minimum(...) succeeds when the pointer
+///        fulfills "minimum" and the buffer's size is zero for any
+///        "size_within" value.
+TEST_F(BtApiAlign, meets_minimum_succeeds_zero_size_buffer_any_size_within)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    patomic_align_t align { 0, test::runtime_alignof(ptr), 0 };
+
+    // test
+    // minimum is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    EXPECT_EQ(0, align.size_within);
+    // pointer is aligned to at least minimum
+    EXPECT_GE(test::runtime_alignof(ptr), align.minimum);
+    // check succeeds for successive values of size_within with a zero sized buffer
+    // should check zero and non-zero values for size_within
+    for (int i = 0; i < 20; ++i)
+    {
+        EXPECT_TRUE(patomic_align_meets_minimum(ptr, align, 0));
+        ++align.size_within;
+    }
+}
+
+/// @brief The check patomic_align_meets_minimum(...) succeeds when the pointer
+///        fulfills "minimum" and the buffer fits within a non-zero
+///        "size_within" with extra space remaining.
+TEST_F(BtApiAlign, meets_minimum_succeeds_buffer_smaller_fits_in_size_within)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data + 1;
+    constexpr patomic_align_t align { 0, 1, 8 };
+
+    // test
+    // minimum is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    EXPECT_GT(align.size_within, 2);
+    // buffer is aligned to at least minimum and size_within
+    // check this because pointer is offset from buffer
+    EXPECT_GE(test::runtime_alignof(buf.data), align.minimum);
+    EXPECT_GE(test::runtime_alignof(buf.data), align.size_within);
+    // pointer is aligned to at least minimum
+    EXPECT_GE(test::runtime_alignof(ptr), align.minimum);
+    // check succeeds
+    EXPECT_TRUE(patomic_align_meets_minimum(ptr, align, align.size_within - 2));
+}
+
+/// @brief The check patomic_align_meets_minimum(...) succeeds when the pointer
+///        fulfills "minimum" and the buffer fits within a non-zero
+///        "size_within" buffer exactly.
+TEST_F(BtApiAlign, meets_minimum_succeeds_buffer_exactly_fits_in_size_within)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    constexpr patomic_align_t align { 0, 1, 8 };
+
+    // test
+    // minimum is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    EXPECT_NE(0, align.size_within);
+    // pointer is aligned to at least minimum and size_within
+    EXPECT_GE(test::runtime_alignof(ptr), align.minimum);
+    EXPECT_GE(test::runtime_alignof(ptr), align.size_within);
+    // check succeeds
+    EXPECT_TRUE(patomic_align_meets_minimum(ptr, align, align.size_within));
+
+}
+
+/// @brief The check patomic_align_meets_minimum(...) fails when the pointer
+///        fulfills "minimum" but the buffer's size is larger than a non-zero
+///        "size_within".
+TEST_F(BtApiAlign, meets_minimum_fails_buffer_larger_than_size_within)
+{
+    // setup
+    constexpr OverAlignedBuffer buf;
+    const void *ptr = buf.data;
+    constexpr patomic_align_t align { 0, 1, 8 };
+
+    // test
+    // minimum is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    EXPECT_NE(0, align.size_within);
+    // pointer is aligned to at least minimum and size_within
+    EXPECT_GE(test::runtime_alignof(ptr), align.minimum);
+    EXPECT_GE(test::runtime_alignof(ptr), align.size_within);
+    // check fails
+    EXPECT_FALSE(patomic_align_meets_minimum(ptr, align, align.size_within + 1));
+}
+
+/// @brief The check patomic_align_meets_minimum(...) fails when the pointer
+///        fulfills "minimum" and the buffer's size is smaller than a non-zero
+///        "size_within", but the buffer does not fit within the "size_within"
+///        due to alignment constraints.
+TEST_F(BtApiAlign, meets_minimum_fails_buffer_fits_but_misaligned_for_size_within)
+{
+    // setup
+    // we need a pointer that is 16 bytes offset from a 64 byte aligned address
+    // the buffer is 32 bytes, crossing the 64 byte alignment boundary
+    alignas(64) constexpr char buffer[80] {};
+    const void *ptr = buffer + (64 - 16);
+    constexpr std::size_t size = 32;
+    constexpr patomic_align_t align { 0, 16, 64 };
+
+    // test
+    // minimum is valid
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    // size_within is 64 bytes but pointer is only aligned to 16 bytes
+    EXPECT_EQ(64, align.size_within);
+    EXPECT_EQ(16, align.minimum);
+    EXPECT_EQ(16, test::runtime_alignof(ptr));
+    // pointer is 16 bytes from a 64 byte alignment boundary and crosses it
+    EXPECT_LE(64, test::runtime_alignof(static_cast<const char *>(ptr) + 16));
+    EXPECT_GT(size, 16);
+    // check fails
+    EXPECT_FALSE(patomic_align_meets_minimum(ptr, align, size));
+}
+
+/// @brief The check patomic_align_meets_minimum(...) always succeeds when the
+///        given pointer is null.
+///
+/// @note  This test is experimental, and not an actual requirement of the API.
+TEST_F(BtApiAlign_Experimental, meets_minimum_succeeds_pointer_is_null)
+{
+    // setup
+    constexpr patomic_align_t align { 0, 32768U, 8 };
+    const void *ptr = nullptr;
+
+    // test
+    EXPECT_TRUE(test::is_positive_pow2(align.minimum));
+    EXPECT_NE(0, align.size_within);
+    EXPECT_TRUE(patomic_align_meets_minimum(ptr, align, 0));
+}
diff --git a/test/kind/bt/api/combine.cpp b/test/kind/bt/api/combine.cpp
new file mode 100644
index 000000000..379e4e90e
--- /dev/null
+++ b/test/kind/bt/api/combine.cpp
@@ -0,0 +1,557 @@
+#include <patomic/api/combine.h>
+
+#include <test/common/compare.hpp>
+#include <test/common/make_ops.hpp>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <array>
+#include <string>
+#include <type_traits>
+
+
+/// @brief Templated test fixture.
+template <class T>
+class BtApiCombineT : public testing::Test
+{
+public:
+    static constexpr test::ops_domain domain = T::value;
+    using OpsTypes = test::ops_types<domain>;
+};
+
+/// @brief Templated test fixture for "slow" tests.
+template <class T>
+class BtApiCombineT_SlowTest : public testing::Test
+{
+public:
+    static constexpr test::ops_domain domain = T::value;
+    using OpsTypes = test::ops_types<domain>;
+};
+
+using BtApiCombineT_Types = ::testing::Types<
+    std::integral_constant<test::ops_domain, test::ops_domain::IMPLICIT>,
+    std::integral_constant<test::ops_domain, test::ops_domain::EXPLICIT>
+>;
+
+namespace
+{
+
+/// @brief Helper type for templated test fixture.
+class TTestHelper
+{
+public:
+    template <class T>
+    static std::string
+    GetName(int)
+    {
+        auto name = test::to_string(T::value);
+        std::transform(name.begin(), name.end(), name.begin(),
+                       [](unsigned char c) noexcept -> char {
+            return static_cast<char>(std::tolower(c));
+        });
+        return name;
+    }
+
+    static void
+    combine(patomic_t& priority, const patomic_t& other) noexcept
+    {
+        return patomic_combine(&priority, &other);
+    }
+
+    static void
+    combine(patomic_explicit_t& priority, const patomic_explicit_t& other) noexcept
+    {
+        return patomic_combine_explicit(&priority, &other);
+    }
+};
+
+/// @brief Used to obtain a distinct non-null function pointer.
+void fn_a() noexcept {}
+
+/// @brief Used to obtain a distinct non-null function pointer.
+void fn_b() noexcept {}
+
+/// @brief Combine align "from" into "to".
+patomic_align_t
+combine_align(const patomic_align_t& to, const patomic_align_t& from) noexcept
+{
+    patomic_align_t ret;
+    ret.recommended = std::max(to.recommended, from.recommended);
+    ret.minimum = std::max(to.minimum, from.minimum);
+    if (to.size_within == 0)
+    {
+        ret.size_within = from.size_within;
+    }
+    else if (from.size_within == 0)
+    {
+        ret.size_within = to.size_within;
+    }
+    else
+    {
+        ret.size_within = std::min(to.size_within, from.size_within);
+    }
+    return ret;
+}
+
+// helper constants
+constexpr patomic_align_t strong_align { 64, 64, 16 };
+constexpr patomic_align_t normal_align { 32, 32, 32 };
+constexpr patomic_align_t weak_align { 16, 16, 64 };
+constexpr std::array<patomic_align_t, 3> aligns {
+    weak_align,
+    normal_align,
+    strong_align
+};
+
+}  // namespace
+
+TYPED_TEST_SUITE(
+    BtApiCombineT,
+    BtApiCombineT_Types,
+    TTestHelper
+);
+
+TYPED_TEST_SUITE(
+    BtApiCombineT_SlowTest,
+    BtApiCombineT_Types,
+    TTestHelper
+);
+
+
+/// @brief Calling combine with two operands where both have all ops non-null
+///        and compare equal and the copied-from alignment is weaker does not
+///        modify the alignment.
+TYPED_TEST(BtApiCombineT, combine_equal_nonnull_ops_does_not_optimize_align)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+    BaseT copied_to { test::make_ops_all_nonnull<D>(), strong_align };
+    const BaseT copied_from { test::make_ops_all_nonnull<D>(), weak_align };
+
+    // test
+    // check alignment is weaker on copied-from
+    EXPECT_LT(copied_from.align.recommended, copied_to.align.recommended);
+    EXPECT_LT(copied_from.align.minimum, copied_to.align.minimum);
+    EXPECT_GT(copied_from.align.size_within, copied_to.align.size_within);
+    EXPECT_GT(copied_to.align.size_within, 0);
+    // check ops compare equal and are non-null (if one op is, they all should be)
+    EXPECT_EQ(copied_from.ops.fp_store, copied_to.ops.fp_store);
+    EXPECT_NE(nullptr, copied_from.ops.fp_store);
+    // check that alignment doesn't change
+    EXPECT_EQ(copied_to.align, strong_align);
+    TTestHelper::combine(copied_to, copied_from);
+    EXPECT_EQ(copied_to.align, strong_align);
+}
+
+/// @brief Calling combine with two operands where both have all ops null and
+///        the copied-from alignment is weaker does not modify the alignment.
+TYPED_TEST(BtApiCombineT, combine_equal_null_ops_does_not_optimize_align)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+    BaseT copied_to { test::make_ops_all_nonnull<D>(nullptr), strong_align };
+    const BaseT copied_from { test::make_ops_all_nonnull<D>(nullptr), weak_align };
+
+    // test
+    // check alignment is weaker on copied-from
+    EXPECT_LT(copied_from.align.recommended, copied_to.align.recommended);
+    EXPECT_LT(copied_from.align.minimum, copied_to.align.minimum);
+    EXPECT_GT(copied_from.align.size_within, copied_to.align.size_within);
+    EXPECT_GT(copied_to.align.size_within, 0);
+    // check ops compare equal and are null (if one op is, they all should be)
+    EXPECT_EQ(copied_from.ops.fp_store, copied_to.ops.fp_store);
+    EXPECT_EQ(nullptr, copied_from.ops.fp_store);
+    // check that alignment doesn't change
+    EXPECT_EQ(copied_to.align, strong_align);
+    TTestHelper::combine(copied_to, copied_from);
+    EXPECT_EQ(copied_to.align, strong_align);
+}
+
+/// @brief The size_within member is considered less strict when its value is
+///        zero (copied-to) than when its value is non-zero (copied-from) for
+///        all ops.
+TYPED_TEST(BtApiCombineT, combine_size_within_to_zero_less_strict_than_from_non_zero)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+    BaseT copied_to {
+        test::make_ops_all_nonnull<D>(nullptr),
+        patomic_align_t { 1, 1, 0 }
+    };
+    const BaseT copied_from {
+        test::make_ops_all_nonnull<D>(),
+        patomic_align_t { 1, 1, 1 }
+    };
+
+    // test
+    // check alignment is equal but only copied-to has zero size_within
+    EXPECT_EQ(copied_from.align.recommended, copied_to.align.recommended);
+    EXPECT_EQ(copied_from.align.minimum, copied_to.align.minimum);
+    EXPECT_NE(0, copied_from.align.size_within);
+    EXPECT_EQ(0, copied_to.align.size_within);
+    // check ops are null for copied-to, and non-null for copied-from
+    EXPECT_EQ(nullptr, copied_to.ops.fp_store);
+    EXPECT_NE(nullptr, copied_from.ops.fp_store);
+    // check that size_within is updated to be non-zero
+    TTestHelper::combine(copied_to, copied_from);
+    EXPECT_NE(0, copied_to.align.size_within);
+}
+
+/// @brief The size_within member is considered less strict when its value is
+///        zero (copied-from) than when its value is non-zero (copied-to) for
+///        all ops.
+TYPED_TEST(BtApiCombineT, combine_size_within_from_zero_less_strict_than_to_non_zero)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+    BaseT copied_to {
+        test::make_ops_all_nonnull<D>(nullptr),
+        patomic_align_t { 1, 1, 1 }
+    };
+    const BaseT copied_from {
+        test::make_ops_all_nonnull<D>(),
+        patomic_align_t { 1, 1, 0 }
+    };
+
+    // test
+    // check alignment is equal but only copied-from has zero size_within
+    EXPECT_EQ(copied_from.align.recommended, copied_to.align.recommended);
+    EXPECT_EQ(copied_from.align.minimum, copied_to.align.minimum);
+    EXPECT_EQ(0, copied_from.align.size_within);
+    EXPECT_NE(0, copied_to.align.size_within);
+    // check ops are null for copied-to, and non-null for copied-from
+    EXPECT_EQ(nullptr, copied_to.ops.fp_store);
+    EXPECT_NE(nullptr, copied_from.ops.fp_store);
+    // check that size_within is updated to be non-zero
+    TTestHelper::combine(copied_to, copied_from);
+    EXPECT_NE(0, copied_to.align.size_within);
+}
+
+/// @brief Calling combine with all combinations of LDST ops set in both
+///        operands, with all combinations of alignment (stronger, equal,
+///        weaker), copies over the correct ops and adjusts the alignment
+///        correctly. Non-null ops compare unequal.
+TYPED_TEST(BtApiCombineT, combine_all_ldst_combinations_correct_result)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+
+    for (const auto& ldst_from : test::make_ops_ldst_combinations<D>(&fn_a))
+    {
+    for (const auto& ldst_to : test::make_ops_ldst_combinations<D>(&fn_b))
+    {
+    for (const auto align_from : aligns)
+    {
+        // patomic*_t objects
+        BaseT combined { ldst_to.ops, normal_align };
+        const BaseT copied_to = combined;
+        const BaseT copied_from { ldst_from.ops, align_from };
+        // arrays of relevant ops
+        const auto arr_to = test::make_ops_ldst_array<D>(copied_to.ops);
+        const auto arr_from = test::make_ops_ldst_array<D>(copied_from.ops);
+
+        // do combine
+        TTestHelper::combine(combined, copied_from);
+        const auto arr_combined = test::make_ops_ldst_array<D>(combined.ops);
+
+        // go through result
+        bool any_ops_copied = false;
+        for (std::size_t i = 0; i < arr_to.size(); ++i)
+        {
+            // test
+            if (arr_to[i] == nullptr && arr_from[i] != nullptr)
+            {
+                // if "to" is null, then we took "from"'s value
+                EXPECT_EQ(arr_combined[i], arr_from[i]);
+                any_ops_copied = true;
+            }
+            else
+            {
+                // if "to" is not null, then it didn't change
+                EXPECT_EQ(arr_combined[i], arr_to[i]);
+            }
+        }
+
+        // check alignment is copied correctly
+        if (any_ops_copied)
+        {
+            const auto combined_align =
+                combine_align(copied_to.align, copied_from.align);
+            EXPECT_EQ(combined.align, combined_align);
+        }
+        else
+        {
+            EXPECT_EQ(combined.align, copied_to.align);
+        }
+    }}}
+}
+
+/// @brief Calling combine with all combinations of XCHG ops set in both
+///        operands, with all combinations of alignment (stronger, equal,
+///        weaker), copies over the correct ops and adjusts the alignment
+///        correctly. Non-null ops compare unequal.
+TYPED_TEST(BtApiCombineT, combine_all_xchg_combinations_correct_result)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using OpsT = typename TestFixture::OpsTypes::ldst_t;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+
+    for (const auto& xchg_from : test::make_ops_xchg_combinations<D>(&fn_a))
+    {
+    for (const auto& xchg_to : test::make_ops_xchg_combinations<D>(&fn_b))
+    {
+    for (const auto align_from : aligns)
+    {
+        // patomic_ops*_t objects
+        OpsT ops_to {};
+        ops_to.xchg_ops = xchg_to.ops;
+        OpsT ops_from {};
+        ops_from.xchg_ops = xchg_from.ops;
+        // patomic*_t objects
+        BaseT combined { ops_to, normal_align };
+        const BaseT copied_to = combined;
+        const BaseT copied_from { ops_from, align_from };
+        // arrays of relevant ops
+        const auto arr_to = test::make_ops_xchg_array<D>(copied_to.ops.xchg_ops);
+        const auto arr_from = test::make_ops_xchg_array<D>(copied_from.ops.xchg_ops);
+
+        // do combine
+        TTestHelper::combine(combined, copied_from);
+        const auto arr_combined = test::make_ops_xchg_array<D>(combined.ops.xchg_ops);
+
+        // go through result
+        bool any_ops_copied = false;
+        for (std::size_t i = 0; i < arr_to.size(); ++i)
+        {
+            // test
+            if (arr_to[i] == nullptr && arr_from[i] != nullptr)
+            {
+                // if "to" is null, then we took "from"'s value
+                EXPECT_EQ(arr_combined[i], arr_from[i]);
+                any_ops_copied = true;
+            }
+            else
+            {
+                // if "to" is not null, then it didn't change
+                EXPECT_EQ(arr_combined[i], arr_to[i]);
+            }
+        }
+
+        // check alignment is copied correctly
+        if (any_ops_copied)
+        {
+            const auto combined_align =
+                combine_align(copied_to.align, copied_from.align);
+            EXPECT_EQ(combined.align, combined_align);
+        }
+        else
+        {
+            EXPECT_EQ(combined.align, copied_to.align);
+        }
+    }}}
+}
+
+/// @brief Calling combine with all combinations of BIT ops set in both
+///        operands, with all combinations of alignment (stronger, equal,
+///        weaker), copies over the correct ops and adjusts the alignment
+///        correctly. Non-null ops compare unequal.
+TYPED_TEST(BtApiCombineT, combine_all_bitwise_combinations_correct_result)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using OpsT = typename TestFixture::OpsTypes::ldst_t;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+
+    for (const auto& bitwise_from : test::make_ops_bitwise_combinations<D>(&fn_a))
+    {
+    for (const auto& bitwise_to : test::make_ops_bitwise_combinations<D>(&fn_b))
+    {
+    for (const auto align_from : aligns)
+    {
+        // patomic_ops*_t objects
+        OpsT ops_to {};
+        ops_to.bitwise_ops = bitwise_to.ops;
+        OpsT ops_from {};
+        ops_from.bitwise_ops = bitwise_from.ops;
+        // patomic*_t objects
+        BaseT combined { ops_to, normal_align };
+        const BaseT copied_to = combined;
+        const BaseT copied_from { ops_from, align_from };
+        // arrays of relevant ops
+        const auto arr_to = test::make_ops_bitwise_array<D>(copied_to.ops.bitwise_ops);
+        const auto arr_from = test::make_ops_bitwise_array<D>(copied_from.ops.bitwise_ops);
+
+        // do combine
+        TTestHelper::combine(combined, copied_from);
+        const auto arr_combined = test::make_ops_bitwise_array<D>(combined.ops.bitwise_ops);
+
+        // go through result
+        bool any_ops_copied = false;
+        for (std::size_t i = 0; i < arr_to.size(); ++i)
+        {
+            // test
+            if (arr_to[i] == nullptr && arr_from[i] != nullptr)
+            {
+                // if "to" is null, then we took "from"'s value
+                EXPECT_EQ(arr_combined[i], arr_from[i]);
+                any_ops_copied = true;
+            }
+            else
+            {
+                // if "to" is not null, then it didn't change
+                EXPECT_EQ(arr_combined[i], arr_to[i]);
+            }
+        }
+
+        // check alignment is copied correctly
+        if (any_ops_copied)
+        {
+            const auto combined_align =
+                combine_align(copied_to.align, copied_from.align);
+            EXPECT_EQ(combined.align, combined_align);
+        }
+        else
+        {
+            EXPECT_EQ(combined.align, copied_to.align);
+        }
+    }}}
+}
+
+/// @brief Calling combine with all combinations of BIN ops set in both
+///        operands, with all combinations of alignment (stronger, equal,
+///        weaker), copies over the correct ops and adjusts the alignment
+///        correctly. Non-null ops compare unequal.
+TYPED_TEST(BtApiCombineT_SlowTest, combine_all_binary_combinations_correct_result)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using OpsT = typename TestFixture::OpsTypes::ldst_t;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+
+    for (const auto& binary_from : test::make_ops_binary_combinations<D>(&fn_a))
+    {
+    for (const auto& binary_to : test::make_ops_binary_combinations<D>(&fn_b))
+    {
+    for (const auto align_from : aligns)
+    {
+        // patomic_ops*_t objects
+        OpsT ops_to {};
+        ops_to.binary_ops = binary_to.ops;
+        OpsT ops_from {};
+        ops_from.binary_ops = binary_from.ops;
+        // patomic*_t objects
+        BaseT combined { ops_to, normal_align };
+        const BaseT copied_to = combined;
+        const BaseT copied_from { ops_from, align_from };
+        // arrays of relevant ops
+        const auto arr_to = test::make_ops_binary_array<D>(copied_to.ops.binary_ops);
+        const auto arr_from = test::make_ops_binary_array<D>(copied_from.ops.binary_ops);
+
+        // do combine
+        TTestHelper::combine(combined, copied_from);
+        const auto arr_combined = test::make_ops_binary_array<D>(combined.ops.binary_ops);
+
+        // go through result
+        bool any_ops_copied = false;
+        for (std::size_t i = 0; i < arr_to.size(); ++i)
+        {
+            // test
+            if (arr_to[i] == nullptr && arr_from[i] != nullptr)
+            {
+                // if "to" is null, then we took "from"'s value
+                EXPECT_EQ(arr_combined[i], arr_from[i]);
+                any_ops_copied = true;
+            }
+            else
+            {
+                // if "to" is not null, then it didn't change
+                EXPECT_EQ(arr_combined[i], arr_to[i]);
+            }
+        }
+
+        // check alignment is copied correctly
+        if (any_ops_copied)
+        {
+            const auto combined_align =
+                combine_align(copied_to.align, copied_from.align);
+            EXPECT_EQ(combined.align, combined_align);
+        }
+        else
+        {
+            EXPECT_EQ(combined.align, copied_to.align);
+        }
+    }}}
+}
+
+/// @brief Calling combine with all combinations of ARI ops set in both
+///        operands, with all combinations of alignment (stronger, equal,
+///        weaker), copies over the correct ops and adjusts the alignment
+///        correctly. Non-null ops compare unequal.
+TYPED_TEST(BtApiCombineT_SlowTest, combine_all_arithmetic_combinations_correct_result)
+{
+    // setup
+    constexpr test::ops_domain D = TestFixture::domain;
+    using OpsT = typename TestFixture::OpsTypes::ldst_t;
+    using BaseT = typename TestFixture::OpsTypes::base_t;
+
+    for (const auto& arithmetic_from : test::make_ops_arithmetic_combinations<D>(&fn_a))
+    {
+    for (const auto& arithmetic_to : test::make_ops_arithmetic_combinations<D>(&fn_b))
+    {
+    for (const auto align_from : aligns)
+    {
+        // patomic_ops*_t objects
+        OpsT ops_to {};
+        ops_to.arithmetic_ops = arithmetic_to.ops;
+        OpsT ops_from {};
+        ops_from.arithmetic_ops = arithmetic_from.ops;
+        // patomic*_t objects
+        BaseT combined { ops_to, normal_align };
+        const BaseT copied_to = combined;
+        const BaseT copied_from { ops_from, align_from };
+        // arrays of relevant ops
+        const auto arr_to = test::make_ops_arithmetic_array<D>(copied_to.ops.arithmetic_ops);
+        const auto arr_from = test::make_ops_arithmetic_array<D>(copied_from.ops.arithmetic_ops);
+
+        // do combine
+        TTestHelper::combine(combined, copied_from);
+        const auto arr_combined = test::make_ops_arithmetic_array<D>(combined.ops.arithmetic_ops);
+
+        // go through result
+        bool any_ops_copied = false;
+        for (std::size_t i = 0; i < arr_to.size(); ++i)
+        {
+            // test
+            if (arr_to[i] == nullptr && arr_from[i] != nullptr)
+            {
+                // if "to" is null, then we took "from"'s value
+                EXPECT_EQ(arr_combined[i], arr_from[i]);
+                any_ops_copied = true;
+            }
+            else
+            {
+                // if "to" is not null, then it didn't change
+                EXPECT_EQ(arr_combined[i], arr_to[i]);
+            }
+        }
+
+        // check alignment is copied correctly
+        if (any_ops_copied)
+        {
+            const auto combined_align =
+                combine_align(copied_to.align, copied_from.align);
+            EXPECT_EQ(combined.align, combined_align);
+        }
+        else
+        {
+            EXPECT_EQ(combined.align, copied_to.align);
+        }
+    }}}
+}
diff --git a/test/kind/bt/api/feature_check_any_all.cpp b/test/kind/bt/api/feature_check_any_all.cpp
new file mode 100644
index 000000000..d34cadf39
--- /dev/null
+++ b/test/kind/bt/api/feature_check_any_all.cpp
@@ -0,0 +1,637 @@
+#include <patomic/api/feature_check.h>
+
+#include <test/common/math.hpp>
+#include <test/common/make_ops.hpp>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <bitset>
+#include <limits>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+
+/// @brief Test fixture.
+class BtApiFeatureCheckAnyAll : public testing::Test
+{};
+
+/// @brief Templated test fixture.
+template <class T>
+class BtApiFeatureCheckAnyAllT : public testing::Test
+{
+public:
+    static constexpr test::ops_domain domain = T::value;
+    using OpsTypes = test::ops_types<domain>;
+};
+
+using BtApiFeatureCheckAnyAllT_Types = ::testing::Types<
+    std::integral_constant<test::ops_domain, test::ops_domain::IMPLICIT>,
+    std::integral_constant<test::ops_domain, test::ops_domain::EXPLICIT>,
+    std::integral_constant<test::ops_domain, test::ops_domain::TRANSACTION>
+>;
+
+namespace
+{
+
+/// @brief Helper constant.
+constexpr auto UINT_BIT_WIDTH = std::numeric_limits<unsigned int>::digits;
+    
+/// @brief Helper type for templated test fixture.
+class TTestHelper
+{
+public:
+    template <class T>
+    static std::string
+    GetName(int)
+    {
+        auto name = test::to_string(T::value);
+        std::transform(name.begin(), name.end(), name.begin(),
+                       [](unsigned char c) noexcept -> char {
+            return static_cast<char>(std::tolower(c));
+        });
+        return name;
+    }
+
+    static unsigned int
+    check_any(const patomic_ops_t& ops, unsigned int opcats) noexcept
+    {
+        return patomic_feature_check_any(&ops, opcats);
+    }
+
+    static unsigned int
+    check_any(const patomic_ops_explicit_t& ops, unsigned int opcats) noexcept
+    {
+        return patomic_feature_check_any_explicit(&ops, opcats);
+    }
+
+    static unsigned int
+    check_any(const patomic_ops_transaction_t& ops, unsigned int opcats) noexcept
+    {
+        return patomic_feature_check_any_transaction(&ops, opcats);
+    }
+
+    static unsigned int
+    check_all(const patomic_ops_t& ops, unsigned int opcats) noexcept
+    {
+        return patomic_feature_check_all(&ops, opcats);
+    }
+
+    static unsigned int
+    check_all(const patomic_ops_explicit_t& ops, unsigned int opcats) noexcept
+    {
+        return patomic_feature_check_all_explicit(&ops, opcats);
+    }
+
+    static unsigned int
+    check_all(const patomic_ops_transaction_t& ops, unsigned int opcats) noexcept
+    {
+        return patomic_feature_check_all_transaction(&ops, opcats);
+    }
+};
+
+}  // namespace
+
+TYPED_TEST_SUITE(
+    BtApiFeatureCheckAnyAllT,
+    BtApiFeatureCheckAnyAllT_Types,
+    TTestHelper
+);
+
+
+/// @brief All "opcat" opcats have exactly one bit set, except for
+///        patomic_opcat_NONE which has zero bits set.
+TEST_F(BtApiFeatureCheckAnyAll, all_opcat_have_zero_or_one_bits_set)
+{
+    // test
+    for (const patomic_opcat_t opcat : test::make_opcats_all_solo())
+    {
+        if (opcat == patomic_opcat_NONE)
+        {
+            EXPECT_EQ(0, opcat);
+        }
+        else
+        {
+            EXPECT_TRUE(test::is_positive_pow2(static_cast<int>(opcat)));
+        }
+    }
+}
+
+/// @brief All "opcat" values are unique.
+TEST_F(BtApiFeatureCheckAnyAll, all_opcat_are_unique)
+{
+    // setup
+    const auto solo_opcats = test::make_opcats_all_solo();
+    const std::set<patomic_opcat_t> solo_opcats_set {
+        solo_opcats.begin(), solo_opcats.end()
+    };
+
+    // test
+    EXPECT_EQ(solo_opcats.size(), solo_opcats_set.size());
+}
+
+/// @brief All "opcats" opcats have multiple bits set.
+TEST_F(BtApiFeatureCheckAnyAll, all_opcats_have_multiple_bits_set)
+{
+    // test
+    for (const int opcats : test::make_opcats_all_combined())
+    {
+        EXPECT_GT(opcats, 0);
+        EXPECT_FALSE(test::is_positive_pow2(opcats));
+    }
+}
+
+/// @brief All "opcats" opcats have expected combination of bits.
+TEST_F(BtApiFeatureCheckAnyAll, all_opcats_have_expected_bits)
+{
+    // setup
+    // values
+    constexpr auto expected_bin = patomic_opcat_BIN_V | patomic_opcat_BIN_F;
+    constexpr auto expected_ari = patomic_opcat_ARI_V | patomic_opcat_ARI_F;
+    constexpr auto expected_implicit =
+        patomic_opcat_LDST |
+        patomic_opcat_XCHG |
+        patomic_opcat_BIT |
+        expected_bin      |
+        expected_ari;
+    constexpr auto expected_explicit = expected_implicit;
+    constexpr auto expected_transaction =
+        expected_implicit   |
+        patomic_opcat_TSPEC |
+        patomic_opcat_TFLAG |
+        patomic_opcat_TRAW;
+    // sets of values
+    const std::vector<int> expected_vec {
+        expected_bin,
+        expected_ari,
+        expected_implicit,
+        expected_explicit,
+        expected_transaction
+    };
+    const std::set<int> expected_set {
+        expected_vec.begin(), expected_vec.end()
+    };
+    const auto combined_opcats = test::make_opcats_all_combined();
+    const std::set<int> actual_set {
+        combined_opcats.begin(), combined_opcats.end()
+    };
+
+    // test
+    // checks that all values are expected
+    EXPECT_EQ(expected_set, actual_set);
+    // checks that each value is assigned the correct variable
+    EXPECT_EQ(patomic_opcats_BIN, expected_bin);
+    EXPECT_EQ(patomic_opcats_ARI, expected_ari);
+    EXPECT_EQ(patomic_opcats_IMPLICIT, expected_implicit);
+    EXPECT_EQ(patomic_opcats_EXPLICIT, expected_explicit);
+    EXPECT_EQ(patomic_opcats_TRANSACTION, expected_transaction);
+    // can't check set sizes in case two opcats have the same value
+    EXPECT_EQ(expected_vec.size(), combined_opcats.size());
+}
+
+/// @brief Each "opcats" opcats consist only of known "opcat" opcat bits.
+TEST_F(BtApiFeatureCheckAnyAll, all_opcats_only_contain_known_opcat_bits)
+{
+    // setup
+    for (unsigned int opcats : test::make_opcats_all_combined())
+    {
+        for (const unsigned int opcat : test::make_opcats_all_solo())
+        {
+            opcats &= ~opcat;
+        }
+
+        // test
+        EXPECT_EQ(0, opcats);
+    }
+}
+
+/// @brief The values of patomic_opcats_IMPLICIT and patomic_opcats_EXPLICIT
+///        are the same.
+TEST_F(BtApiFeatureCheckAnyAll, opcats_implicit_eq_explicit)
+{
+    // test
+    EXPECT_EQ(patomic_opcats_IMPLICIT, patomic_opcats_EXPLICIT);
+}
+
+/// @brief The bits set in patomic_opcats_IMPLICIT and patomic_opcats_EXPLICIT
+///        are all set in patomic_opcats_TRANSACTION.
+TEST_F(BtApiFeatureCheckAnyAll, opcats_implicit_explicit_subset_of_transaction)
+{
+    // setup
+    constexpr auto masked_implicit =
+        patomic_opcats_IMPLICIT & patomic_opcats_TRANSACTION;
+    constexpr auto masked_explicit =
+        patomic_opcats_EXPLICIT & patomic_opcats_TRANSACTION;
+
+    // test
+    EXPECT_EQ(masked_implicit, patomic_opcats_IMPLICIT);
+    EXPECT_EQ(masked_explicit, patomic_opcats_EXPLICIT);
+}
+
+/// @brief Calling check_any with zero opcat bits returns zero.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_any_zero_bits_returns_zero)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+
+    // test
+    EXPECT_EQ(0u, TTestHelper::check_any(ops, 0u));
+}
+
+/// @brief Calling check_all with zero opcat bits returns zero.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_all_zero_bits_returns_zero)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+
+    // test
+    EXPECT_EQ(0u, TTestHelper::check_all(ops, 0u));
+}
+
+/// @brief Calling check_any with invalid opcat bits does not unset the invalid
+///        bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_any_ignores_invalid_bits)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    constexpr unsigned int invalid_opcats = ~TestFixture::OpsTypes::full_opcat;
+    const std::bitset<UINT_BIT_WIDTH> expected_result = invalid_opcats;
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_any(ops, invalid_opcats);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_all with invalid opcat bits does not unset the invalid
+///        bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_all_ignores_invalid_bits)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    constexpr unsigned int invalid_opcats = ~TestFixture::OpsTypes::full_opcat;
+    const std::bitset<UINT_BIT_WIDTH> expected_result = invalid_opcats;
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_all(ops, invalid_opcats);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_any with all pointers set in patomic_ops*_t unsets
+///        exactly the bits in patomic_{IMPLICIT, EXPLICIT, TRANSACTION}.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_any_full_bits_match_excepted)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    constexpr unsigned int input_opcats = ~0;
+    const unsigned set_opcats = TestFixture::OpsTypes::full_opcat;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_any(ops, input_opcats);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_all with all pointers set in patomic_ops*_t unsets
+///        exactly the bits in patomic_{IMPLICIT, EXPLICIT, TRANSACTION}.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_all_full_bits_match_expected)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    constexpr unsigned int input_opcats = ~0;
+    const unsigned set_opcats = TestFixture::OpsTypes::full_opcat;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_all(ops, input_opcats);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_any with all combinations of LDST function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_any_ldst_bits_match_expected)
+{
+    // setup
+    for (const auto& ldst : test::make_ops_ldst_combinations<TestFixture::domain>())
+    {
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = ldst.any ? patomic_opcat_LDST : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_any(ldst.ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_all with all combinations of LDST function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_all_ldst_bits_match_expected)
+{
+    // setup
+    for (const auto& ldst : test::make_ops_ldst_combinations<TestFixture::domain>())
+    {
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = ldst.all ? patomic_opcat_LDST : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_all(ldst.ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_any with all combinations of XCHG function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_any_xchg_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& xchg : test::make_ops_xchg_combinations<TestFixture::domain>())
+    {
+        ops.xchg_ops = xchg.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = xchg.any ? patomic_opcat_XCHG : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_any(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_all with all combinations of XCHG function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_all_xchg_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& xchg : test::make_ops_xchg_combinations<TestFixture::domain>())
+    {
+        ops.xchg_ops = xchg.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = xchg.all ? patomic_opcat_XCHG : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_all(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_any with all combinations of BIT function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_any_bitwise_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& bitwise : test::make_ops_bitwise_combinations<TestFixture::domain>())
+    {
+        ops.bitwise_ops = bitwise.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = bitwise.any ? patomic_opcat_BIT : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_any(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_all with all combinations of BIT function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_all_bitwise_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& bitwise : test::make_ops_bitwise_combinations<TestFixture::domain>())
+    {
+        ops.bitwise_ops = bitwise.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = bitwise.all ? patomic_opcat_BIT : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_all(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_any with all combinations of BIN(_V/F) function
+///        pointers set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_any_binary_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& binary : test::make_ops_binary_combinations<TestFixture::domain>())
+    {
+        ops.binary_ops = binary.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats =
+            (binary.any_void ? patomic_opcat_BIN_V : 0) |
+            (binary.any_fetch ? patomic_opcat_BIN_F : 0);
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_any(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_all with all combinations of BIN(_V/F) function
+///        pointers set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_all_binary_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& binary : test::make_ops_binary_combinations<TestFixture::domain>())
+    {
+        ops.binary_ops = binary.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats =
+            (binary.all_void ? patomic_opcat_BIN_V : 0) |
+            (binary.all_fetch ? patomic_opcat_BIN_F : 0);
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_all(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_any with all combinations of ARI(_V/F) function
+///        pointers set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_any_arithmetic_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& arithmetic : test::make_ops_arithmetic_combinations<TestFixture::domain>())
+    {
+        ops.arithmetic_ops = arithmetic.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats =
+            (arithmetic.any_void ? patomic_opcat_ARI_V : 0) |
+            (arithmetic.any_fetch ? patomic_opcat_ARI_F : 0);
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_any(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_all with all combinations of ARI(_V/F) function
+///        pointers set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckAnyAllT, check_all_arithmetic_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& arithmetic : test::make_ops_arithmetic_combinations<TestFixture::domain>())
+    {
+        ops.arithmetic_ops = arithmetic.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats =
+            (arithmetic.all_void ? patomic_opcat_ARI_V : 0) |
+            (arithmetic.all_fetch ? patomic_opcat_ARI_F : 0);
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_all(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_any with all combinations of TSPEC function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckAnyAll, check_any_special_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& special : test::make_ops_special_combinations_transaction())
+    {
+        ops.special_ops = special.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = special.any ? patomic_opcat_TSPEC : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_any(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_all with all combinations of TSPEC function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckAnyAll, check_all_special_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& special : test::make_ops_special_combinations_transaction())
+    {
+        ops.special_ops = special.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = special.all ? patomic_opcat_TSPEC : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_all(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_any with all combinations of TFLAG function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckAnyAll, check_any_flag_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& flag : test::make_ops_flag_combinations_transaction())
+    {
+        ops.flag_ops = flag.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = flag.any ? patomic_opcat_TFLAG : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_any(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_all with all combinations of TFLAG function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckAnyAll, check_all_flag_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& flag : test::make_ops_flag_combinations_transaction())
+    {
+        ops.flag_ops = flag.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = flag.all ? patomic_opcat_TFLAG : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_all(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_any with all combinations of TRAW function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckAnyAll, check_any_raw_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& raw : test::make_ops_raw_combinations_transaction())
+    {
+        ops.raw_ops = raw.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = raw.any ? patomic_opcat_TRAW : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_any(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_all with all combinations of TRAW function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckAnyAll, check_all_raw_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& raw : test::make_ops_raw_combinations_transaction())
+    {
+        ops.raw_ops = raw.ops;
+        constexpr unsigned int input_opcats = ~0;
+        const unsigned int set_opcats = raw.all ? patomic_opcat_TRAW : 0;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opcats);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_all(ops, input_opcats);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
diff --git a/test/kind/bt/api/feature_check_leaf.cpp b/test/kind/bt/api/feature_check_leaf.cpp
new file mode 100644
index 000000000..dffe0eac8
--- /dev/null
+++ b/test/kind/bt/api/feature_check_leaf.cpp
@@ -0,0 +1,652 @@
+#include <patomic/api/feature_check.h>
+
+#include <test/common/death.hpp>
+#include <test/common/make_ops.hpp>
+#include <test/common/math.hpp>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <bitset>
+#include <limits>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+
+/// @brief Test fixture.
+class BtApiFeatureCheckLeaf : public testing::Test
+{};
+
+/// @brief Templated test fixture.
+template <class T>
+class BtApiFeatureCheckLeafT : public testing::Test
+{
+public:
+    static constexpr test::ops_domain domain = T::value;
+    using OpsTypes = test::ops_types<domain>;
+};
+
+/// @brief Templated test fixture for death tests.
+template <class T>
+class BtApiFeatureCheckLeafT_DeathTest : public testing::Test
+{
+public:
+    static constexpr test::ops_domain domain = T::value;
+    using OpsTypes = test::ops_types<domain>;
+};
+
+using BtApiFeatureCheckLeafT_Types = ::testing::Types<
+    std::integral_constant<test::ops_domain, test::ops_domain::IMPLICIT>,
+    std::integral_constant<test::ops_domain, test::ops_domain::EXPLICIT>,
+    std::integral_constant<test::ops_domain, test::ops_domain::TRANSACTION>
+>;
+
+namespace
+{
+    
+/// @brief Helper constant.
+constexpr auto UINT_BIT_WIDTH = std::numeric_limits<unsigned int>::digits;
+
+/// @brief Helper type for templated test fixture.
+class TTestHelper
+{
+public:
+    template <class T>
+    static std::string
+    GetName(int)
+    {
+        auto name = test::to_string(T::value);
+        std::transform(name.begin(), name.end(), name.begin(),
+                       [](unsigned char c) noexcept -> char {
+           return static_cast<char>(std::tolower(c));
+       });
+        return name;
+    }
+
+    static unsigned int
+    check_leaf(const patomic_ops_t& ops, patomic_opcat_t opcat,
+               unsigned int opkinds) noexcept
+    {
+        return patomic_feature_check_leaf(&ops, opcat, opkinds);
+    }
+
+    static unsigned int
+    check_leaf(const patomic_ops_explicit_t& ops, patomic_opcat_t opcat,
+               unsigned int opkinds) noexcept
+    {
+        return patomic_feature_check_leaf_explicit(&ops, opcat, opkinds);
+    }
+
+    static unsigned int
+    check_leaf(const patomic_ops_transaction_t& ops, patomic_opcat_t opcat,
+               unsigned int opkinds) noexcept
+    {
+        return patomic_feature_check_leaf_transaction(&ops, opcat, opkinds);
+    }
+};
+
+}  // namespace
+
+TYPED_TEST_SUITE(
+    BtApiFeatureCheckLeafT,
+    BtApiFeatureCheckLeafT_Types,
+    TTestHelper
+);
+
+TYPED_TEST_SUITE(
+    BtApiFeatureCheckLeafT_DeathTest,
+    BtApiFeatureCheckLeafT_Types,
+    TTestHelper
+);
+
+
+/// @brief All "opkind" opkinds have exactly one bit set, except for
+///        patomic_opkind_NONE which has zero bits set.
+TEST_F(BtApiFeatureCheckLeaf, all_opkind_have_zero_or_one_bits_set)
+{
+    // test
+    for (const patomic_opkind_t opkind : test::make_opkinds_all_solo())
+    {
+        if (opkind == patomic_opkind_NONE)
+        {
+            EXPECT_EQ(0, opkind);
+        }
+        else
+        {
+            EXPECT_TRUE(test::is_positive_pow2(static_cast<int>(opkind)));
+        }
+    }
+}
+
+/// @brief All "opkinds" opkinds have multiple bits set.
+TEST_F(BtApiFeatureCheckLeaf, all_opkinds_have_multiple_bits_set)
+{
+    // test
+    for (const int opkinds : test::make_opkinds_all_combined())
+    {
+        EXPECT_GT(opkinds, 0);
+        EXPECT_FALSE(test::is_positive_pow2(opkinds));
+    }
+}
+
+/// @brief All "opkinds" opkinds have expected combination of bits.
+TEST_F(BtApiFeatureCheckLeaf, all_opkinds_have_expected_bits)
+{
+    // setup
+    // values
+    constexpr auto expected_ldst = patomic_opkind_LOAD | patomic_opkind_STORE;
+    constexpr auto expected_xchg =
+        patomic_opkind_EXCHANGE     |
+        patomic_opkind_CMPXCHG_WEAK |
+        patomic_opkind_CMPXCHG_STRONG;
+    constexpr auto expected_bit =
+        patomic_opkind_TEST       |
+        patomic_opkind_TEST_COMPL |
+        patomic_opkind_TEST_SET   |
+        patomic_opkind_TEST_RESET;
+    constexpr auto expected_bin =
+        patomic_opkind_OR  |
+        patomic_opkind_XOR |
+        patomic_opkind_AND |
+        patomic_opkind_NOT;
+    constexpr auto expected_ari =
+        patomic_opkind_ADD |
+        patomic_opkind_SUB |
+        patomic_opkind_INC |
+        patomic_opkind_DEC |
+        patomic_opkind_NEG;
+    constexpr auto expected_tspec =
+        patomic_opkind_DOUBLE_CMPXCHG |
+        patomic_opkind_MULTI_CMPXCHG  |
+        patomic_opkind_GENERIC        |
+        patomic_opkind_GENERIC_WFB;
+    constexpr auto expected_tflag =
+        patomic_opkind_TEST     |
+        patomic_opkind_TEST_SET |
+        patomic_opkind_CLEAR;
+    constexpr auto expected_traw =
+        patomic_opkind_TBEGIN  |
+        patomic_opkind_TABORT  |
+        patomic_opkind_TCOMMIT |
+        patomic_opkind_TTEST;
+    // sets of values
+    const std::vector<int> expected_vec {
+        expected_ldst,
+        expected_xchg,
+        expected_bit,
+        expected_bin,
+        expected_ari,
+        expected_tspec,
+        expected_tflag,
+        expected_traw
+    };
+    const auto actual_vec = test::make_opkinds_all_combined();
+    const std::set<int> expected_set {
+        expected_vec.begin(), expected_vec.end()
+    };
+    const std::set<int> actual_set {
+        actual_vec.begin(), actual_vec.end()
+    };
+
+    // test
+    // checks that all values are expected
+    EXPECT_EQ(expected_set, actual_set);
+    // checks that each value is assigned the correct variable
+    EXPECT_EQ(patomic_opkinds_LDST, expected_ldst);
+    EXPECT_EQ(patomic_opkinds_XCHG, expected_xchg);
+    EXPECT_EQ(patomic_opkinds_BIT, expected_bit);
+    EXPECT_EQ(patomic_opkinds_BIN, expected_bin);
+    EXPECT_EQ(patomic_opkinds_ARI, expected_ari);
+    EXPECT_EQ(patomic_opkinds_TSPEC, expected_tspec);
+    EXPECT_EQ(patomic_opkinds_TFLAG, expected_tflag);
+    EXPECT_EQ(patomic_opkinds_TRAW, expected_traw);
+    // can't check set sizes in case two opcats have the same value
+    EXPECT_EQ(expected_vec.size(), actual_vec.size());
+}
+
+/// @brief All "opkinds" opkinds consist only of known "opkind" opkind bits.
+TEST_F(BtApiFeatureCheckLeaf, all_opkinds_only_contain_known_opkind_bits)
+{
+    // setup
+    for (unsigned int opkinds : test::make_opkinds_all_combined())
+    {
+        for (const unsigned int opkind : test::make_opkinds_all_solo())
+        {
+            opkinds &= ~opkind;
+        }
+
+        // test
+        EXPECT_EQ(0, opkinds);
+    }
+}
+
+/// @brief Calling check_leaf with a non-zero opcat and zero opkind bits
+///        returns zero.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_zero_opkind_bits_returns_zero)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    constexpr auto non_zero_opcat = patomic_opcat_LDST;
+
+    // test
+    EXPECT_EQ(0u, TTestHelper::check_leaf(ops, non_zero_opcat, 0u));
+}
+
+/// @brief Calling check_leaf with invalid opkind bits for any valid opcat does
+///        not unset the invalid opkind bits.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_ignores_invalid_opkind_bits)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    unsigned int invalid_opkind = ~0u;
+    for (const unsigned int valid_opkind : test::make_opkinds_all_solo())
+    {
+        invalid_opkind &= ~valid_opkind;
+    }
+    const std::bitset<UINT_BIT_WIDTH> expected_result = invalid_opkind;
+
+    // test
+    for (const patomic_opcat_t opcat : test::make_opcats_all_solo())
+    {
+        if (opcat == 0)
+        {
+            continue;
+        }
+
+        ASSERT_TRUE(test::is_positive_pow2(static_cast<unsigned int>(opcat)));
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_leaf(ops, opcat, invalid_opkind);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_leaf with a valid opkind with an opcat which does not
+///        apply to the domain does not unset any opkind bits.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_unused_opcat_ignores_all_opkind_bits)
+{
+    // skip this test when running with ubsan because it invokes UB
+    // we cast a value to patomic_opcat_t which has no corresponding label
+#if PATOMIC_HAS_UBSAN
+    GTEST_SKIP() << "Cannot run this test case under ubsan because "
+                    "it invokes UB as part of the test setup";
+#endif
+
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    const auto opcat_vec = test::make_opcats_all_solo();
+    const std::set<unsigned int> opcat_set {
+        opcat_vec.begin(), opcat_vec.end()
+    };
+    unsigned int opcat = 1;
+    while (opcat != 0 && opcat_set.count(opcat) != 0)
+    {
+        opcat <<= 1;
+    }
+    constexpr unsigned int input_opkinds = ~0u;
+    const std::bitset<UINT_BIT_WIDTH> expected_result = input_opkinds;
+    
+    // test
+    if (opcat != 0)
+    {
+        ASSERT_TRUE(test::is_positive_pow2(opcat));
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_leaf(ops, static_cast<patomic_opcat_t>(opcat), input_opkinds);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_leaf with all LDST function pointers set in
+///        patomic_ops*_t unsets exactly the bits in patomic_opkinds_LDST.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_full_ldst_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    ops.fp_load = test::make_ops_all_nonnull<TestFixture::domain>().fp_load;
+    ops.fp_store = test::make_ops_all_nonnull<TestFixture::domain>().fp_store;
+    constexpr unsigned int input_opkinds = ~0u;
+    const unsigned int set_opkinds = patomic_opkinds_LDST;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opkinds);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_leaf(ops, patomic_opcat_LDST, input_opkinds);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_leaf with all combinations of LDST function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_ldst_bits_match_expected)
+{
+    // setup
+    for (const auto& ldst : test::make_ops_ldst_combinations<TestFixture::domain>())
+    {
+        constexpr unsigned int input_opkinds = ~0u;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(ldst.opkinds);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_leaf(ldst.ops, patomic_opcat_LDST, input_opkinds);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_leaf with all XCHG function pointers set in
+///        patomic_ops*_t unsets exactly the bits in patomic_opkinds_XCHG.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_full_xchg_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    ops.xchg_ops = test::make_ops_all_nonnull<TestFixture::domain>().xchg_ops;
+    constexpr unsigned int input_opkinds = ~0u;
+    const unsigned int set_opkinds = patomic_opkinds_XCHG;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opkinds);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_leaf(ops, patomic_opcat_XCHG, input_opkinds);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_leaf with all combinations of XCHG function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_xchg_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& xchg : test::make_ops_xchg_combinations<TestFixture::domain>())
+    {
+        ops.xchg_ops = xchg.ops;
+        constexpr unsigned int input_opkinds = ~0u;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(xchg.opkinds);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_leaf(ops, patomic_opcat_XCHG, input_opkinds);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_leaf with all BIT function pointers set in
+///        patomic_ops*_t unsets exactly the bits in patomic_opkinds_BIT.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_full_bitwise_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    ops.bitwise_ops = test::make_ops_all_nonnull<TestFixture::domain>().bitwise_ops;
+    constexpr unsigned int input_opkinds = ~0u;
+    const unsigned int set_opkinds = patomic_opkinds_BIT;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opkinds);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_leaf(ops, patomic_opcat_BIT, input_opkinds);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_leaf with all combinations of BIT function pointers
+///        set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_bitwise_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& bitwise : test::make_ops_bitwise_combinations<TestFixture::domain>())
+    {
+        ops.bitwise_ops = bitwise.ops;
+        constexpr unsigned int input_opkinds = ~0u;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(bitwise.opkinds);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_leaf(ops, patomic_opcat_BIT, input_opkinds);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_leaf with all BIN(_V/F) function pointers set in
+///        patomic_ops*_t unsets exactly the bits in patomic_opkinds_BIN.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_full_binary_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    ops.binary_ops = test::make_ops_all_nonnull<TestFixture::domain>().binary_ops;
+    constexpr unsigned int input_opkinds = ~0u;
+    const unsigned int set_opkinds = patomic_opkinds_BIN;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opkinds);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result_void =
+        TTestHelper::check_leaf(ops, patomic_opcat_BIN_V, input_opkinds);
+    const std::bitset<UINT_BIT_WIDTH> actual_result_fetch =
+        TTestHelper::check_leaf(ops, patomic_opcat_BIN_F, input_opkinds);
+    EXPECT_EQ(expected_result, actual_result_void);
+    EXPECT_EQ(expected_result, actual_result_fetch);
+}
+
+/// @brief Calling check_leaf with all combinations of BIN(_V/F) function
+///        pointers set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_binary_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& binary : test::make_ops_binary_combinations<TestFixture::domain>())
+    {
+        ops.binary_ops = binary.ops;
+        constexpr unsigned int input_opkinds = ~0u;
+        const auto expected_result_void = ~std::bitset<UINT_BIT_WIDTH>(binary.opkinds_void);
+        const auto expected_result_fetch = ~std::bitset<UINT_BIT_WIDTH>(binary.opkinds_fetch);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result_void =
+            TTestHelper::check_leaf(ops, patomic_opcat_BIN_V, input_opkinds);
+        const std::bitset<UINT_BIT_WIDTH> actual_result_fetch =
+            TTestHelper::check_leaf(ops, patomic_opcat_BIN_F, input_opkinds);
+        EXPECT_EQ(expected_result_void, actual_result_void);
+        EXPECT_EQ(expected_result_fetch, actual_result_fetch);
+    }
+}
+
+/// @brief Calling check_leaf with all ARI(_V/F) function pointers set in
+///        patomic_ops*_t unsets exactly the bits in patomic_opkinds_ARI.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_full_arithmetic_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    ops.arithmetic_ops = test::make_ops_all_nonnull<TestFixture::domain>().arithmetic_ops;
+    constexpr unsigned int input_opkinds = ~0u;
+    const unsigned int set_opkinds = patomic_opkinds_ARI;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opkinds);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result_void =
+        TTestHelper::check_leaf(ops, patomic_opcat_ARI_V, input_opkinds);
+    const std::bitset<UINT_BIT_WIDTH> actual_result_fetch =
+        TTestHelper::check_leaf(ops, patomic_opcat_ARI_F, input_opkinds);
+    EXPECT_EQ(expected_result, actual_result_void);
+    EXPECT_EQ(expected_result, actual_result_fetch);
+}
+
+/// @brief Calling check_leaf with all combinations of ARI(_V/F) function
+///        pointers set in patomic_ops*_t unsets the correct bits.
+TYPED_TEST(BtApiFeatureCheckLeafT, check_leaf_arithmetic_bits_match_expected)
+{
+    // setup
+    typename TestFixture::OpsTypes::ldst_t ops {};
+    for (const auto& arithmetic : test::make_ops_arithmetic_combinations<TestFixture::domain>())
+    {
+        ops.arithmetic_ops = arithmetic.ops;
+        constexpr unsigned int input_opkinds = ~0u;
+        const auto expected_result_void = ~std::bitset<UINT_BIT_WIDTH>(arithmetic.opkinds_void);
+        const auto expected_result_fetch = ~std::bitset<UINT_BIT_WIDTH>(arithmetic.opkinds_fetch);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result_void =
+            TTestHelper::check_leaf(ops, patomic_opcat_ARI_V, input_opkinds);
+        const std::bitset<UINT_BIT_WIDTH> actual_result_fetch =
+            TTestHelper::check_leaf(ops, patomic_opcat_ARI_F, input_opkinds);
+        EXPECT_EQ(expected_result_void, actual_result_void);
+        EXPECT_EQ(expected_result_fetch, actual_result_fetch);
+    }
+}
+
+/// @brief Calling check_leaf with all TSPEC function pointers set in
+///        patomic_ops_transaction_t unsets exactly the bits in
+///        patomic_opkinds_TSPEC.
+TEST_F(BtApiFeatureCheckLeaf, check_leaf_full_special_bits_match_expected)
+{
+    // setup
+    constexpr auto domain = test::ops_domain::TRANSACTION;
+    patomic_ops_transaction_t ops {};
+    ops.special_ops = test::make_ops_all_nonnull<domain>().special_ops;
+    constexpr unsigned int input_opkinds = ~0u;
+    const unsigned int set_opkinds = patomic_opkinds_TSPEC;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opkinds);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_leaf(ops, patomic_opcat_TSPEC, input_opkinds);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_leaf with all combinations of TSPEC function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckLeaf, check_leaf_special_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& special : test::make_ops_special_combinations_transaction())
+    {
+        ops.special_ops = special.ops;
+        constexpr unsigned int input_opkinds = ~0u;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(special.opkinds);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_leaf(ops, patomic_opcat_TSPEC, input_opkinds);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_leaf with all TFLAG function pointers set in
+///        patomic_ops_transaction_t unsets exactly the bits in
+///        patomic_opkinds_TFLAG.
+TEST_F(BtApiFeatureCheckLeaf, check_leaf_full_flag_bits_match_expected)
+{
+    // setup
+    constexpr auto domain = test::ops_domain::TRANSACTION;
+    patomic_ops_transaction_t ops {};
+    ops.flag_ops = test::make_ops_all_nonnull<domain>().flag_ops;
+    constexpr unsigned int input_opkinds = ~0u;
+    const unsigned int set_opkinds = patomic_opkinds_TFLAG;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opkinds);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_leaf(ops, patomic_opcat_TFLAG, input_opkinds);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_leaf with all combinations of TFLAG function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckLeaf, check_leaf_flag_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& flag : test::make_ops_flag_combinations_transaction())
+    {
+        ops.flag_ops = flag.ops;
+        constexpr unsigned int input_opkinds = ~0u;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(flag.opkinds);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_leaf(ops, patomic_opcat_TFLAG, input_opkinds);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_leaf with all TRAW function pointers set in
+///        patomic_ops_transaction_t unsets exactly the bits in
+///        patomic_opkinds_TRAW.
+TEST_F(BtApiFeatureCheckLeaf, check_leaf_full_raw_bits_match_expected)
+{
+    // setup
+    constexpr auto domain = test::ops_domain::TRANSACTION;
+    patomic_ops_transaction_t ops {};
+    ops.raw_ops = test::make_ops_all_nonnull<domain>().raw_ops;
+    constexpr unsigned int input_opkinds = ~0u;
+    const unsigned int set_opkinds = patomic_opkinds_TRAW;
+    const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(set_opkinds);
+
+    // test
+    const std::bitset<UINT_BIT_WIDTH> actual_result =
+        TTestHelper::check_leaf(ops, patomic_opcat_TRAW, input_opkinds);
+    EXPECT_EQ(expected_result, actual_result);
+}
+
+/// @brief Calling check_leaf with all combinations of TRAW function pointers
+///        set in patomic_ops_transaction_t unsets the correct bits.
+TEST_F(BtApiFeatureCheckLeaf, check_leaf_raw_bits_match_expected)
+{
+    // setup
+    patomic_ops_transaction_t ops {};
+    for (const auto& raw : test::make_ops_raw_combinations_transaction())
+    {
+        ops.raw_ops = raw.ops;
+        constexpr unsigned int input_opkinds = ~0u;
+        const auto expected_result = ~std::bitset<UINT_BIT_WIDTH>(raw.opkinds);
+
+        // test
+        const std::bitset<UINT_BIT_WIDTH> actual_result =
+            TTestHelper::check_leaf(ops, patomic_opcat_TRAW, input_opkinds);
+        EXPECT_EQ(expected_result, actual_result);
+    }
+}
+
+/// @brief Calling check_leaf with an opcat value which has no bits set is
+///        fatally asserted.
+TYPED_TEST(BtApiFeatureCheckLeafT_DeathTest, check_leaf_asserts_on_zero_bit_opcat)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    constexpr auto zero_bit_opcat = static_cast<patomic_opcat_t>(0);
+
+    // test
+    EXPECT_EQ(0, zero_bit_opcat);
+    EXPECT_EXIT(
+        TTestHelper::check_leaf(ops, zero_bit_opcat, 0u),
+        test::KilledByAbort(),
+        ".*"
+    );
+}
+
+/// @brief Calling check_leaf with an opcat value which has more than one bit
+///        set is fatally asserted.
+TYPED_TEST(BtApiFeatureCheckLeafT_DeathTest, check_leaf_asserts_on_multi_bit_opcat)
+{
+    // setup
+    const auto ops = test::make_ops_all_nonnull<TestFixture::domain>();
+    constexpr auto multi_valid_bit_opcat = TestFixture::OpsTypes::full_opcat;
+    constexpr auto multi_invalid_bit_opcat = ~multi_valid_bit_opcat;
+    constexpr auto multi_mixed_bit_opcat =
+        multi_valid_bit_opcat | multi_invalid_bit_opcat;
+    constexpr unsigned int multi_opcats[] {
+        multi_valid_bit_opcat,
+        multi_invalid_bit_opcat,
+        multi_mixed_bit_opcat
+    };
+
+    // test
+    for (const unsigned int multi_opcat : multi_opcats)
+    {
+        EXPECT_NE(0, multi_opcat);
+        EXPECT_FALSE(test::is_positive_pow2(multi_opcat));
+        EXPECT_EXIT(
+            TTestHelper::check_leaf(
+                ops, static_cast<patomic_opcat_t>(multi_opcat), 0u),
+            test::KilledByAbort(),
+            ".*"
+        );
+    }
+}
diff --git a/test/kind/bt/api/ids.cpp b/test/kind/bt/api/ids.cpp
new file mode 100644
index 000000000..b20510e57
--- /dev/null
+++ b/test/kind/bt/api/ids.cpp
@@ -0,0 +1,287 @@
+#include <patomic/api/ids.h>
+
+#include <test/common/death.hpp>
+#include <test/common/math.hpp>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <map>
+#include <set>
+#include <vector>
+
+
+/// @brief Test fixture.
+class BtApiIds : public testing::Test
+{
+public:
+    const std::map<patomic_id_t, patomic_kind_t> impls_id_to_kind {
+        { patomic_id_NULL, patomic_kind_UNKN },
+        { patomic_id_STDC, patomic_kind_BLTN}
+    };
+
+    const std::vector<patomic_id_t> ids {
+        patomic_id_NULL,
+        patomic_id_STDC
+    };
+
+    const std::vector<patomic_kind_t> kinds {
+        patomic_kind_UNKN,
+        patomic_kind_DYN,
+        patomic_kind_OS,
+        patomic_kind_LIB,
+        patomic_kind_BLTN,
+        patomic_kind_ASM
+    };
+};
+
+/// @brief Test fixture for death tests.
+class BtApiIds_DeathTest : public testing::Test
+{};
+
+
+/// @brief All ids are unique.
+TEST_F(BtApiIds, all_ids_are_unique)
+{
+    // setup
+    const std::set<patomic_id_t> ids_set { ids.begin(), ids.end() };
+
+    // test
+    EXPECT_EQ(ids.size(), impls_id_to_kind.size());
+    EXPECT_EQ(ids.size(), ids_set.size());
+}
+
+/// @brief All kinds are unique.
+TEST_F(BtApiIds, all_kinds_are_unique)
+{
+    // setup
+    const std::set<patomic_kind_t> kinds_set { kinds.begin(), kinds.end() };
+
+    // test
+    EXPECT_EQ(kinds.size(), kinds_set.size());
+}
+
+/// @brief All ids have exactly zero or one bits set (except for ALL).
+TEST_F(BtApiIds, all_ids_have_zero_or_one_bits_set)
+{
+    // setup
+    for (const auto impl_it : impls_id_to_kind)
+    {
+        const unsigned long id = impl_it.first;
+
+        // test
+        if (id == patomic_id_NULL)
+        {
+            EXPECT_EQ(0, id);
+        }
+        else
+        {
+            EXPECT_TRUE(test::is_positive_pow2(id));
+        }
+    }
+}
+
+/// @brief All kinds have exactly zero or one bits set (except for ALL).
+TEST_F(BtApiIds, all_kinds_have_zero_or_one_bits_set)
+{
+    // test
+    for (const patomic_kind_t kind : kinds)
+    {
+        if (kind == patomic_kind_UNKN)
+        {
+            EXPECT_EQ(0, kind);
+        }
+        else
+        {
+            EXPECT_TRUE(test::is_positive_pow2(static_cast<int>(kind)));
+        }
+    }
+}
+
+/// @brief Each id's kind has exactly zero or one bits set.
+TEST_F(BtApiIds, each_ids_kind_has_zero_or_one_bits_set)
+{
+    // setup
+    for (const auto impl_it : impls_id_to_kind)
+    {
+        const patomic_kind_t kind = impl_it.second;
+
+        // test
+        if (kind == patomic_kind_UNKN)
+        {
+            EXPECT_EQ(0, kind);
+        }
+    }
+}
+
+/// @brief The value of patomic_kinds_ALL is exactly the value of all the other
+///        kinds combined.
+TEST_F(BtApiIds, all_kinds_matches_exactly_each_kind)
+{
+    // setup
+    int all_kinds {};
+    for (const patomic_kind_t kind : kinds)
+    {
+        all_kinds |= kind;
+    }
+
+    // test
+    EXPECT_EQ(patomic_kinds_ALL, all_kinds);
+}
+
+/// @brief The value of patomic_ids_ALL is at least the value of all the other
+///        ids combined.
+TEST_F(BtApiIds, all_ids_matches_at_least_each_id)
+{
+    // setup
+    unsigned long all_ids {};
+    for (const auto impl_it : impls_id_to_kind)
+    {
+        all_ids |= impl_it.first;
+    }
+
+    // test
+    EXPECT_EQ(patomic_ids_ALL & all_ids, all_ids);
+}
+
+/// @brief All expected ids match a bit set in patomic_kinds_ALL.
+TEST_F(BtApiIds, all_expected_ids_are_provided)
+{
+    // setup
+    const unsigned long all_ids = patomic_get_ids(patomic_kinds_ALL);
+    for (const auto impl_it : impls_id_to_kind)
+    {
+        const unsigned long id = impl_it.first;
+
+        // test
+        EXPECT_EQ(all_ids&  id, id);
+    }
+}
+
+/// @brief There are no bits set in patomic_kinds_ALL which do not match an expected id.
+TEST_F(BtApiIds, no_unexpected_ids_are_provided)
+{
+    // setup
+    unsigned long all_ids = patomic_get_ids(patomic_kinds_ALL);
+    for (const auto impl_it : impls_id_to_kind)
+    {
+        const unsigned long id = impl_it.first;
+        all_ids ^= id;
+    }
+
+    // test
+    EXPECT_EQ(0, all_ids);
+}
+
+/// @brief All combinations of valid kinds yields the expected combinations of
+///        ids.
+TEST_F(BtApiIds, get_ids_gives_correct_ids_for_all_kind_combinations)
+{
+    // calculate all kind combinations
+    std::vector<int> all_kind_combos;
+    all_kind_combos.resize(1ull << kinds.size());
+    for (std::size_t i = 0; i < all_kind_combos.size(); ++i)
+    {
+        int kind_combo = 0;
+        for (std::size_t j = 0; j < kinds.size(); ++j)
+        {
+            if (i & (1ull << j))
+            {
+                kind_combo |= kinds[j];
+            }
+        }
+        all_kind_combos[i] = kind_combo;
+    }
+
+    // skip empty set
+    all_kind_combos.erase(all_kind_combos.begin());
+
+
+    // calculate resulting ids
+    std::vector<unsigned long> all_id_results;
+    for (const int kind_combo: all_kind_combos)
+    {
+        unsigned long id_combo = 0;
+        for (const auto& impl_it : impls_id_to_kind)
+        {
+            if (kind_combo&  impl_it.second)
+            {
+                id_combo |= impl_it.first;
+            }
+        }
+        all_id_results.push_back(id_combo);
+    }
+
+    // test
+    ASSERT_EQ(all_kind_combos.size(), all_id_results.size());
+    for (std::size_t i = 0; i < all_kind_combos.size(); ++i)
+    {
+        const int kinds = all_kind_combos[i];
+        const unsigned long ids = all_id_results[i];
+        EXPECT_EQ(ids, patomic_get_ids(kinds));
+    }
+}
+
+/// @brief Invalid kinds are ignored and if no valid kinds are provided, the
+///        NULL implementation id is returned.
+TEST_F(BtApiIds, get_ids_ignores_invalid_kinds)
+{
+    // setup
+    ASSERT_FALSE(kinds.empty());
+    const auto invalid_kind = kinds.back() << 1;
+    EXPECT_EQ(kinds.end(), std::find(kinds.begin(), kinds.end(), invalid_kind));
+    const auto stdc_kind = impls_id_to_kind.at(patomic_id_STDC);
+
+    // test
+    EXPECT_EQ(patomic_id_NULL, patomic_get_ids(invalid_kind));
+    EXPECT_EQ(patomic_id_STDC, patomic_get_ids(invalid_kind | stdc_kind));
+}
+
+/// @brief The corresponding kind is returned for each valid id.
+TEST_F(BtApiIds, get_kind_gives_correct_kind_for_all_ids)
+{
+    // setup
+    for (const auto impl_it : impls_id_to_kind)
+    {
+        const unsigned long id = impl_it.first;
+        const patomic_kind_t kind = impl_it.second;
+
+        // test
+        EXPECT_EQ(kind, patomic_get_kind(id));
+    }
+}
+
+/// @brief The UNKN kind is returned for any invalid id.
+TEST_F(BtApiIds, get_kind_gives_kind_unkn_for_invalid_id)
+{
+    // setup
+    unsigned long id = 1;
+    while (id != 0 && impls_id_to_kind.count(id) != 0)
+    {
+        id <<= 1;
+    }
+
+    // test
+    if (id != 0)
+    {
+        ASSERT_TRUE(test::is_positive_pow2(id));
+        EXPECT_EQ(patomic_get_kind(id), patomic_kind_UNKN);
+    }
+}
+
+
+/// @brief Passing multiple ids (multiple bits set) is fatally asserted.
+TEST_F(BtApiIds_DeathTest, get_kinds_asserts_on_multiple_ids)
+{
+    // setup
+    constexpr unsigned long multiple_bits_set = 3;
+
+    // test
+    EXPECT_GT(multiple_bits_set, 0);
+    EXPECT_FALSE(test::is_positive_pow2(multiple_bits_set));
+    EXPECT_EXIT(
+        patomic_get_kind(multiple_bits_set),
+        test::KilledByAbort(),
+        ".*"
+    );
+}
diff --git a/test/kind/bt/api/memory_order.cpp b/test/kind/bt/api/memory_order.cpp
new file mode 100644
index 000000000..3196dbb18
--- /dev/null
+++ b/test/kind/bt/api/memory_order.cpp
@@ -0,0 +1,250 @@
+#include <patomic/api/memory_order.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <limits>
+#include <vector>
+
+
+/// @brief Test fixture.
+class BtApiMemoryOrder : public testing::Test
+{
+public:
+    const std::vector<int> invalid_orders {
+        -1, -10, 10,
+        std::numeric_limits<int>::min(),
+        std::numeric_limits<int>::max(),
+    };
+
+    const std::vector<patomic_memory_order_t> valid_orders {
+        patomic_RELAXED,
+        patomic_CONSUME,
+        patomic_ACQUIRE,
+        patomic_RELEASE,
+        patomic_ACQ_REL,
+        patomic_SEQ_CST
+    };
+
+    const std::vector<patomic_memory_order_t> load_orders {
+        patomic_RELAXED,
+        patomic_CONSUME,
+        patomic_ACQUIRE,
+        patomic_SEQ_CST
+    };
+
+    const std::vector<patomic_memory_order_t> non_load_orders {
+        patomic_RELEASE,
+        patomic_ACQ_REL
+    };
+
+    const std::vector<patomic_memory_order_t> store_orders {
+        patomic_RELAXED,
+        patomic_RELEASE,
+        patomic_ACQ_REL,
+        patomic_SEQ_CST
+    };
+
+    const std::vector<patomic_memory_order_t> non_store_orders {
+        patomic_CONSUME,
+        patomic_ACQUIRE
+    };
+
+    static bool
+    contains(const std::vector<patomic_memory_order_t>& orders,
+             const patomic_memory_order_t order) noexcept
+    {
+        const auto it = std::find(std::begin(orders), std::end(orders), order);
+        return it != std::end(orders);
+    }
+};
+
+
+/// @brief Valid orders are allowed by patomic_is_valid_order.
+TEST_F(BtApiMemoryOrder, is_valid_order_allows_all_valid_orders)
+{
+    // test orders on function and macro
+    for (const patomic_memory_order_t order : this->valid_orders)
+    {
+        EXPECT_EQ(1, patomic_is_valid_order(order));
+        EXPECT_EQ(1, PATOMIC_IS_VALID_ORDER(order));
+    }
+}
+
+/// @brief Invalid orders are rejected by patomic_is_valid_order.
+TEST_F(BtApiMemoryOrder, is_valid_order_rejects_invalid_orders)
+{
+    // test orders on function and macro
+    for (const int order : this->invalid_orders)
+    {
+        EXPECT_EQ(0, patomic_is_valid_order(order));
+        EXPECT_EQ(0, PATOMIC_IS_VALID_ORDER(order));
+    }
+}
+
+/// @brief Valid store orders are allowed by patomic_is_valid_store_order.
+TEST_F(BtApiMemoryOrder, is_valid_store_order_allows_all_valid_store_orders)
+{
+    // test orders on function and macro
+    for (const patomic_memory_order_t order : this->store_orders)
+    {
+        EXPECT_EQ(1, patomic_is_valid_store_order(order));
+        EXPECT_EQ(1, PATOMIC_IS_VALID_STORE_ORDER(order));
+    }
+}
+
+/// @brief Invalid store orders are rejected by patomic_is_valid_store_order.
+TEST_F(BtApiMemoryOrder, is_valid_store_order_rejects_invalid_store_orders)
+{
+    // orders to test
+    std::vector<int> bad_orders = this->invalid_orders;
+    for (const int order : this->non_store_orders)
+    {
+        bad_orders.push_back(order);
+    }
+
+    // test orders on function and macro
+    for (const int order : bad_orders)
+    {
+        EXPECT_EQ(0, patomic_is_valid_store_order(order));
+        EXPECT_EQ(0, PATOMIC_IS_VALID_STORE_ORDER(order));
+    }
+}
+
+/// @brief Valid load orders are allowed by patomic_is_valid_load_order.
+TEST_F(BtApiMemoryOrder, is_valid_load_order_allows_all_valid_load_orders)
+{
+    // test orders on function and macro
+    for (const patomic_memory_order_t order : this->load_orders)
+    {
+        EXPECT_EQ(1, patomic_is_valid_load_order(order));
+        EXPECT_EQ(1, PATOMIC_IS_VALID_LOAD_ORDER(order));
+    }
+}
+
+/// @brief Invalid load orders are rejected by patomic_is_valid_load_order.
+TEST_F(BtApiMemoryOrder, is_valid_load_order_rejects_invalid_load_orders)
+{
+    // orders to test
+    std::vector<int> bad_orders = this->invalid_orders;
+    for (const int order : this->non_load_orders)
+    {
+        bad_orders.push_back(order);
+    }
+
+    // test orders on function and macro
+    for (const int order : bad_orders)
+    {
+        EXPECT_EQ(0, patomic_is_valid_load_order(order));
+        EXPECT_EQ(0, PATOMIC_IS_VALID_LOAD_ORDER(order));
+    }
+}
+
+/// @brief Valid succ-fail order pairs are allowed by patomic_is_valid_fail_order.
+TEST_F(BtApiMemoryOrder, is_valid_fail_order_allows_all_valid_pairs)
+{
+    // go through all combinations of orders
+    for (const patomic_memory_order_t succ : this->valid_orders)
+    {
+        for (const patomic_memory_order_t fail : this->valid_orders)
+        {
+            // skip non-load fail orders or fail > succ
+            if (fail > succ || contains(this->non_load_orders, fail))
+            {
+                continue;
+            }
+
+            // test orders on function and macro
+            EXPECT_EQ(1, patomic_is_valid_fail_order(succ, fail));
+            EXPECT_EQ(1, PATOMIC_IS_VALID_FAIL_ORDER(succ, fail));
+        }
+    }
+}
+
+/// @brief Succ order less than fail order is rejected by patomic_is_valid_fail_order.
+TEST_F(BtApiMemoryOrder, is_valid_fail_order_rejects_succ_lt_fail)
+{
+    // setup iterators
+    const auto begin = std::begin(this->valid_orders);
+    const auto end = std::end(this->valid_orders);
+
+    // ensure orders are sorted
+    ASSERT_TRUE(std::is_sorted(begin, end));
+
+    // go through all order pairs where one order is smaller than the other
+    for (auto fail_it = std::next(begin); fail_it < end; ++fail_it)
+    {
+        for (auto succ_it = begin; succ_it < fail_it; ++succ_it)
+        {
+            // test orders on function and macro
+            EXPECT_EQ(0, patomic_is_valid_fail_order(*succ_it, *fail_it));
+            EXPECT_EQ(0, PATOMIC_IS_VALID_FAIL_ORDER(*succ_it, *fail_it));
+        }
+    }
+}
+
+/// @brief Invalid succ order is rejected by patomic_is_valid_fail_order.
+TEST_F(BtApiMemoryOrder, is_valid_fail_order_rejects_invalid_succ_order)
+{
+    // go through all combinations of orders
+    for (const int succ : this->invalid_orders)
+    {
+        for (const patomic_memory_order_t fail : this->valid_orders)
+        {
+            // test orders on function and macro
+            EXPECT_EQ(0, patomic_is_valid_fail_order(succ, fail));
+            EXPECT_EQ(0, PATOMIC_IS_VALID_FAIL_ORDER(succ, fail));
+        }
+    }
+}
+
+/// @brief Invalid fail order is rejected by patomic_is_valid_fail_order.
+TEST_F(BtApiMemoryOrder, is_valid_fail_order_rejects_invalid_fail_order)
+{
+    // fail is also invalid if it's a non-load order
+    std::vector<int> bad_orders = this->invalid_orders;
+    for (const int order : this->non_load_orders)
+    {
+        bad_orders.push_back(order);
+    }
+
+    // go through all combinations of orders
+    for (const int fail : bad_orders)
+    {
+        for (const patomic_memory_order_t succ : this->valid_orders)
+        {
+            // test orders on function and macro
+            EXPECT_EQ(0, patomic_is_valid_fail_order(succ, fail));
+            EXPECT_EQ(0, PATOMIC_IS_VALID_FAIL_ORDER(succ, fail));
+        }
+    }
+}
+
+/// @brief Fail order is created from valid succ order by patomic_cmpxchg_fail_order.
+TEST_F(BtApiMemoryOrder, cmpxchg_fail_order_converts_valid_succ_order)
+{
+    // test load orders on function and macro, stay the same
+    for (const patomic_memory_order_t order : this->load_orders)
+    {
+        EXPECT_EQ(order, patomic_cmpxchg_fail_order(order));
+        EXPECT_EQ(order, PATOMIC_CMPXCHG_FAIL_ORDER(order));
+    }
+
+    // test non-load orders on function and macro, are converted
+    for (const patomic_memory_order_t order : this->non_load_orders)
+    {
+        EXPECT_EQ(patomic_ACQUIRE, patomic_cmpxchg_fail_order(order));
+        EXPECT_EQ(patomic_ACQUIRE, PATOMIC_CMPXCHG_FAIL_ORDER(order));
+    }
+}
+
+/// @brief Invalid succ order is returned directly by patomic_cmpxchg_fail_order.
+TEST_F(BtApiMemoryOrder, cmpxchg_fail_order_returns_invalid_succ_order)
+{
+    // test orders on function and macro
+    for (const int order : this->invalid_orders)
+    {
+        EXPECT_EQ(order, patomic_cmpxchg_fail_order(order));
+        EXPECT_EQ(order, PATOMIC_CMPXCHG_FAIL_ORDER(order));
+    }
+}
diff --git a/test/kind/bt/api/options.cpp b/test/kind/bt/api/options.cpp
new file mode 100644
index 000000000..05dc823a3
--- /dev/null
+++ b/test/kind/bt/api/options.cpp
@@ -0,0 +1,64 @@
+#include <patomic/api/options.h>
+
+#include <test/common/math.hpp>
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+
+/// @brief Test fixture.
+class BtApiOptions : public testing::Test
+{
+public:
+    const std::vector<patomic_option_t> solo_options {
+        patomic_option_NONE
+    };
+
+    const std::vector<patomic_option_t> combined_options {
+    };
+};
+
+
+/// @brief All "option" options have exactly one bit set, except for
+///        patomic_option_NONE which has zero bits set.
+TEST_F(BtApiOptions, all_option_have_zero_or_one_bits_set)
+{
+    // test
+    for (const patomic_option_t option : solo_options)
+    {
+        if (option == patomic_option_NONE)
+        {
+            EXPECT_EQ(0, option);
+        }
+        else
+        {
+            EXPECT_TRUE(test::is_positive_pow2(static_cast<int>(option)));
+        }
+    }
+}
+
+/// @brief All "options" options have multiple bits set.
+TEST_F(BtApiOptions, all_options_have_multiple_bits_set)
+{
+    // test
+    for (const int options : combined_options)
+    {
+        EXPECT_GT(options, 0);
+        EXPECT_FALSE(test::is_positive_pow2(options));
+    }
+}
+
+/// @brief All "options" options have expected combinations of bits.
+TEST_F(BtApiOptions, all_options_have_expected_bits0)
+{
+    // test (no combined options yet)
+    EXPECT_TRUE(combined_options.empty());
+}
+
+/// @brief All "options" options consist only of known "option" option bits.
+TEST_F(BtApiOptions, all_options_only_contain_known_option_bits)
+{
+    // test (no combined options yet)
+    EXPECT_TRUE(combined_options.empty());
+}
diff --git a/test/kind/bt/api/transaction.cpp b/test/kind/bt/api/transaction.cpp
new file mode 100644
index 000000000..7356a1e8d
--- /dev/null
+++ b/test/kind/bt/api/transaction.cpp
@@ -0,0 +1,70 @@
+#include <patomic/api/transaction.h>
+
+#include <gtest/gtest.h>
+
+#include <vector>
+
+
+/// @brief Test fixture.
+class BtApiTransaction : public testing::Test
+{};
+
+
+/// @brief Reason returned is 0 if status is not patomic_TABORT_EXPLICIT.
+TEST_F(BtApiTransaction, reason_is_zero_if_not_explicit_abort)
+{
+    // create other statuses
+    // new status kinds may be added, but they are guaranteed to fit in 8 bits
+    std::vector<unsigned long> statuses;
+    for (unsigned long kind = 0UL; kind < 0xffUL; ++kind)
+    {
+        // check we don't test explicit abort
+        if (kind == patomic_TABORT_EXPLICIT)
+        {
+            continue;
+        }
+
+        // combine status with a non-zero reason
+        statuses.push_back(kind | (0xffUL << 8));
+    }
+
+    // check that the reason for all of these is zero
+    for (const unsigned long status : statuses)
+    {
+        const unsigned char reason = patomic_transaction_abort_reason(status);
+        EXPECT_EQ(0U, reason);
+    }
+}
+
+/// @brief Reason is returned if status is patomic_TABORT_EXPLICIT.
+TEST_F(BtApiTransaction, reason_returned_if_explicit_abort)
+{
+    // create reasons
+    const std::vector<unsigned long> reasons {
+        0x55UL,
+        0xaaUL,
+        0x0fUL,
+        0xf0UL,
+        0xffUL,
+        0UL
+    };
+
+    // check that reason is returned from status
+    for (const unsigned long reason : reasons)
+    {
+        const unsigned long status = (reason << 8) | patomic_TABORT_EXPLICIT;
+        EXPECT_EQ(reason, patomic_transaction_abort_reason(status));
+    }
+}
+
+/// @brief Check that only first 8 bits of reason are provided.
+TEST_F(BtApiTransaction, reason_only_saves_first_8_bits)
+{
+    // create status with extended reason (more than 8 bits)
+    constexpr unsigned long extended_reason = 0xfffUL;
+    constexpr unsigned long status = (extended_reason << 8) | patomic_TABORT_EXPLICIT;
+
+    // check that reason is truncated
+    EXPECT_NE(extended_reason, patomic_transaction_abort_reason(status));
+    EXPECT_EQ(extended_reason & 0xFF, patomic_transaction_abort_reason(status));
+}
diff --git a/test/kind/bt/api/version.cpp b/test/kind/bt/api/version.cpp
new file mode 100644
index 000000000..ef4f4a22b
--- /dev/null
+++ b/test/kind/bt/api/version.cpp
@@ -0,0 +1,196 @@
+#include <patomic/api/version.h>
+
+#include <gtest/gtest.h>
+
+#include <regex>
+#include <string>
+
+
+/// @brief Test fixture.
+class BtApiVersion : public testing::Test
+{
+public:
+    const std::regex semver_regex{
+        R"(^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$)"
+    };
+};
+
+
+/// @brief Return value of patomic_version_string() compares equal to
+///        PATOMIC_VERSION_STRING.
+TEST_F(BtApiVersion, version_string_fn_cmp_eq_macro)
+{
+    // setup
+    constexpr auto macro = PATOMIC_VERSION_STRING;
+    const auto fnval = patomic_version_string();
+
+    // test
+    EXPECT_STREQ(macro, fnval);
+}
+
+/// @brief Return value of patomic_version_major() compares equal to
+///        PATOMIC_VERSION_MAJOR.
+TEST_F(BtApiVersion, version_major_fn_cmp_eq_macro)
+{
+    // setup
+    constexpr auto macro = PATOMIC_VERSION_MAJOR;
+    const auto fnval = patomic_version_major();
+
+    // test
+    EXPECT_EQ(macro, fnval);
+}
+
+/// @brief Return value of patomic_version_minor() compares equal to
+///        PATOMIC_VERSION_MINOR.
+TEST_F(BtApiVersion, version_minor_fn_cmp_eq_macro)
+{
+    // setup
+    constexpr auto macro = PATOMIC_VERSION_MINOR;
+    const auto fnval = patomic_version_minor();
+
+    // test
+    EXPECT_EQ(macro, fnval);
+}
+
+/// @brief Return value of patomic_version_patch() compares equal to
+///        PATOMIC_VERSION_PATCH.
+TEST_F(BtApiVersion, version_patch_fn_cmp_eq_macro)
+{
+    // setup
+    constexpr auto macro = PATOMIC_VERSION_PATCH;
+    const auto fnval = patomic_version_patch();
+
+    // test
+    EXPECT_EQ(macro, fnval);
+}
+
+/// @brief PATOMIC_VERSION_STRING matches SemVer regex.
+TEST_F(BtApiVersion, version_string_matches_semver_regex)
+{
+    // test
+    EXPECT_TRUE(std::regex_match(PATOMIC_VERSION_STRING, semver_regex));
+}
+
+/// @brief PATOMIC_VERSION_STRING major component compares equal to
+///        PATOMIC_VERSION_MAJOR.
+TEST_F(BtApiVersion, version_string_major_component_matches_version_major)
+{
+    // get major component as int
+    std::cmatch sub_matches;
+    ASSERT_TRUE(std::regex_match(
+        PATOMIC_VERSION_STRING, sub_matches, semver_regex));
+    const int major = std::stoi(sub_matches[1].str());
+
+    // test
+    EXPECT_EQ(major, PATOMIC_VERSION_MAJOR);
+}
+
+/// @brief PATOMIC_VERSION_STRING minor component compares equal to
+///        PATOMIC_VERSION_MINOR.
+TEST_F(BtApiVersion, version_string_minor_component_matches_version_minor)
+{
+    // get major component as int
+    std::cmatch sub_matches;
+    ASSERT_TRUE(std::regex_match(
+        PATOMIC_VERSION_STRING, sub_matches, semver_regex));
+    const int minor = std::stoi(sub_matches[2].str());
+
+    // test
+    EXPECT_EQ(minor, PATOMIC_VERSION_MINOR);
+}
+
+/// @brief PATOMIC_VERSION_STRING major component compares equal to
+///        PATOMIC_VERSION_MAJOR.
+TEST_F(BtApiVersion, version_string_patch_component_matches_version_patch)
+{
+    // get major component as int
+    std::cmatch sub_matches;
+    ASSERT_TRUE(std::regex_match(
+        PATOMIC_VERSION_STRING, sub_matches, semver_regex));
+    const int patch = std::stoi(sub_matches[3].str());
+
+    // test
+    EXPECT_EQ(patch, PATOMIC_VERSION_PATCH);
+}
+
+/// @brief Library is not compatible with major versions that do not compare
+///        equal to PATOMIC_VERSION_MAJOR.
+TEST_F(BtApiVersion, version_not_compatible_major_ne)
+{
+    // setup
+    constexpr int major = PATOMIC_VERSION_MAJOR;
+    for (const int bad_major : { major - 1, major + 1 })
+    {
+
+        // test
+        EXPECT_FALSE(PATOMIC_VERSION_COMPATIBLE_WITH(bad_major, 0, 0));
+        EXPECT_FALSE(patomic_version_compatible_with(bad_major, 0, 0));
+    }
+}
+
+/// @brief Library is not compatible with minor versions that compare greater
+///        than PATOMIC_VERSION_MINOR when the major version compares equal
+///        to PATOMIC_VERSION_MAJOR.
+TEST_F(BtApiVersion, version_not_compatible_major_eq_minor_gt)
+{
+    // setup
+    constexpr int major = PATOMIC_VERSION_MAJOR;
+    constexpr int minor_gt = PATOMIC_VERSION_MINOR + 1;
+
+    // test
+    EXPECT_FALSE(PATOMIC_VERSION_COMPATIBLE_WITH(major, minor_gt, 0));
+    EXPECT_FALSE(patomic_version_compatible_with(major, minor_gt, 0));
+}
+
+/// @brief Library is not compatible with patch versions that compare greater
+///        than PATOMIC_VERSION_PATCH when the major and minor versions compare
+///        equal to PATOMIC_VERSION_MAJOR and PATOMIC_VERSION_MINOR
+///        respectively.
+TEST_F(BtApiVersion, version_not_compatible_major_eq_minor_eq_patch_gt)
+{
+    // setup
+    constexpr int major = PATOMIC_VERSION_MAJOR;
+    constexpr int minor = PATOMIC_VERSION_MINOR;
+    constexpr int patch_gt = PATOMIC_VERSION_PATCH + 1;
+
+    // test
+    EXPECT_FALSE(PATOMIC_VERSION_COMPATIBLE_WITH(major, minor, patch_gt));
+    EXPECT_FALSE(patomic_version_compatible_with(major, minor, patch_gt));
+}
+
+/// @brief Library is compatible with minor versions that compare less than
+///        PATOMIC_VERSION_MINOR when the major version compares equal to
+///        PATOMIC_VERSION_MAJOR and the patch version has any value.
+TEST_F(BtApiVersion, version_compatible_major_eq_minor_lt_patch_any)
+{
+    // setup
+    constexpr int patch = PATOMIC_VERSION_PATCH;
+    for (const int patch_any : { -1, 0, patch - 1, patch, patch + 1 })
+    {
+        constexpr int major = PATOMIC_VERSION_MAJOR;
+        constexpr int minor_lt = PATOMIC_VERSION_MINOR - 1;
+
+        // test
+        EXPECT_TRUE(PATOMIC_VERSION_COMPATIBLE_WITH(major, minor_lt, patch_any));
+        EXPECT_TRUE(patomic_version_compatible_with(major, minor_lt, patch_any));
+    }
+}
+
+/// @brief Library is compatible with minor versions that compare equal to
+///        PATOMIC_VERSION_MINOR when the major version compares equal to
+///        PATOMIC_VERSION_MAJOR and the patch version compares less than or
+///        equal to PATOMIC_VERSION_PATCH.
+TEST_F(BtApiVersion, version_compatible_major_eq_minor_eq_patch_le)
+{
+    // setup
+    constexpr int patch = PATOMIC_VERSION_PATCH;
+    for (const int patch_le : { patch - 1, patch })
+    {
+        constexpr int major = PATOMIC_VERSION_MAJOR;
+        constexpr int minor = PATOMIC_VERSION_MINOR;
+
+        // test
+        EXPECT_TRUE(PATOMIC_VERSION_COMPATIBLE_WITH(major, minor, patch_le));
+        EXPECT_TRUE(patomic_version_compatible_with(major, minor, patch_le));
+    }
+}
diff --git a/test/kind/bt/example_add.cpp b/test/kind/bt/example_add.cpp
deleted file mode 100644
index 238875ab1..000000000
--- a/test/kind/bt/example_add.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#include <gtest/gtest.h>
-#include <patomic/patomic.h>
-
-
-TEST(BtSuite, Add)
-{
-    ASSERT_EQ(5, patomic_example_add(2, 3));
-}
diff --git a/test/kind/bt/example_sub.cpp b/test/kind/bt/example_sub.cpp
deleted file mode 100644
index 8de1bddc9..000000000
--- a/test/kind/bt/example_sub.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#include <gtest/gtest.h>
-#include <patomic/patomic.h>
-
-
-TEST(BtSuite, Sub)
-{
-    ASSERT_EQ(5, patomic_example_add(2, 3));
-}
diff --git a/test/kind/st/CMakeLists.txt b/test/kind/st/CMakeLists.txt
index 5312dc3a9..b741d703a 100644
--- a/test/kind/st/CMakeLists.txt
+++ b/test/kind/st/CMakeLists.txt
@@ -1,10 +1,19 @@
-include(../../cmake/CreateTest.cmake)
+# ---- Setup Tests ----
 
+add_custom_target(${test_target_name}-st)
+add_dependencies(${test_target_name} ${test_target_name}-st)
 
-# ---- Setup Tests ----
 
-add_custom_target(patomic-test-st)
-add_dependencies(patomic-test patomic-test-st)
+# ---- Create Tests ----
+
+create_st(
+    NAME StAsan
+    SOURCE
+        asan.cpp
+)
 
-create_st(NAME check_asan SOURCE check_asan.cpp)
-create_st(NAME check_ubsan SOURCE check_ubsan.cpp)
+create_st(
+    NAME StUbsan
+    SOURCE
+        ubsan.cpp
+)
diff --git a/test/kind/st/check_asan.cpp b/test/kind/st/asan.cpp
similarity index 84%
rename from test/kind/st/check_asan.cpp
rename to test/kind/st/asan.cpp
index 4ddf1c882..d034dfab8 100644
--- a/test/kind/st/check_asan.cpp
+++ b/test/kind/st/asan.cpp
@@ -2,10 +2,12 @@
 #include <gtest/gtest-spi.h>
 
 
+/// @brief Test fixture.
 class StAsan : public testing::Test
 {};
 
 
+/// @brief Check that asan catches use-after-free.
 TEST_F(StAsan, UseAfterFree)
 {
 #if PATOMIC_HAS_ASAN
@@ -19,6 +21,7 @@ TEST_F(StAsan, UseAfterFree)
 #endif
 }
 
+/// @brief Check that asan catches a heap allocated buffer overflow.
 TEST_F(StAsan, HeapBufferOverflow)
 {
 #if PATOMIC_HAS_ASAN
@@ -33,6 +36,7 @@ TEST_F(StAsan, HeapBufferOverflow)
 #endif
 }
 
+/// @brief Check that asan catches a stack allocated buffer overflow.
 TEST_F(StAsan, StackBufferOverflow)
 {
 #if PATOMIC_HAS_ASAN
@@ -51,6 +55,7 @@ TEST_F(StAsan, StackBufferOverflow)
 #endif
 }
 
+/// @brief Check that asan catches a statically allocated buffer overflow.
 TEST_F(StAsan, GlobalBufferOverflow)
 {
 #if PATOMIC_HAS_ASAN
diff --git a/test/kind/st/check_ubsan.cpp b/test/kind/st/ubsan.cpp
similarity index 66%
rename from test/kind/st/check_ubsan.cpp
rename to test/kind/st/ubsan.cpp
index b97f683da..f84767ac7 100644
--- a/test/kind/st/check_ubsan.cpp
+++ b/test/kind/st/ubsan.cpp
@@ -5,11 +5,13 @@
 #include <limits>
 
 
-class StUbsan : public testing::Test
+/// @brief Test fixture.
+class StCheckUbsan : public testing::Test
 {};
 
 
-TEST_F(StUbsan, ShiftExponentTooLarge)
+/// @brief Check that ubsan catches shifting by more than the bit width.
+TEST_F(StCheckUbsan, ShiftExponentTooLarge)
 {
 #if PATOMIC_HAS_UBSAN
     EXPECT_FATAL_FAILURE({
@@ -22,7 +24,8 @@ TEST_F(StUbsan, ShiftExponentTooLarge)
 #endif
 }
 
-TEST_F(StUbsan, SignedIntegerOverflow)
+/// @brief Check that ubsan catches signed integer overflow.
+TEST_F(StCheckUbsan, SignedIntegerOverflow)
 {
 #if PATOMIC_HAS_UBSAN
     EXPECT_FATAL_FAILURE({
@@ -36,7 +39,8 @@ TEST_F(StUbsan, SignedIntegerOverflow)
 #endif
 }
 
-TEST_F(StUbsan, FloatCastOverflow)
+/// @brief Check that ubsan catches overflow from casting float to integer.
+TEST_F(StCheckUbsan, FloatCastOverflow)
 {
 #if PATOMIC_HAS_UBSAN && defined(__clang__)
     EXPECT_FATAL_FAILURE({
diff --git a/test/kind/ut/CMakeLists.txt b/test/kind/ut/CMakeLists.txt
index a87c6a9ef..2145af367 100644
--- a/test/kind/ut/CMakeLists.txt
+++ b/test/kind/ut/CMakeLists.txt
@@ -1,10 +1,7 @@
-include(../../cmake/CreateTest.cmake)
-
-
 # ---- Setup Tests ----
 
-add_custom_target(patomic-test-ut)
-add_dependencies(patomic-test patomic-test-ut)
+add_custom_target(${test_target_name}-ut)
+add_dependencies(${test_target_name} ${test_target_name}-ut)
 
-create_ut(NAME example_add SOURCE example_add.cpp "${PATOMIC_SOURCE_DIR}/src/patomic.c")
-create_ut(NAME example_sub SOURCE example_sub.cpp "${PATOMIC_SOURCE_DIR}/src/patomic.c")
+# add all subdirectories
+add_subdirectory(internal)
diff --git a/test/kind/ut/example_add.cpp b/test/kind/ut/example_add.cpp
deleted file mode 100644
index 99d8ee625..000000000
--- a/test/kind/ut/example_add.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#include <gtest/gtest.h>
-#include <patomic/patomic.h>
-
-
-TEST(UtSuite, Add)
-{
-    ASSERT_EQ(5, patomic_example_add(2, 3));
-}
diff --git a/test/kind/ut/example_sub.cpp b/test/kind/ut/example_sub.cpp
deleted file mode 100644
index 4799d147d..000000000
--- a/test/kind/ut/example_sub.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#include <gtest/gtest.h>
-#include <patomic/patomic.h>
-
-
-TEST(UtSuite, Sub)
-{
-    ASSERT_EQ(5, patomic_example_add(2, 3));
-}
diff --git a/test/kind/ut/internal/CMakeLists.txt b/test/kind/ut/internal/CMakeLists.txt
new file mode 100644
index 000000000..da911491b
--- /dev/null
+++ b/test/kind/ut/internal/CMakeLists.txt
@@ -0,0 +1,8 @@
+# ---- Create Tests ----
+
+create_ut(
+    NAME UtInternalAlign
+    SOURCE
+        align.cpp
+        "${PATOMIC_SOURCE_DIR}/src/api/align.c"
+)
diff --git a/test/kind/ut/internal/align.cpp b/test/kind/ut/internal/align.cpp
new file mode 100644
index 000000000..932d236b6
--- /dev/null
+++ b/test/kind/ut/internal/align.cpp
@@ -0,0 +1,77 @@
+extern "C" {
+#include <patomic/internal/align.h>
+}
+
+#include <gtest/gtest.h>
+
+
+/// @brief Test fixture.
+class UtInternalAlign : public testing::Test
+{};
+
+
+/// @brief Comparing patomic_align_t prioritizes recommend over all other
+///        members. Larger recommended is stricter (and compares greater).
+TEST_F(UtInternalAlign, compare_align_recommended_ne)
+{
+    // setup
+    // larger recommended is stricter
+    patomic_align_t lt { 1, 16, 16 };
+    patomic_align_t gt { 2, 8, 8 };
+
+    // test
+    // check values
+    EXPECT_LT(lt.recommended, gt.recommended);
+    EXPECT_GT(lt.minimum, gt.minimum);
+    EXPECT_GT(lt.size_within, gt.size_within);
+    // do comparison
+    EXPECT_LT(patomic_internal_compare_align(lt, gt), 0);
+    EXPECT_GT(patomic_internal_compare_align(gt, lt), 0);
+}
+
+/// @brief Comparing patomic_align_t prioritizes minimum over all other members
+///        if recommend compares equal. Larger minimum is stricter (and
+///        compares greater).
+TEST_F(UtInternalAlign, compare_align_minimum_ne_recommended_eq)
+{
+    // setup
+    // larger recommended is stricter
+    patomic_align_t lt { 64, 1, 16 };
+    patomic_align_t gt { 64, 2, 8 };
+
+    // test
+    // check values
+    EXPECT_EQ(lt.recommended, gt.recommended);
+    EXPECT_LT(lt.minimum, gt.minimum);
+    EXPECT_GT(lt.size_within, gt.size_within);
+    // do comparison
+    EXPECT_LT(patomic_internal_compare_align(lt, gt), 0);
+    EXPECT_GT(patomic_internal_compare_align(gt, lt), 0);
+}
+
+/// @brief Comparing patomic_align_t prioritizes size_within after all other
+///        members. Larger size_within is less strict (and compares lesser),
+///        except for 0 which is the least strict.
+TEST_F(UtInternalAlign, compare_align_size_within_any_minimum_eq_recommended_eq)
+{
+    // setup
+    // smaller size_within is stricter (except zero which is least strict)
+    patomic_align_t zero { 64, 32, 0 };
+    patomic_align_t lt { 64, 32, 2 };
+    patomic_align_t gt { 64, 32, 1 };
+
+    // test
+    // check values
+    EXPECT_EQ(0, zero.size_within);
+    EXPECT_EQ(lt.recommended, gt.recommended);
+    EXPECT_EQ(lt.minimum, gt.minimum);
+    EXPECT_GT(lt.size_within, gt.size_within);
+    // do comparison
+    EXPECT_EQ(patomic_internal_compare_align(zero, zero), 0);
+    EXPECT_EQ(patomic_internal_compare_align(lt, lt), 0);
+    EXPECT_EQ(patomic_internal_compare_align(gt, gt), 0);
+    EXPECT_LT(patomic_internal_compare_align(zero, lt), 0);
+    EXPECT_LT(patomic_internal_compare_align(lt, gt), 0);
+    EXPECT_GT(patomic_internal_compare_align(gt, lt), 0);
+    EXPECT_GT(patomic_internal_compare_align(lt, zero), 0);
+}
diff --git a/test/src/CMakeLists.txt b/test/src/CMakeLists.txt
index 2c59f583b..a6d9e44d7 100644
--- a/test/src/CMakeLists.txt
+++ b/test/src/CMakeLists.txt
@@ -1,24 +1,20 @@
 # create object target that is automatically linked to all tests
-add_library(
-    patomic-test-src OBJECT
-    # .
-    "sanitizer_error.cpp"
-    "sanitizer_options.cpp"
-)
-
-# add include directories
-target_include_directories(
-    patomic-test-src PRIVATE
-    "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
-    "$<BUILD_INTERFACE:${GTEST_INCLUDE_DIRS}>"
-)
+# excluded from compilation unless a test is built
+add_library(${test_target_name}-src OBJECT EXCLUDE_FROM_ALL)
 
+# add dependencies
 # don't need to link against GTest or patomic:
 # - the test libraries already link against GTest which should be enough
 # - we shouldn't need anything from patomic here (or UTs would break)
+target_link_libraries(${test_target_name}-src PRIVATE
+    ${test_target_name}-include
+)
 
 # require C++14 as minimum
-target_compile_features(
-    patomic-test-src PRIVATE
+target_compile_features(${test_target_name}-src PRIVATE
     cxx_std_14
 )
+
+# add all subdirectories
+add_subdirectory(common)
+add_subdirectory(sanitizer)
diff --git a/test/src/common/CMakeLists.txt b/test/src/common/CMakeLists.txt
new file mode 100644
index 000000000..6371d88a0
--- /dev/null
+++ b/test/src/common/CMakeLists.txt
@@ -0,0 +1,7 @@
+# add directory files to target
+target_sources(${test_target_name}-src PRIVATE
+    align.cpp
+    compare.cpp
+    death.cpp
+    make_ops.cpp
+)
diff --git a/test/src/common/align.cpp b/test/src/common/align.cpp
new file mode 100644
index 000000000..560f29f73
--- /dev/null
+++ b/test/src/common/align.cpp
@@ -0,0 +1,59 @@
+#include <test/common/align.hpp>
+
+#include <cstdint>
+
+namespace test
+{
+
+
+void *
+aligned_pointer(
+    void *const buf_ptr, const std::size_t buf_size, const std::size_t align,
+    const std::size_t size
+) noexcept
+{
+    // defer to const implementation
+    const void *cc_ptr = buf_ptr;
+    cc_ptr = aligned_pointer(cc_ptr, buf_size, align, size);
+    return const_cast<void *>(cc_ptr);
+}
+
+
+const void *
+aligned_pointer(
+    const void *const buf_ptr, const std::size_t buf_size,
+    const std::size_t align, const std::size_t size
+) noexcept
+{
+    // special case for zero alignment
+    if (align == 0)
+    {
+        return (buf_size >= size) ? buf_ptr : nullptr;
+    }
+
+    // calculate the offset to the next aligned address
+    const auto raw_addr = reinterpret_cast<std::uintptr_t>(buf_ptr);
+    const std::size_t remainder = raw_addr % align;
+    const std::size_t offset = (align - remainder) % align;
+
+    // check that there is enough room in the buffer
+    if (buf_size < offset + size)
+    {
+        return nullptr;
+    }
+
+    // return aligned pointer
+    return static_cast<const char *>(buf_ptr) + offset;
+}
+
+
+std::size_t
+runtime_alignof(const void *ptr) noexcept
+{
+    // get the least significant set bit
+    const auto raw_addr = reinterpret_cast<std::uintptr_t>(ptr);
+    return raw_addr ? (raw_addr & (~raw_addr + 1)) : 1;
+}
+
+
+}  // namespace test
diff --git a/test/src/common/compare.cpp b/test/src/common/compare.cpp
new file mode 100644
index 000000000..5d3d00a01
--- /dev/null
+++ b/test/src/common/compare.cpp
@@ -0,0 +1,17 @@
+#include <test/common/compare.hpp>
+
+
+bool
+operator==(const patomic_align_t& lhs, const patomic_align_t& rhs) noexcept
+{
+    return lhs.recommended == rhs.recommended &&
+           lhs.minimum     == rhs.minimum     &&
+           lhs.size_within == rhs.size_within;
+}
+
+
+bool
+operator!=(const patomic_align_t& lhs, const patomic_align_t& rhs) noexcept
+{
+    return !(lhs == rhs);
+}
diff --git a/test/src/common/death.cpp b/test/src/common/death.cpp
new file mode 100644
index 000000000..6b7afbb3d
--- /dev/null
+++ b/test/src/common/death.cpp
@@ -0,0 +1,38 @@
+#include <test/common/death.hpp>
+
+#include <csignal>
+#include <cstdlib>
+
+
+namespace test
+{
+
+
+KilledByAbortPredicateType
+KilledByAbort()
+{
+#if PATOMIC_GTEST_HAS_KILLED_BY_SIGNAL
+    return testing::KilledBySignal(SIGABRT);
+#elif defined(GTEST_OS_WINDOWS)
+    #if defined(_UCRT)
+        const static unsigned int old_flags = _set_abort_behavior(0u, 0u);
+        if (old_flags & _CALL_REPORTFAULT)
+        {
+            constexpr int status_stack_buffer_overflow = -1073740791;
+            return testing::ExitedWithCode(status_stack_buffer_overflow);
+        }
+        else
+        {
+    #endif
+            constexpr int abort_exit_code = 3;
+            return testing::ExitedWithCode(abort_exit_code);
+    #if defined(_UCRT)
+        }
+    #endif
+#else
+    return testing::ExitedWithCode(3);
+#endif
+}
+
+
+}  // namespace test
diff --git a/test/src/common/make_ops.cpp b/test/src/common/make_ops.cpp
new file mode 100644
index 000000000..bb6a63c02
--- /dev/null
+++ b/test/src/common/make_ops.cpp
@@ -0,0 +1,884 @@
+#include <test/common/make_ops.hpp>
+#include <test/common/utility.hpp>
+
+#include <array>
+#include <cstdlib>
+
+
+#define CREATE_SETTER_LAMBDA(name, opkind)                                     \
+    const auto set_##name = [](T& ops, unsigned int& opkinds,                  \
+                               void(*nonnull_value)()) noexcept -> void {      \
+        ops.fp_##name = test::convertible_to_any<void(*)()> { nonnull_value }; \
+        opkinds |= patomic_opkind_##opkind;                                    \
+    }
+
+
+#define CREATE_GETTER_LAMBDA(name)                                   \
+    const auto get_##name = [](const T& ops) noexcept -> void(*)() { \
+        return reinterpret_cast<void(*)()>(ops.fp_##name);           \
+    }
+
+
+#define DEFINE_MAKE_OPS_COMBINATIONS_IET(fn_name, type_name, ops_holder_type)          \
+    template <>                                                                        \
+    std::vector<ops_holder_type<patomic_ops##type_name##t>>                            \
+    make_ops_##fn_name##_combinations<ops_domain::IMPLICIT>()                          \
+    {                                                                                  \
+        using T = patomic_ops##type_name##t;                                           \
+        return ::make_ops_##fn_name##_combinations<T>(&::only_for_address);            \
+    }                                                                                  \
+                                                                                       \
+    template <>                                                                        \
+    std::vector<ops_holder_type<patomic_ops##type_name##t>>                            \
+    make_ops_##fn_name##_combinations<ops_domain::IMPLICIT>(void(*nonnull_value)())    \
+    {                                                                                  \
+        using T = patomic_ops##type_name##t;                                           \
+        return ::make_ops_##fn_name##_combinations<T>(nonnull_value);                  \
+    }                                                                                  \
+                                                                                       \
+    template <>                                                                        \
+    std::vector<ops_holder_type<patomic_ops_explicit##type_name##t>>                   \
+    make_ops_##fn_name##_combinations<ops_domain::EXPLICIT>()                          \
+    {                                                                                  \
+        using T = patomic_ops_explicit##type_name##t;                                  \
+        return ::make_ops_##fn_name##_combinations<T>(&::only_for_address);            \
+    }                                                                                  \
+                                                                                       \
+    template <>                                                                        \
+    std::vector<ops_holder_type<patomic_ops_explicit##type_name##t>>                   \
+    make_ops_##fn_name##_combinations<ops_domain::EXPLICIT>(void(*nonnull_value)())    \
+    {                                                                                  \
+        using T = patomic_ops_explicit##type_name##t;                                  \
+        return ::make_ops_##fn_name##_combinations<T>(nonnull_value);                  \
+    }                                                                                  \
+                                                                                       \
+    template <>                                                                        \
+    std::vector<ops_holder_type<patomic_ops_transaction##type_name##t>>                \
+    make_ops_##fn_name##_combinations<ops_domain::TRANSACTION>()                       \
+    {                                                                                  \
+        using T = patomic_ops_transaction##type_name##t;                               \
+        return ::make_ops_##fn_name##_combinations<T>(&::only_for_address);            \
+    }                                                                                  \
+                                                                                       \
+    template <>                                                                        \
+    std::vector<ops_holder_type<patomic_ops_transaction##type_name##t>>                \
+    make_ops_##fn_name##_combinations<ops_domain::TRANSACTION>(void(*nonnull_value)()) \
+    {                                                                                  \
+        using T = patomic_ops_transaction##type_name##t;                               \
+        return ::make_ops_##fn_name##_combinations<T>(nonnull_value);                  \
+    }                                                                                  \
+                                                                                       \
+    static_assert(!!true, "require semicolon")
+
+
+#define DEFINE_MAKE_OPS_ARRAY_IET(fn_name, type_name, op_count)    \
+    template <>                                                    \
+    std::array<void(*)(), op_count>                                \
+    make_ops_##fn_name##_array<ops_domain::IMPLICIT>(              \
+        const patomic_ops##type_name##t& ops) noexcept             \
+    {                                                              \
+        return ::make_ops_##fn_name##_array(ops);                  \
+                                                                   \
+    }                                                              \
+                                                                   \
+    template <>                                                    \
+    std::array<void(*)(), op_count>                                \
+    make_ops_##fn_name##_array<ops_domain::EXPLICIT>(              \
+        const patomic_ops_explicit##type_name##t& ops) noexcept    \
+    {                                                              \
+        return ::make_ops_##fn_name##_array(ops);                  \
+    }                                                              \
+                                                                   \
+    template <>                                                    \
+    std::array<void(*)(), op_count>                                \
+    make_ops_##fn_name##_array<ops_domain::TRANSACTION>(           \
+        const patomic_ops_transaction##type_name##t& ops) noexcept \
+    {                                                              \
+        return ::make_ops_##fn_name##_array(ops);                  \
+    }                                                              \
+                                                                   \
+    static_assert(!!true, "require semicolon")
+
+
+namespace
+{
+
+
+constexpr void
+only_for_address() noexcept
+{}
+
+
+template <class T>
+constexpr T
+make_ops_all_nonnull_except_transaction_specific(void(*nonnull_value)()) noexcept
+{
+    // setup
+    T ops {};
+    const test::convertible_to_any<void(*)(void)> non_null {
+        nonnull_value
+    };
+
+    // initialize all members to be non-null
+    // LDST
+    ops.fp_store = non_null;
+    ops.fp_load  = non_null;
+    // XCHG
+    ops.xchg_ops.fp_exchange       = non_null;
+    ops.xchg_ops.fp_cmpxchg_weak   = non_null;
+    ops.xchg_ops.fp_cmpxchg_strong = non_null;
+    // BIT
+    ops.bitwise_ops.fp_test       = non_null;
+    ops.bitwise_ops.fp_test_compl = non_null;
+    ops.bitwise_ops.fp_test_set   = non_null;
+    ops.bitwise_ops.fp_test_reset = non_null;
+    // BIN_V
+    ops.binary_ops.fp_or  = non_null;
+    ops.binary_ops.fp_xor = non_null;
+    ops.binary_ops.fp_and = non_null;
+    ops.binary_ops.fp_not = non_null;
+    // BIN_F
+    ops.binary_ops.fp_fetch_or  = non_null;
+    ops.binary_ops.fp_fetch_xor = non_null;
+    ops.binary_ops.fp_fetch_and = non_null;
+    ops.binary_ops.fp_fetch_not = non_null;
+    // ARI_V
+    ops.arithmetic_ops.fp_add = non_null;
+    ops.arithmetic_ops.fp_sub = non_null;
+    ops.arithmetic_ops.fp_inc = non_null;
+    ops.arithmetic_ops.fp_dec = non_null;
+    ops.arithmetic_ops.fp_neg = non_null;
+    // ARI_F
+    ops.arithmetic_ops.fp_fetch_add = non_null;
+    ops.arithmetic_ops.fp_fetch_sub = non_null;
+    ops.arithmetic_ops.fp_fetch_inc = non_null;
+    ops.arithmetic_ops.fp_fetch_dec = non_null;
+    ops.arithmetic_ops.fp_fetch_neg = non_null;
+
+    // return fully initialized object
+    return ops;
+}
+
+
+template <class T>
+class setters_vf
+{
+public:
+    setters_vf() noexcept = default;
+
+    setters_vf& v(void(*set_v)(T&, unsigned int&, void(*)())) noexcept
+    {
+        this->set_void = set_v;
+        return *this;
+    }
+
+    setters_vf& f(void(*set_f)(T&, unsigned int&, void(*)())) noexcept
+    {
+        this->set_fetch = set_f;
+        return *this;
+    }
+
+    void (*set_void)(T&, unsigned int&, void(*)()) = nullptr;
+    void (*set_fetch)(T&, unsigned int&, void(*)()) = nullptr;
+};
+
+
+template <class T>
+std::vector<test::ops_any_all<T>>
+make_ops_combinations(const std::vector<void(*)(T&, unsigned int&, void(*)())>& setters,
+                      void(*nonnull_value)())
+{
+    // setup
+    std::vector<test::ops_any_all<T>> combinations;
+    combinations.resize(1ull << setters.size());
+
+    // go through all combinations
+    for (std::size_t i = 0; i < combinations.size(); ++i)
+    {
+        // set initial values
+        combinations[i].all = true;
+        combinations[i].any = false;
+
+        // set necessary members and update values
+        for (std::size_t j = 0; j < setters.size(); ++j)
+        {
+            if (i & (1ull << j))
+            {
+                setters[j](combinations[i].ops, combinations[i].opkinds, nonnull_value);
+                combinations[i].any = true;
+            }
+            else
+            {
+                combinations[i].all = false;
+            }
+        }
+    }
+
+    // return
+    return combinations;
+}
+
+
+template <class T>
+std::vector<test::ops_any_all_vf<T>>
+make_ops_combinations(const std::vector<setters_vf<T>>& setters, void(*nonnull_value)())
+{
+    // setup
+    const auto sqrt_size = (1u << setters.size());
+    std::vector<test::ops_any_all_vf<T>> combinations;
+    combinations.resize(sqrt_size * sqrt_size);
+
+    // go through all combinations
+    for (std::size_t i_fetch = 0; i_fetch < sqrt_size; ++i_fetch)
+    {
+        for (std::size_t i_void = 0; i_void < sqrt_size; ++i_void)
+        {
+            // set initial values
+            const auto i = i_void + (i_fetch * sqrt_size);
+            combinations[i].all_void = true;
+            combinations[i].all_fetch = true;
+            combinations[i].any_void = false;
+            combinations[i].any_fetch = false;
+
+            for (std::size_t j = 0; j < setters.size(); ++j)
+            {
+                // conditionally set void operations
+                if (i_void & (1ull << j))
+                {
+                    setters[j].set_void(
+                        combinations[i].ops, combinations[i].opkinds_void, nonnull_value);
+                    combinations[i].any_void = true;
+                }
+                else
+                {
+                    combinations[i].all_void = false;
+                }
+
+                // conditionally set fetch operations
+                if (i_fetch & (1ull << j))
+                {
+                    setters[j].set_fetch(
+                        combinations[i].ops, combinations[i].opkinds_fetch, nonnull_value);
+                    combinations[i].any_fetch = true;
+                }
+                else
+                {
+                    combinations[i].all_fetch = false;
+                }
+            }
+        }
+    }
+
+    // return
+    return combinations;
+}
+
+
+template <class T>
+std::vector<test::ops_any_all<T>>
+make_ops_ldst_combinations(void(*nonnull_value)())
+{
+    // lambda helpers
+    CREATE_SETTER_LAMBDA(store, STORE);
+    CREATE_SETTER_LAMBDA(load, LOAD);
+    const std::vector<void(*)(T&, unsigned int&, void(*)())> setters {
+        set_store,
+        set_load
+    };
+
+    // create all combinations
+    return make_ops_combinations(setters, nonnull_value);
+}
+
+
+template <class T>
+std::vector<test::ops_any_all<T>>
+make_ops_xchg_combinations(void(*nonnull_value)())
+{
+    // lambda helpers
+    CREATE_SETTER_LAMBDA(exchange, EXCHANGE);
+    CREATE_SETTER_LAMBDA(cmpxchg_weak, CMPXCHG_WEAK);
+    CREATE_SETTER_LAMBDA(cmpxchg_strong, CMPXCHG_STRONG);
+    const std::vector<void(*)(T&, unsigned int&, void(*)())> setters {
+        set_exchange,
+        set_cmpxchg_weak,
+        set_cmpxchg_strong
+    };
+
+    // create all combinations
+    return make_ops_combinations(setters, nonnull_value);
+}
+
+
+template <class T>
+std::vector<test::ops_any_all<T>>
+make_ops_bitwise_combinations(void(*nonnull_value)())
+{
+    // lambda helpers
+    CREATE_SETTER_LAMBDA(test, TEST);
+    CREATE_SETTER_LAMBDA(test_compl, TEST_COMPL);
+    CREATE_SETTER_LAMBDA(test_set, TEST_SET);
+    CREATE_SETTER_LAMBDA(test_reset, TEST_RESET);
+    const std::vector<void(*)(T&, unsigned int&, void(*)())> setters {
+        set_test,
+        set_test_compl,
+        set_test_set,
+        set_test_reset
+    };
+
+    // create all combinations
+    return make_ops_combinations(setters, nonnull_value);
+}
+
+
+template <class T>
+std::vector<test::ops_any_all_vf<T>>
+make_ops_binary_combinations(void(*nonnull_value)())
+{
+    // lambda helpers
+    CREATE_SETTER_LAMBDA(or, OR);
+    CREATE_SETTER_LAMBDA(xor, XOR);
+    CREATE_SETTER_LAMBDA(and, AND);
+    CREATE_SETTER_LAMBDA(not, NOT);
+    CREATE_SETTER_LAMBDA(fetch_or, OR);
+    CREATE_SETTER_LAMBDA(fetch_xor, XOR);
+    CREATE_SETTER_LAMBDA(fetch_and, AND);
+    CREATE_SETTER_LAMBDA(fetch_not, NOT);
+    const std::vector<setters_vf<T>> setters {
+        setters_vf<T>().v(set_or).f(set_fetch_or),
+        setters_vf<T>().v(set_xor).f(set_fetch_xor),
+        setters_vf<T>().v(set_and).f(set_fetch_and),
+        setters_vf<T>().v(set_not).f(set_fetch_not)
+    };
+
+    // create all combinations
+    return make_ops_combinations(setters, nonnull_value);
+}
+
+
+template <class T>
+std::vector<test::ops_any_all_vf<T>>
+make_ops_arithmetic_combinations(void(*nonnull_value)())
+{
+    // lambda helpers
+    CREATE_SETTER_LAMBDA(add, ADD);
+    CREATE_SETTER_LAMBDA(sub, SUB);
+    CREATE_SETTER_LAMBDA(inc, INC);
+    CREATE_SETTER_LAMBDA(dec, DEC);
+    CREATE_SETTER_LAMBDA(neg, NEG);
+    CREATE_SETTER_LAMBDA(fetch_add, ADD);
+    CREATE_SETTER_LAMBDA(fetch_sub, SUB);
+    CREATE_SETTER_LAMBDA(fetch_inc, INC);
+    CREATE_SETTER_LAMBDA(fetch_dec, DEC);
+    CREATE_SETTER_LAMBDA(fetch_neg, NEG);
+    const std::vector<setters_vf<T>> setters {
+        setters_vf<T>().v(set_add).f(set_fetch_add),
+        setters_vf<T>().v(set_sub).f(set_fetch_sub),
+        setters_vf<T>().v(set_inc).f(set_fetch_inc),
+        setters_vf<T>().v(set_dec).f(set_fetch_dec),
+        setters_vf<T>().v(set_neg).f(set_fetch_neg),
+    };
+
+    // create all combinations
+    return make_ops_combinations(setters, nonnull_value);
+}
+
+
+template <class T, std::size_t N>
+std::array<void(*)(), N>
+make_ops_array(const T& ops,
+               const std::array<void(*(*)(const T&))(), N>& getters) noexcept
+{
+    // go through all combinations
+    std::array<void(*)(), N> arr {};
+    for (std::size_t i = 0; i < N; ++i)
+    {
+        arr[i] = getters[i](ops);
+    }
+    return arr;
+}
+
+
+template <class T>
+std::array<void(*)(), 2>
+make_ops_ldst_array(const T& ldst_ops) noexcept
+{
+    // lambda helpers
+    CREATE_GETTER_LAMBDA(store);
+    CREATE_GETTER_LAMBDA(load);
+    const std::array<void(*(*)(const T&))(), 2> getters {
+        get_store,
+        get_load
+    };
+
+    // create array
+    return make_ops_array(ldst_ops, getters);
+}
+
+
+template <class T>
+std::array<void(*)(), 3>
+make_ops_xchg_array(const T& xchg_ops) noexcept
+{
+    // lambda helpers
+    CREATE_GETTER_LAMBDA(exchange);
+    CREATE_GETTER_LAMBDA(cmpxchg_weak);
+    CREATE_GETTER_LAMBDA(cmpxchg_strong);
+    const std::array<void(*(*)(const T&))(), 3> getters {
+        get_exchange,
+        get_cmpxchg_weak,
+        get_cmpxchg_strong
+    };
+
+    // create array
+    return make_ops_array(xchg_ops, getters);
+}
+
+
+template <class T>
+std::array<void(*)(), 4>
+make_ops_bitwise_array(const T& bitwise_ops) noexcept
+{
+    // lambda helpers
+    CREATE_GETTER_LAMBDA(test);
+    CREATE_GETTER_LAMBDA(test_compl);
+    CREATE_GETTER_LAMBDA(test_set);
+    CREATE_GETTER_LAMBDA(test_reset);
+    const std::array<void(*(*)(const T&))(), 4> getters {
+        get_test,
+        get_test_compl,
+        get_test_set,
+        get_test_reset
+    };
+
+    // create array
+    return make_ops_array(bitwise_ops, getters);
+}
+
+
+template <class T>
+std::array<void(*)(), 8>
+make_ops_binary_array(const T& binary_ops) noexcept
+{
+    // lambda helpers
+    CREATE_GETTER_LAMBDA(or);
+    CREATE_GETTER_LAMBDA(xor);
+    CREATE_GETTER_LAMBDA(and);
+    CREATE_GETTER_LAMBDA(not);
+    CREATE_GETTER_LAMBDA(fetch_or);
+    CREATE_GETTER_LAMBDA(fetch_xor);
+    CREATE_GETTER_LAMBDA(fetch_and);
+    CREATE_GETTER_LAMBDA(fetch_not);
+    const std::array<void(*(*)(const T&))(), 8> getters {
+        get_or,
+        get_xor,
+        get_and,
+        get_not,
+        get_fetch_or,
+        get_fetch_xor,
+        get_fetch_and,
+        get_fetch_not
+    };
+
+    // create array
+    return make_ops_array(binary_ops, getters);
+}
+
+
+template <class T>
+std::array<void(*)(), 10>
+make_ops_arithmetic_array(const T& arithmetic_ops) noexcept
+{
+    // lambda helpers
+    CREATE_GETTER_LAMBDA(add);
+    CREATE_GETTER_LAMBDA(sub);
+    CREATE_GETTER_LAMBDA(inc);
+    CREATE_GETTER_LAMBDA(dec);
+    CREATE_GETTER_LAMBDA(neg);
+    CREATE_GETTER_LAMBDA(fetch_add);
+    CREATE_GETTER_LAMBDA(fetch_sub);
+    CREATE_GETTER_LAMBDA(fetch_inc);
+    CREATE_GETTER_LAMBDA(fetch_dec);
+    CREATE_GETTER_LAMBDA(fetch_neg);
+    const std::array<void(*(*)(const T&))(), 10> getters {
+        get_add,
+        get_sub,
+        get_inc,
+        get_dec,
+        get_neg,
+        get_fetch_add,
+        get_fetch_sub,
+        get_fetch_inc,
+        get_fetch_dec,
+        get_fetch_neg
+    };
+
+    // create array
+    return make_ops_array(arithmetic_ops, getters);
+}
+
+
+}  // namespace
+
+
+namespace test
+{
+
+
+std::string
+to_string(ops_domain domain)
+{
+    switch (domain)
+    {
+        case ops_domain::IMPLICIT:
+            return "IMPLICIT";
+        case ops_domain::EXPLICIT:
+            return "EXPLICIT";
+        case ops_domain::TRANSACTION:
+            return "TRANSACTION";
+        default:
+            return "(unknown)";
+    }
+}
+
+
+std::vector<patomic_opcat_t>
+make_opcats_all_solo()
+{
+    return {
+        patomic_opcat_NONE,
+        patomic_opcat_LDST,
+        patomic_opcat_XCHG,
+        patomic_opcat_BIT,
+        patomic_opcat_BIN_V,
+        patomic_opcat_BIN_F,
+        patomic_opcat_ARI_V,
+        patomic_opcat_ARI_F,
+        patomic_opcat_TSPEC,
+        patomic_opcat_TFLAG,
+        patomic_opcat_TRAW
+    };
+}
+
+
+std::vector<patomic_opcat_t>
+make_opcats_all_combined()
+{
+    return {
+        patomic_opcats_BIN,
+        patomic_opcats_ARI,
+        patomic_opcats_IMPLICIT,
+        patomic_opcats_EXPLICIT,
+        patomic_opcats_TRANSACTION
+    };
+}
+
+
+std::vector<patomic_opkind_t>
+make_opkinds_all_solo()
+{
+    return {
+        patomic_opkind_NONE,
+        patomic_opkind_LOAD,
+        patomic_opkind_STORE,
+        patomic_opkind_EXCHANGE,
+        patomic_opkind_CMPXCHG_WEAK,
+        patomic_opkind_CMPXCHG_STRONG,
+        patomic_opkind_TEST,
+        patomic_opkind_TEST_SET,
+        patomic_opkind_TEST_RESET,
+        patomic_opkind_TEST_COMPL,
+        patomic_opkind_CLEAR,
+        patomic_opkind_OR,
+        patomic_opkind_XOR,
+        patomic_opkind_AND,
+        patomic_opkind_NOT,
+        patomic_opkind_ADD,
+        patomic_opkind_SUB,
+        patomic_opkind_INC,
+        patomic_opkind_DEC,
+        patomic_opkind_NEG,
+        patomic_opkind_DOUBLE_CMPXCHG,
+        patomic_opkind_MULTI_CMPXCHG,
+        patomic_opkind_GENERIC,
+        patomic_opkind_GENERIC_WFB,
+        patomic_opkind_TBEGIN,
+        patomic_opkind_TABORT,
+        patomic_opkind_TCOMMIT,
+        patomic_opkind_TTEST
+    };
+}
+
+
+std::vector<patomic_opkind_t>
+make_opkinds_all_combined()
+{
+    return {
+        patomic_opkinds_LDST,
+        patomic_opkinds_XCHG,
+        patomic_opkinds_BIT,
+        patomic_opkinds_BIN,
+        patomic_opkinds_ARI,
+        patomic_opkinds_TSPEC,
+        patomic_opkinds_TFLAG,
+        patomic_opkinds_TRAW
+    };
+}
+
+
+template <>
+patomic_ops_t
+make_ops_all_nonnull<ops_domain::IMPLICIT>(void(*nonnull_value)()) noexcept
+{
+    using ops_t = patomic_ops_t;
+    return ::make_ops_all_nonnull_except_transaction_specific<ops_t>(nonnull_value);
+}
+
+
+template <>
+patomic_ops_explicit_t
+make_ops_all_nonnull<ops_domain::EXPLICIT>(void(*nonnull_value)()) noexcept
+{
+    using ops_t = patomic_ops_explicit_t;
+    return ::make_ops_all_nonnull_except_transaction_specific<ops_t>(nonnull_value);
+}
+
+
+template <>
+patomic_ops_transaction_t
+make_ops_all_nonnull<ops_domain::TRANSACTION>(void(*nonnull_value)()) noexcept
+{
+    using ops_t = patomic_ops_transaction_t;
+    ops_t ops = ::make_ops_all_nonnull_except_transaction_specific<ops_t>(nonnull_value);
+    const test::convertible_to_any<void(*)()> non_null {
+        nonnull_value
+    };
+
+    // initialize all transaction specific members to be non-null
+    // TSPEC
+    ops.special_ops.fp_double_cmpxchg = non_null;
+    ops.special_ops.fp_multi_cmpxchg  = non_null;
+    ops.special_ops.fp_generic        = non_null;
+    ops.special_ops.fp_generic_wfb    = non_null;
+    // TFLAG
+    ops.flag_ops.fp_test     = non_null;
+    ops.flag_ops.fp_test_set = non_null;
+    ops.flag_ops.fp_clear    = non_null;
+    // TRAW
+    ops.raw_ops.fp_tbegin  = non_null;
+    ops.raw_ops.fp_tabort  = non_null;
+    ops.raw_ops.fp_tcommit = non_null;
+    ops.raw_ops.fp_ttest   = non_null;
+
+    // return fully nonnull ops
+    return ops;
+}
+
+
+template <>
+patomic_ops_t
+make_ops_all_nonnull<ops_domain::IMPLICIT>() noexcept
+{
+    constexpr auto nonnull_value = &only_for_address;
+    return make_ops_all_nonnull<ops_domain::IMPLICIT>(nonnull_value);
+}
+
+
+template <>
+patomic_ops_explicit_t
+make_ops_all_nonnull<ops_domain::EXPLICIT>() noexcept
+{
+    constexpr auto nonnull_value = &only_for_address;
+    return make_ops_all_nonnull<ops_domain::EXPLICIT>(nonnull_value);
+}
+
+
+template <>
+patomic_ops_transaction_t
+make_ops_all_nonnull<ops_domain::TRANSACTION>() noexcept
+{
+    constexpr auto nonnull_value = &only_for_address;
+    return make_ops_all_nonnull<ops_domain::TRANSACTION>(nonnull_value);
+}
+
+
+DEFINE_MAKE_OPS_COMBINATIONS_IET(ldst, _, ops_any_all);
+
+
+DEFINE_MAKE_OPS_COMBINATIONS_IET(xchg, _xchg_, ops_any_all);
+
+
+DEFINE_MAKE_OPS_COMBINATIONS_IET(bitwise, _bitwise_, ops_any_all);
+
+
+DEFINE_MAKE_OPS_COMBINATIONS_IET(binary, _binary_, ops_any_all_vf);
+
+
+DEFINE_MAKE_OPS_COMBINATIONS_IET(arithmetic, _arithmetic_, ops_any_all_vf);
+
+
+std::vector<ops_any_all<patomic_ops_transaction_special_t>>
+make_ops_special_combinations_transaction(void(*nonnull_value)())
+{
+    // lambda helpers
+    using T = patomic_ops_transaction_special_t;
+    CREATE_SETTER_LAMBDA(double_cmpxchg, DOUBLE_CMPXCHG);
+    CREATE_SETTER_LAMBDA(multi_cmpxchg, MULTI_CMPXCHG);
+    CREATE_SETTER_LAMBDA(generic, GENERIC);
+    CREATE_SETTER_LAMBDA(generic_wfb, GENERIC_WFB);
+    const std::vector<void(*)(T&, unsigned int&, void(*)())> setters {
+        set_double_cmpxchg,
+        set_multi_cmpxchg,
+        set_generic,
+        set_generic_wfb
+    };
+
+    // create all combinations
+    return make_ops_combinations(setters, nonnull_value);
+}
+
+
+std::vector<ops_any_all<patomic_ops_transaction_special_t>>
+make_ops_special_combinations_transaction()
+{
+    // defer to overload
+    return make_ops_special_combinations_transaction(&::only_for_address);
+}
+
+
+std::vector<ops_any_all<patomic_ops_transaction_flag_t>>
+make_ops_flag_combinations_transaction(void(*nonnull_value)())
+{
+    // lambda helpers
+    using T = patomic_ops_transaction_flag_t;
+    CREATE_SETTER_LAMBDA(test, TEST);
+    CREATE_SETTER_LAMBDA(test_set, TEST_SET);
+    CREATE_SETTER_LAMBDA(clear, CLEAR);
+    const std::vector<void(*)(T&, unsigned int&, void(*)())> setters {
+        set_test,
+        set_test_set,
+        set_clear
+    };
+
+    // create all combinations
+    return make_ops_combinations(setters, nonnull_value);
+}
+
+
+std::vector<ops_any_all<patomic_ops_transaction_flag_t>>
+make_ops_flag_combinations_transaction()
+{
+    // defer to overload
+    return make_ops_flag_combinations_transaction(&::only_for_address);
+}
+
+
+std::vector<ops_any_all<patomic_ops_transaction_raw_t>>
+make_ops_raw_combinations_transaction(void(*nonnull_value)())
+{
+    // lambda helpers
+    using T = patomic_ops_transaction_raw_t;
+    CREATE_SETTER_LAMBDA(tbegin, TBEGIN);
+    CREATE_SETTER_LAMBDA(tabort, TABORT);
+    CREATE_SETTER_LAMBDA(tcommit, TCOMMIT);
+    CREATE_SETTER_LAMBDA(ttest, TTEST);
+    const std::vector<void(*)(T&, unsigned int&, void(*)())> setters {
+        set_tbegin,
+        set_tabort,
+        set_tcommit,
+        set_ttest
+    };
+
+    // create all combinations
+    return make_ops_combinations(setters, nonnull_value);
+}
+
+
+std::vector<ops_any_all<patomic_ops_transaction_raw_t>>
+make_ops_raw_combinations_transaction()
+{
+    // defer to overload
+    return make_ops_raw_combinations_transaction(&::only_for_address);
+}
+
+
+DEFINE_MAKE_OPS_ARRAY_IET(ldst, _, 2);
+
+
+DEFINE_MAKE_OPS_ARRAY_IET(xchg, _xchg_, 3);
+
+
+DEFINE_MAKE_OPS_ARRAY_IET(bitwise, _bitwise_, 4);
+
+
+DEFINE_MAKE_OPS_ARRAY_IET(binary, _binary_, 8);
+
+
+DEFINE_MAKE_OPS_ARRAY_IET(arithmetic, _arithmetic_, 10);
+
+
+std::array<void(*)(), 4>
+make_ops_special_array_transaction(
+    const patomic_ops_transaction_special_t& special) noexcept
+{
+    // lambda helpers
+    using T = patomic_ops_transaction_special_t;
+    CREATE_GETTER_LAMBDA(double_cmpxchg);
+    CREATE_GETTER_LAMBDA(multi_cmpxchg);
+    CREATE_GETTER_LAMBDA(generic);
+    CREATE_GETTER_LAMBDA(generic_wfb);
+    const std::array<void(*(*)(const T&))(), 4> getters {
+        get_double_cmpxchg,
+        get_multi_cmpxchg,
+        get_generic,
+        get_generic_wfb
+    };
+
+    // create array
+    return make_ops_array(special, getters);
+}
+
+
+std::array<void(*)(), 3>
+make_ops_flag_array_transaction(
+    const patomic_ops_transaction_flag_t& flag) noexcept
+{
+    // lambda helpers
+    using T = patomic_ops_transaction_flag_t;
+    CREATE_GETTER_LAMBDA(test);
+    CREATE_GETTER_LAMBDA(test_set);
+    CREATE_GETTER_LAMBDA(clear);
+    const std::array<void(*(*)(const T&))(), 3> getters {
+        get_test,
+        get_test_set,
+        get_clear
+    };
+
+    // create array
+    return make_ops_array(flag, getters);
+}
+
+
+std::array<void(*)(), 4>
+make_ops_raw_array_transaction(
+    const patomic_ops_transaction_raw_t& raw) noexcept
+{
+    // lambda helpers
+    using T = patomic_ops_transaction_raw_t;
+    CREATE_GETTER_LAMBDA(tbegin);
+    CREATE_GETTER_LAMBDA(tabort);
+    CREATE_GETTER_LAMBDA(tcommit);
+    CREATE_GETTER_LAMBDA(ttest);
+    const std::array<void(*(*)(const T&))(), 4> getters {
+        get_tbegin,
+        get_tabort,
+        get_tcommit,
+        get_ttest
+    };
+
+    // create array
+    return make_ops_array(raw, getters);
+}
+
+
+}  // namespace test
diff --git a/test/src/sanitizer/CMakeLists.txt b/test/src/sanitizer/CMakeLists.txt
new file mode 100644
index 000000000..64fcb2b9f
--- /dev/null
+++ b/test/src/sanitizer/CMakeLists.txt
@@ -0,0 +1,5 @@
+# add directory files to target
+target_sources(${test_target_name}-src PRIVATE
+    error.cpp
+    options.cpp
+)
diff --git a/test/src/sanitizer_error.cpp b/test/src/sanitizer/error.cpp
similarity index 100%
rename from test/src/sanitizer_error.cpp
rename to test/src/sanitizer/error.cpp
diff --git a/test/src/sanitizer_options.cpp b/test/src/sanitizer/options.cpp
similarity index 100%
rename from test/src/sanitizer_options.cpp
rename to test/src/sanitizer/options.cpp