├── .clang-format ├── .clang-tidy ├── .cmake-format.py ├── .devcontainer └── devcontainer.json ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CMakeLists.txt ├── CMakePresets.json ├── CODEOWNERS ├── CPPLINT.cfg ├── DockerFile ├── LICENSE ├── README.md ├── cmake ├── FindCoroutines.cmake ├── compiler_warnings.cmake ├── gapConfig.cmake.in ├── prevent_in_source_builds.cmake ├── project_settings.cmake ├── sanitizers.cmake ├── static_analyzers.cmake └── utils.cmake ├── core ├── CMakeLists.txt └── include │ └── gap │ └── core │ ├── benchmark.hpp │ ├── bigint.hpp │ ├── common.hpp │ ├── concepts.hpp │ ├── config.hpp │ ├── coroutine.hpp │ ├── crtp.hpp │ ├── dense_map.hpp │ ├── fixed_string.hpp │ ├── generator.hpp │ ├── hash.hpp │ ├── memoize.hpp │ ├── on_scope_exit.hpp │ ├── optional.hpp │ ├── overloads.hpp │ ├── parser.hpp │ ├── pointer.hpp │ ├── ranges.hpp │ ├── recursive_generator.hpp │ ├── source_location.hpp │ ├── strong_type.hpp │ ├── type_traits.hpp │ └── union_find.hpp ├── coro ├── CMakeLists.txt ├── include │ └── gap │ │ └── coro │ │ ├── async_manual_reset_event.hpp │ │ ├── awaitable_traits.hpp │ │ ├── broken_promise.hpp │ │ ├── coroutine.hpp │ │ ├── fmap.hpp │ │ ├── generator.hpp │ │ ├── manual_reset_event.hpp │ │ ├── recursive_generator.hpp │ │ ├── shared_task.hpp │ │ ├── single_consumer_event.hpp │ │ ├── sync_wait.hpp │ │ ├── task.hpp │ │ └── when_all_ready.hpp └── src │ └── async_manual_reset_event.cpp ├── doctest.cfg ├── graph ├── CMakeLists.txt └── include │ └── gap │ └── graph │ └── graph.hpp ├── mlir ├── CMakeLists.txt ├── include │ └── gap │ │ └── mlir │ │ ├── common.hpp │ │ ├── functors.hpp │ │ └── views.hpp └── src │ └── views.cpp ├── notes.md ├── sarif ├── CMakeLists.txt ├── include │ └── gap │ │ └── sarif │ │ └── sarif.hpp ├── scripts │ └── sarif-headergen │ │ ├── .gitignore │ │ ├── README.md │ │ ├── pyproject.toml │ │ └── src │ │ └── sarif_headergen │ │ ├── __cli__.py │ │ ├── __init__.py │ │ ├── output.py │ │ └── schema.py └── src │ └── sarif.cpp ├── test ├── CMakeLists.txt ├── core │ ├── CMakeLists.txt │ ├── benchmark.cpp │ ├── bigint.cpp │ ├── concepts.cpp │ ├── crtp.cpp │ ├── dense_map.cpp │ ├── hash.cpp │ ├── memoize.cpp │ ├── optional.cpp │ ├── parser.cpp │ ├── ranges.cpp │ ├── strong_type.cpp │ └── union_find.cpp ├── coro │ ├── CMakeLists.txt │ ├── counted.cpp │ ├── counted.hpp │ ├── generator.cpp │ ├── recursive_generator.cpp │ ├── shared_task.cpp │ ├── sync_wait.cpp │ ├── task.cpp │ └── when_all_ready.cpp ├── graph │ ├── CMakeLists.txt │ └── graph.cpp ├── main.cpp ├── mlir │ ├── CMakeLists.txt │ ├── functors.cpp │ ├── mlir_context_fixture.hpp │ ├── mlir_module_fixture.hpp │ └── views.cpp └── sarif │ ├── CMakeLists.txt │ └── sarif.cpp └── vcpkg.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: AlwaysBreak 3 | AlignArrayOfStructures: Right 4 | AlignConsecutiveBitFields: Consecutive 5 | AlignConsecutiveMacros: 'true' 6 | AlignConsecutiveAssignments: 'true' 7 | AlignEscapedNewlines: Left 8 | AlignOperands: 'true' 9 | AlignTrailingComments: 'true' 10 | AllowAllArgumentsOnNextLine: 'true' 11 | AllowAllConstructorInitializersOnNextLine: 'true' 12 | AllowAllParametersOfDeclarationOnNextLine: 'true' 13 | AllowShortBlocksOnASingleLine: 'true' 14 | AllowShortCaseLabelsOnASingleLine: 'true' 15 | AllowShortFunctionsOnASingleLine: All 16 | AllowShortIfStatementsOnASingleLine: Never 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortLoopsOnASingleLine: 'false' 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: 'false' 21 | AlwaysBreakTemplateDeclarations: 'Yes' 22 | BinPackArguments: 'true' 23 | BinPackParameters: 'false' 24 | BreakBeforeBinaryOperators: All 25 | BreakBeforeTernaryOperators: 'true' 26 | BreakConstructorInitializers: BeforeComma 27 | BreakInheritanceList: BeforeComma 28 | BreakStringLiterals: 'true' 29 | ColumnLimit: '100' 30 | CompactNamespaces: 'false' 31 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' 32 | ConstructorInitializerIndentWidth: '4' 33 | ContinuationIndentWidth: '4' 34 | Cpp11BracedListStyle: 'false' 35 | DerivePointerAlignment: 'true' 36 | FixNamespaceComments: 'true' 37 | IncludeBlocks: Regroup 38 | IndentCaseLabels: 'true' 39 | IndentPPDirectives: BeforeHash 40 | IndentWidth: '4' 41 | IndentWrappedFunctionNames: 'false' 42 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 43 | Language: Cpp 44 | MaxEmptyLinesToKeep: '1' 45 | NamespaceIndentation: All 46 | PointerAlignment: Right 47 | ReflowComments: 'true' 48 | SortIncludes: 'true' 49 | SortUsingDeclarations: 'true' 50 | SpaceAfterCStyleCast: 'true' 51 | SpaceAfterLogicalNot: 'false' 52 | SpaceAfterTemplateKeyword: 'false' 53 | SpaceBeforeAssignmentOperators: 'true' 54 | SpaceBeforeCpp11BracedList: 'false' 55 | SpaceBeforeCtorInitializerColon: 'true' 56 | SpaceBeforeInheritanceColon: 'true' 57 | SpaceBeforeParens: ControlStatements 58 | SpaceBeforeRangeBasedForLoopColon: 'true' 59 | SpaceInEmptyParentheses: 'false' 60 | SpacesBeforeTrailingComments: '1' 61 | SpacesInAngles: 'true' 62 | SpacesInCStyleCastParentheses: 'false' 63 | SpacesInContainerLiterals: 'false' 64 | SpacesInParentheses: 'false' 65 | SpacesInSquareBrackets: 'false' 66 | Standard: Latest 67 | TabWidth: '4' 68 | UseTab: Never 69 | 70 | PenaltyReturnTypeOnItsOwnLine: 10000 71 | 72 | BreakBeforeBraces: Custom 73 | BraceWrapping: 74 | AfterEnum: true 75 | AfterStruct: false 76 | AfterClass: false 77 | AfterFunction: false 78 | AfterNamespace: true 79 | AfterExternBlock: true 80 | BeforeElse: false 81 | BeforeLambdaBody: false 82 | BeforeWhile: false 83 | SplitEmptyFunction: false 84 | SplitEmptyRecord: false 85 | 86 | ... 87 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*' 3 | WarningsAsErrors: '' 4 | HeaderFilterRegex: '.*' 5 | AnalyzeTemporaryDtors: false 6 | FormatStyle: none 7 | User: Work 8 | CheckOptions: 9 | - key: llvm-else-after-return.WarnOnConditionVariables 10 | value: 'false' 11 | - key: modernize-loop-convert.MinConfidence 12 | value: reasonable 13 | - key: modernize-replace-auto-ptr.IncludeStyle 14 | value: llvm 15 | - key: cert-str34-c.DiagnoseSignedUnsignedCharComparisons 16 | value: 'false' 17 | - key: google-readability-namespace-comments.ShortNamespaceLines 18 | value: '10' 19 | - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField 20 | value: 'false' 21 | - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 22 | value: 'true' 23 | - key: cert-dcl16-c.NewSuffixes 24 | value: 'L;LL;LU;LLU' 25 | - key: google-readability-braces-around-statements.ShortStatementLines 26 | value: '1' 27 | - key: modernize-pass-by-value.IncludeStyle 28 | value: llvm 29 | - key: google-readability-namespace-comments.SpacesBeforeComments 30 | value: '2' 31 | - key: modernize-loop-convert.MaxCopySize 32 | value: '16' 33 | - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors 34 | value: 'true' 35 | - key: modernize-use-nullptr.NullMacros 36 | value: 'NULL' 37 | - key: llvm-qualified-auto.AddConstToQualified 38 | value: 'false' 39 | - key: modernize-loop-convert.NamingStyle 40 | value: CamelCase 41 | - key: llvm-else-after-return.WarnOnUnfixable 42 | value: 'false' 43 | - key: google-readability-function-size.StatementThreshold 44 | value: '800' 45 | ... 46 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.202.5/containers/docker-existing-dockerfile 3 | { 4 | "name": "Existing Dockerfile", 5 | "runArgs": ["--init"], 6 | 7 | // Sets the run context to one level up instead of the .devcontainer folder. 8 | "context": "..", 9 | 10 | // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. 11 | "dockerFile": "../DockerFile", 12 | 13 | // Set *default* container specific settings.json values on container create. 14 | "settings": {}, 15 | 16 | // Add the IDs of extensions you want installed when the container is created. 17 | "extensions": [] 18 | 19 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 20 | // "forwardPorts": [], 21 | 22 | // Uncomment the next line to run commands after the container is created - for example installing curl. 23 | // "postCreateCommand": "apt-get update && apt-get install -y curl", 24 | 25 | // Uncomment when using a ptrace-based debugger like C++, Go, and Rust 26 | // "runArgs": [ "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" ], 27 | 28 | // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker. 29 | // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ], 30 | 31 | // Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root. 32 | // "remoteUser": "vscode" 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | # Indicates the location of the vcpkg as a Git submodule of the project repository. 11 | VCPKG_ROOT: ${{ github.workspace }}/vcpkg 12 | # Tells vcpkg where binary packages are stored. 13 | VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/vcpkg/bincache 14 | # Use specific vcpkg version 15 | VCPKG_COMMIT_ID: '8e7e48eb0f61427bd5c18ba80bac7f33aa184ba5' 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-22.04 20 | 21 | timeout-minutes: 15 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | with: 26 | submodules: true 27 | 28 | - name: Install dependencies 29 | run: | 30 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 31 | sudo apt-get update 32 | sudo apt-get install -y \ 33 | gpg wget curl zip unzip tar git pkg-config \ 34 | ninja-build clang-tidy cppcheck ccache build-essential \ 35 | doctest-dev 36 | 37 | - name: "Create directory '${{ env.VCPKG_DEFAULT_BINARY_CACHE }}'" 38 | run: mkdir -p $VCPKG_DEFAULT_BINARY_CACHE 39 | shell: bash 40 | 41 | - name: Restore artifacts, or setup vcpkg 42 | uses: lukka/get-cmake@latest 43 | 44 | - name: Setup vcpkg 45 | uses: lukka/run-vcpkg@v10 46 | with: 47 | vcpkgGitCommitId: '${{ env.VCPKG_COMMIT_ID }}' 48 | vcpkgJsonGlob: 'vcpkg.json' 49 | 50 | - name: Run cmake 51 | uses: lukka/run-cmake@v10 52 | with: 53 | configurePreset: 'ninja-multi-vcpkg-full' 54 | buildPreset: 'ninja-vcpkg-deb-full' 55 | testPreset: 'ninja-vcpkg-deb-full' 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Build 35 | /build 36 | /builds 37 | .cache/ 38 | CMakeCache.txt 39 | 40 | Testing/ 41 | 42 | # Install 43 | install/* 44 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v3.2.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | - repo: https://github.com/pocc/pre-commit-hooks 12 | rev: v1.3.4 13 | hooks: 14 | - id: clang-format 15 | - id: cppcheck 16 | args: [--enable=all, 17 | --library=doctest, 18 | --suppress=missingIncludeSystem, 19 | --suppress=unusedFunction, 20 | --suppress=unmatchedSuppression, 21 | --suppress=preprocessorErrorDirective, 22 | --suppress=noValidConfiguration] 23 | - id: cpplint 24 | - repo: https://github.com/cheshirekow/cmake-format-precommit 25 | rev: v0.6.10 26 | hooks: 27 | - id: cmake-format 28 | args: [--config=.cmake-format.py] 29 | - id: cmake-lint 30 | args: [--config=.cmake-format.py] 31 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | cmake_minimum_required(VERSION 3.22) 4 | 5 | # Fix behavior of CMAKE_CXX_STANDARD when targeting macOS. 6 | if (POLICY CMP0025) 7 | cmake_policy(SET CMP0025 NEW) 8 | endif () 9 | 10 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") 11 | 12 | project( 13 | gap 14 | LANGUAGES C CXX 15 | VERSION 0.0.0 16 | DESCRIPTION "A utility library to bridge llvm and mlir gaps" 17 | HOMEPAGE_URL "https://github.com/lifting-bits/gap" 18 | ) 19 | 20 | # Globally set the required C++ standard 21 | set(CMAKE_CXX_STANDARD 20) 22 | set(CMAKE_CXX_EXTENSIONS OFF) 23 | 24 | include(GNUInstallDirs) 25 | 26 | # check if gap is being used directly or via add_subdirectory, 27 | # but allow overriding 28 | if(NOT DEFINED GAP_MASTER_PROJECT) 29 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 30 | set(GAP_MASTER_PROJECT ON) 31 | else() 32 | set(GAP_MASTER_PROJECT OFF) 33 | endif() 34 | endif() 35 | 36 | include(project_settings) 37 | include(utils) 38 | 39 | # adds header paths of library to list named var 40 | function(add_headers lib var) 41 | set(headers ${${var}}) 42 | foreach (header ${ARGN}) 43 | set(headers ${headers} ${CMAKE_CURRENT_SOURCE_DIR}/include/gap/${lib}/${header}) 44 | endforeach() 45 | set(${var} ${headers} PARENT_SCOPE) 46 | endfunction() 47 | 48 | # adds source paths of library to list named var 49 | function(add_sources lib var) 50 | set(sources ${${var}}) 51 | foreach (source ${ARGN}) 52 | set(sources ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/src/${source}) 53 | endforeach() 54 | set(${var} ${sources} PARENT_SCOPE) 55 | endfunction() 56 | 57 | # 58 | # CCACHE 59 | # 60 | find_program(CCACHE_PROGRAM ccache) 61 | if (CCACHE_PROGRAM) 62 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") 63 | endif () 64 | 65 | # 66 | # GAP build settings 67 | # 68 | add_library(gap-settings INTERFACE) 69 | add_library(gap::gap-settings ALIAS gap-settings) 70 | 71 | option(GAP_ENABLE_WARNINGS "Enable extended warnings" ON) 72 | 73 | if(GAP_ENABLE_WARNINGS) 74 | include(compiler_warnings) 75 | set_project_warnings(gap-settings) 76 | endif() 77 | 78 | target_include_directories(gap-settings 79 | INTERFACE 80 | $ 81 | $ 82 | ) 83 | 84 | # sanitizer options if supported by compiler 85 | include(sanitizers) 86 | enable_sanitizers(gap-settings) 87 | 88 | # allow for static analysis options 89 | include(static_analyzers) 90 | 91 | option(GAP_ENABLE_COROUTINES "Enable coroutines" ON) 92 | 93 | if (${GAP_ENABLE_COROUTINES}) 94 | find_package(Coroutines COMPONENTS Experimental Final REQUIRED) 95 | target_compile_definitions(gap-settings INTERFACE GAP_ENABLE_COROUTINES) 96 | target_link_libraries(gap-settings INTERFACE std::coroutines) 97 | endif() 98 | 99 | # 100 | # Core GAP libraries 101 | # 102 | 103 | add_subdirectory(core) 104 | add_subdirectory(coro) 105 | add_subdirectory(graph) 106 | 107 | option(GAP_ENABLE_SARIF "Enable SARIF support" 108 | $ 109 | ) 110 | 111 | if (${GAP_ENABLE_SARIF}) 112 | add_subdirectory(sarif) 113 | endif() 114 | 115 | option(GAP_ENABLE_MLIR "Enable MLIR support" OFF) 116 | 117 | if (${GAP_ENABLE_MLIR}) 118 | find_package(LLVM REQUIRED CONFIG) 119 | find_package(MLIR REQUIRED CONFIG) 120 | list(APPEND CMAKE_MODULE_PATH "${MLIR_CMAKE_DIR}") 121 | include(AddMLIR) 122 | 123 | add_subdirectory(mlir) 124 | endif() 125 | 126 | set(GAP_HEADERS 127 | ${GAP_CORE_HEADERS} 128 | ${GAP_CORO_HEADERS} 129 | ) 130 | 131 | add_library(gap INTERFACE) 132 | add_library(gap::gap ALIAS gap) 133 | 134 | target_link_libraries(gap 135 | INTERFACE 136 | gap-core 137 | gap-coro 138 | gap-settings 139 | ) 140 | 141 | set_target_properties(gap PROPERTIES PUBLIC_HEADER "${GAP_HEADERS}") 142 | 143 | # 144 | # testing support 145 | # 146 | option(GAP_ENABLE_TESTING "Enable GAP Test Builds" ON) 147 | 148 | if (GAP_ENABLE_TESTING) 149 | include(CTest) 150 | enable_testing() 151 | add_subdirectory(test) 152 | endif () 153 | 154 | # 155 | # instalation support 156 | # 157 | 158 | option(GAP_INSTALL "Generate the install target." ${GAP_MASTER_PROJECT}) 159 | 160 | set_property( 161 | TARGET gap PROPERTY POSITION_INDEPENDENT_CODE ON 162 | ) 163 | 164 | set_target_properties(${PROJECT_NAME} 165 | PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} 166 | ) 167 | 168 | if (GAP_INSTALL) 169 | set(GAP_CMAKE_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} CACHE PATH "Path where CMake utilities will be installed") 170 | 171 | set(GAP_INSTALL_TARGETS gap gap-core gap-coro gap-settings) 172 | set(GAP_EXPORT_NAME gapTargets) 173 | 174 | install_gap_target(gap core) 175 | install_gap_target(gap-core core) 176 | install_gap_target(gap-settings core) 177 | install_gap_target(gap-coro coro) 178 | install_gap_target(gap-graph graph) 179 | 180 | if (${GAP_ENABLE_SARIF}) 181 | install_gap_target(gap-sarif sarif) 182 | endif() 183 | 184 | if (${GAP_ENABLE_MLIR}) 185 | install_gap_target(gap-mlir mlir) 186 | endif() 187 | 188 | # 189 | # packaging support 190 | # 191 | 192 | set(CPACK_PACKAGE_VENDOR "Trail of Bits") 193 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY 194 | "A utility library to bridge llvm and mlir gaps." 195 | ) 196 | set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_PROJECT_VERSION_MAJOR}) 197 | set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR}) 198 | set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH}) 199 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 200 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 201 | 202 | include(CPack) 203 | 204 | set(GAP_CONFIG_NAME "${PROJECT_NAME}Config") 205 | set(GAP_PACKAGE_CONFIG_FILE "${GAP_CONFIG_NAME}.cmake") 206 | set(GAP_PACKAGE_CONFIG_VERSION_FILE "${GAP_CONFIG_NAME}Version.cmake") 207 | 208 | include(CMakePackageConfigHelpers) 209 | 210 | configure_package_config_file( 211 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${GAP_CONFIG_NAME}.cmake.in" 212 | "${CMAKE_CURRENT_BINARY_DIR}/${GAP_PACKAGE_CONFIG_FILE}" 213 | INSTALL_DESTINATION ${GAP_CMAKE_INSTALL_DIR} 214 | ) 215 | 216 | write_basic_package_version_file( 217 | ${GAP_PACKAGE_CONFIG_VERSION_FILE} 218 | VERSION ${PACKAGE_VERSION} 219 | COMPATIBILITY SameMajorVersion 220 | ) 221 | 222 | install(FILES 223 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindCoroutines.cmake" 224 | "${CMAKE_CURRENT_BINARY_DIR}/${GAP_PACKAGE_CONFIG_FILE}" 225 | "${CMAKE_CURRENT_BINARY_DIR}/${GAP_PACKAGE_CONFIG_VERSION_FILE}" 226 | DESTINATION ${GAP_CMAKE_INSTALL_DIR} 227 | ) 228 | 229 | install(FILES 230 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/compiler_warnings.cmake" 231 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/sanitizers.cmake" 232 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/static_analyzers.cmake" 233 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/project_settings.cmake" 234 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/prevent_in_source_builds.cmake" 235 | DESTINATION ${GAP_CMAKE_INSTALL_DIR} 236 | ) 237 | 238 | endif() 239 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 22, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "with-doctest", 11 | "hidden": true, 12 | "description": "Use doctest package", 13 | "cacheVariables": { 14 | "VCPKG_MANIFEST_FEATURES": "with-doctest" 15 | } 16 | }, 17 | { 18 | "name": "all-vcpkg-features", 19 | "hidden": true, 20 | "description": "Use doctest package and enable SARIF", 21 | "cacheVariables": { 22 | "VCPKG_MANIFEST_FEATURES": "with-doctest;sarif" 23 | } 24 | }, 25 | { 26 | "name": "ninja-multi-vcpkg", 27 | "displayName": "Ninja Multi-Config Configure Settings", 28 | "description": "Configure with vcpkg toolchain", 29 | "binaryDir": "${sourceDir}/builds/${presetName}", 30 | "generator": "Ninja Multi-Config", 31 | "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", 32 | "installDir": "~/opt/gap", 33 | "cacheVariables": { 34 | "CMAKE_CONFIGURATION_TYPES": "Release;RelWithDebInfo;Debug" 35 | } 36 | }, 37 | { 38 | "name": "ninja-multi-vcpkg-full", 39 | "displayName": "Ninja Multi-Config Configure Settings with doctest", 40 | "inherits": [ "ninja-multi-vcpkg", "all-vcpkg-features" ] 41 | } 42 | ], 43 | 44 | "buildPresets": [ 45 | { 46 | "name": "ninja-vcpkg-deb", 47 | "configurePreset": "ninja-multi-vcpkg", 48 | "displayName": "Build ninja-multi-vcpkg-debug", 49 | "configuration": "Debug" 50 | }, 51 | { 52 | "name": "ninja-vcpkg-rel", 53 | "configurePreset": "ninja-multi-vcpkg", 54 | "displayName": "Build ninja-multi-vcpkg-release", 55 | "configuration": "Release" 56 | }, 57 | { 58 | "name": "ninja-vcpkg-deb-full", 59 | "configurePreset": "ninja-multi-vcpkg-full", 60 | "displayName": "Build ninja-multi-vcpkg-debug-full", 61 | "configuration": "Debug" 62 | }, 63 | { 64 | "name": "ninja-vcpkg-rel-full", 65 | "configurePreset": "ninja-multi-vcpkg-full", 66 | "displayName": "Build ninja-multi-vcpkg-release-full", 67 | "configuration": "Release" 68 | } 69 | ], 70 | 71 | "testPresets": [ 72 | { 73 | "name": "ninja-vcpkg-deb", 74 | "configurePreset": "ninja-multi-vcpkg", 75 | "output": { 76 | "verbosity": "verbose" 77 | }, 78 | "configuration": "Debug" 79 | }, 80 | { 81 | "name": "ninja-vcpkg-deb-full", 82 | "configurePreset": "ninja-multi-vcpkg-full", 83 | "output": { 84 | "verbosity": "verbose" 85 | }, 86 | "configuration": "Debug" 87 | } 88 | ] 89 | } 90 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @xlauko 2 | -------------------------------------------------------------------------------- /CPPLINT.cfg: -------------------------------------------------------------------------------- 1 | filter=-build/c++11 2 | 3 | filter=-build/header_guard 4 | filter=-build/include_order 5 | filter=+build/pragma_once 6 | 7 | filter=-whitespace/line_length 8 | filter=-whitespace/braces 9 | filter=-whitespace/indent 10 | filter=-whitespace/comments 11 | 12 | filter=-runtime/indentation_namespace 13 | filter=-runtime/references 14 | 15 | filter=-readability/braces 16 | -------------------------------------------------------------------------------- /DockerFile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest AS base 2 | 3 | ARG DEBIAN_FRONTEND=noninteractive 4 | 5 | RUN apt-get update -y 6 | RUN apt-get install software-properties-common -y 7 | RUN add-apt-repository ppa:ubuntu-toolchain-r/test 8 | 9 | RUN apt-get update -y 10 | RUN apt-get install -y gpg wget curl zip unzip tar git pkg-config gcc-11 g++-11 11 | 12 | RUN wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null 13 | RUN echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ bionic main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null 14 | 15 | RUN apt-get update -y 16 | RUN apt-get install -y clang-tidy cppcheck clang-12 cmake ninja-built libc++-12-dev libc++abi-12-dev 17 | 18 | FROM base AS vcpkg 19 | 20 | RUN git clone https://github.com/Microsoft/vcpkg.git /opt/vcpkg 21 | 22 | WORKDIR /opt/vcpkg 23 | 24 | RUN ./bootstrap-vcpkg.sh && ./vcpkg integrate install && \ 25 | ./vcpkg integrate bash && \ 26 | echo 'export PATH=$PATH:/opt/vcpkg' >>~/.bashrc \ 27 | echo 'export VCPKG_ROOT=/opt/vcpkg' >>~/.bashrc 28 | 29 | WORKDIR /root 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build and Test](https://github.com/lifting-bits/gap/actions/workflows/build.yml/badge.svg)](https://github.com/lifting-bits/gap/actions/workflows/build.yml) 2 | 3 | # GAP 4 | A utility library to bridge llvm and mlir gaps. 5 | 6 | ## Build & Configure 7 | NOTE: if using the vcpkg preset, `VCPKG_ROOT` must be set to the root directory of the vcpkg instance 8 | 9 | ``` 10 | cmake --preset ninja-multi-vcpkg 11 | ``` 12 | 13 | ``` 14 | cmake --build --preset ninja-multi-vcpkg 15 | ``` 16 | 17 | ## Install & Integrate 18 | 19 | Simply use cmake install infrastructure: 20 | 21 | ``` 22 | cmake -DCMAKE_INSTALL_PREFIX:PATH= --build --preset ninja-multi-vcpkg --target install 23 | ``` 24 | 25 | where installation path might be for example `~/opt/gap`. 26 | 27 | To integrate into other `cmake` project simply include: 28 | 29 | ``` 30 | find_package(gap CONFIG REQUIRED) 31 | ``` 32 | 33 | and point `cmake` to `gap` install directory: 34 | 35 | ``` 36 | cmake -Dgap_DIR=~/opt/gap/lib/cmake/gap ... 37 | ``` 38 | 39 | ## Test 40 | 41 | ``` 42 | ctest ./builds/ --preset ninja-multi-vcpkg 43 | ``` 44 | 45 | ## Dependencies 46 | 47 | | Name | Version | 48 | | ---- | ------- | 49 | | [Git](https://git-scm.com/) | Latest | 50 | | [CMake](https://cmake.org/) | 3.21+ | 51 | | [Clang](http://clang.llvm.org/) | 12+ | 52 | | [ccache](https://ccache.dev/) | Latest | 53 | 54 | Cmake takes care of downloading and building the vcpkg dependencies listed in `vcpkg.json`. 55 | 56 | ## Dependencies for development 57 | 58 | - [pre-commit](https://pre-commit.com/) `pip install pre-commit` 59 | - [cmake-format](https://cmake-format.readthedocs.io/en/latest/) `pip install cmakelang` 60 | recommended together with vscode cmake-format extension 61 | 62 | ## Structure 63 | 64 | 1. __Core GAP__ 65 | 66 | - contracts 67 | - error handling 68 | - parsing 69 | - logging 70 | - coroutines 71 | - ranges 72 | - benchmarks 73 | - data structures 74 | - cmake utils 75 | 76 | 2. __LLVM GAP__ 77 | 78 | - ranges 79 | - single context library (sc) 80 | - monadic builders 81 | 82 | 3. __MLIR GAP__ 83 | 84 | - parser 85 | - fmt 86 | - single context library (sc) 87 | - ranges 88 | 89 | 4. __Any IR GAP__ 90 | 91 | - serialize/deserialize uniform ir representation 92 | -------------------------------------------------------------------------------- /cmake/compiler_warnings.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | # setups available compiler warnings 4 | function (set_project_warnings project) 5 | option(GAP_WARNINGS_AS_ERRORS "Treat compiler warnings as errors" TRUE) 6 | 7 | set(clang_warnings 8 | -Wall 9 | -Wextra # reasonable and standard 10 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 11 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual 12 | # destructor. This helps catch hard to track down memory errors 13 | -Wold-style-cast # warn for c-style casts 14 | -Wcast-align # warn for potential performance problem casts 15 | -Wunused # warn on anything being unused 16 | -Woverloaded-virtual # warn if you overload (not override) a virtual function 17 | -Wpedantic # warn if non-standard C++ is used 18 | -Wconversion # warn on type conversions that may lose data 19 | -Wsign-conversion # warn on sign conversions 20 | -Wnull-dereference # warn if a null dereference is detected 21 | -Wdouble-promotion # warn if float is implicit promoted to double 22 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 23 | ) 24 | 25 | if (GAP_WARNINGS_AS_ERRORS) 26 | set(clang_warnings ${clang_warnings} -Werror) 27 | endif () 28 | 29 | set(gcc_warnings 30 | ${clang_warnings} 31 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 32 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 33 | -Wduplicated-branches # warn if if / else branches have duplicated code 34 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 35 | -Wuseless-cast # warn if you perform a cast to the same type 36 | ) 37 | 38 | if (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 39 | set(project_warnings ${clang_warnings}) 40 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 41 | set(project_warnings ${gcc_warnings}) 42 | else () 43 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 44 | endif () 45 | 46 | target_compile_options(${project} INTERFACE ${project_warnings}) 47 | 48 | endfunction () 49 | -------------------------------------------------------------------------------- /cmake/gapConfig.cmake.in: -------------------------------------------------------------------------------- 1 | include(CMakeFindDependencyMacro) 2 | 3 | @PACKAGE_INIT@ 4 | 5 | # Provide path for scripts 6 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") 7 | 8 | if (@GAP_ENABLE_COROUTINES@) 9 | set( CMAKE_CXX_STANDARD 20 ) 10 | set( CMAKE_CXX_EXTENSIONS OFF ) 11 | 12 | find_dependency(Coroutines COMPONENTS Experimental Final REQUIRED) 13 | endif() 14 | 15 | if (@GAP_ENABLE_SARIF@) 16 | find_dependency(nlohmann_json CONFIG REQUIRED) 17 | endif() 18 | 19 | include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") 20 | check_required_components("@PROJECT_NAME@") 21 | 22 | if (NOT DEFINED @PROJECT_NAME@_FIND_QUIETLY) 23 | message(STATUS "Found Gap: ${CMAKE_CURRENT_LIST_DIR}") 24 | endif() 25 | -------------------------------------------------------------------------------- /cmake/prevent_in_source_builds.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | # This function will prevent in-source builds 4 | function (assure_out_of_source_builds) 5 | # make sure the user doesn't play dirty with symlinks 6 | get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) 7 | get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) 8 | 9 | # disallow in-source builds 10 | if ("${srcdir}" STREQUAL "${bindir}") 11 | message("######################################################") 12 | message("Warning: in-source builds are disabled") 13 | message("Please create a separate build directory and run cmake from there") 14 | message("######################################################") 15 | message(FATAL_ERROR "Quitting configuration") 16 | endif () 17 | endfunction () 18 | 19 | assure_out_of_source_builds() 20 | -------------------------------------------------------------------------------- /cmake/project_settings.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2022-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | # generate a compile commands JSON file. 4 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 5 | 6 | # Set a default build type if none was specified 7 | if( NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES ) 8 | message( STATUS "Setting build type to 'RelWithDebInfo' as none was specified." ) 9 | set(CMAKE_BUILD_TYPE 10 | RelWithDebInfo 11 | CACHE STRING "Choose the type of build." FORCE) 12 | # Set the possible values of build type for cmake-gui, ccmake 13 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" 14 | "MinSizeRel" "RelWithDebInfo") 15 | endif() 16 | 17 | find_program( CCACHE ccache ) 18 | if ( CCACHE ) 19 | message( STATUS "Using ccache" ) 20 | set( CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE} ) 21 | else() 22 | message( WARNING "CCache was not found." ) 23 | endif() 24 | 25 | # Generate compile_commands.json to make it easier to work with clang based tools 26 | set( CMAKE_EXPORT_COMPILE_COMMANDS ON ) 27 | 28 | option( ENABLE_IPO 29 | "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF 30 | ) 31 | 32 | if( ENABLE_IPO ) 33 | include( CheckIPOSupported ) 34 | check_ipo_supported( RESULT res OUTPUT out ) 35 | if( res ) 36 | set( CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE ) 37 | else() 38 | message( SEND_ERROR "IPO is not supported: ${out}" ) 39 | endif() 40 | endif() 41 | -------------------------------------------------------------------------------- /cmake/sanitizers.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | # add sanitizer options for a targer 4 | function (enable_sanitizers target) 5 | 6 | if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 7 | option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" FALSE) 8 | 9 | if (ENABLE_COVERAGE) 10 | target_compile_options(${target} INTERFACE --coverage -O0 -g) 11 | target_link_libraries(${target} INTERFACE --coverage) 12 | endif () 13 | 14 | set(sanitizers "") 15 | 16 | option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) 17 | if (ENABLE_SANITIZER_ADDRESS) 18 | list(APPEND sanitizers "address") 19 | endif () 20 | 21 | option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) 22 | if (ENABLE_SANITIZER_MEMORY) 23 | list(APPEND sanitizers "memory") 24 | endif () 25 | 26 | option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer" OFF) 27 | if (ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) 28 | list(APPEND sanitizers "undefined") 29 | endif () 30 | 31 | option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) 32 | if (ENABLE_SANITIZER_THREAD) 33 | list(APPEND sanitizers "thread") 34 | endif () 35 | 36 | list(JOIN sanitizers "," list_of_sanitizers) 37 | 38 | endif () 39 | 40 | if (list_of_sanitizers) 41 | if (NOT "${list_of_sanitizers}" STREQUAL "") 42 | target_compile_options(${target} INTERFACE -fsanitize=${list_of_sanitizers}) 43 | target_link_libraries(${target} INTERFACE -fsanitize=${list_of_sanitizers}) 44 | endif () 45 | endif () 46 | 47 | endfunction () 48 | -------------------------------------------------------------------------------- /cmake/static_analyzers.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | option(ENABLE_CPPCHECK "Enable static analysis with cppcheck" OFF) 4 | option(ENABLE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF) 5 | option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable static analysis with include-what-you-use" OFF) 6 | 7 | if (ENABLE_CPPCHECK) 8 | find_program(CPPCHECK cppcheck) 9 | if (CPPCHECK) 10 | set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --suppress=missingInclude --enable=all --inconclusive) 11 | else () 12 | message(SEND_ERROR "cppcheck requested but executable not found") 13 | endif () 14 | endif () 15 | 16 | if (ENABLE_CLANG_TIDY) 17 | find_program(CLANGTIDY clang-tidy) 18 | if (CLANGTIDY) 19 | set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) 20 | else () 21 | message(SEND_ERROR "clang-tidy requested but executable not found") 22 | endif () 23 | endif () 24 | 25 | if (ENABLE_INCLUDE_WHAT_YOU_USE) 26 | find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) 27 | if (INCLUDE_WHAT_YOU_USE) 28 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) 29 | else () 30 | message(SEND_ERROR "include-what-you-use requested but executable not found") 31 | endif () 32 | endif () 33 | -------------------------------------------------------------------------------- /cmake/utils.cmake: -------------------------------------------------------------------------------- 1 | function(add_gap_library name headers) 2 | add_library(${name} INTERFACE) 3 | 4 | add_library(gap::${name} ALIAS ${name}) 5 | 6 | target_compile_features(${name} INTERFACE cxx_std_20) 7 | 8 | target_include_directories(${name} 9 | INTERFACE 10 | $ 11 | $ 12 | ) 13 | 14 | set_target_properties(${name} PROPERTIES PUBLIC_HEADER "${headers}") 15 | 16 | target_link_libraries(${name} INTERFACE gap-settings) 17 | endfunction() 18 | 19 | function(add_gap_static_library name headers sources) 20 | cmake_parse_arguments(ARG "" "" "SYSTEM_INCLUDE_DIRECTORIES;LINK_LIBRARIES" ${ARGN}) 21 | add_library(${name} STATIC ${sources}) 22 | 23 | add_library(gap::${name} ALIAS ${name}) 24 | 25 | target_compile_features(${name} INTERFACE cxx_std_20) 26 | 27 | target_include_directories(${name} 28 | SYSTEM PUBLIC 29 | ${ARG_SYSTEM_INCLUDE_DIRECTORIES} 30 | PUBLIC 31 | $ 32 | $ 33 | ) 34 | 35 | set_target_properties(${name} PROPERTIES PUBLIC_HEADER "${headers}") 36 | 37 | target_link_libraries(${name} 38 | PUBLIC 39 | ${ARG_LINK_LIBRARIES} 40 | INTERFACE 41 | gap-settings 42 | ) 43 | endfunction() 44 | 45 | function(install_gap_target name include_dir) 46 | set(GAP_EXPORT_NAME gapTargets) 47 | 48 | install(TARGETS ${name} 49 | EXPORT ${GAP_EXPORT_NAME} 50 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 51 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 52 | NAMELINK_SKIP 53 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 54 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gap/${include_dir} 55 | ) 56 | 57 | install(EXPORT ${GAP_EXPORT_NAME} 58 | FILE ${GAP_EXPORT_NAME}.cmake 59 | NAMESPACE gap:: 60 | DESTINATION ${GAP_CMAKE_INSTALL_DIR} 61 | ) 62 | 63 | install(TARGETS ${name} 64 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 65 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 66 | NAMELINK_ONLY 67 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 68 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/gap/${include_dir} 69 | ) 70 | endfunction() 71 | -------------------------------------------------------------------------------- /core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | add_headers(core GAP_CORE_HEADERS 4 | bigint.hpp 5 | common.hpp 6 | concepts.hpp 7 | config.hpp 8 | coroutine.hpp 9 | crtp.hpp 10 | dense_map.hpp 11 | generator.hpp 12 | hash.hpp 13 | memoize.hpp 14 | optional.hpp 15 | overloads.hpp 16 | parser.hpp 17 | pointer.hpp 18 | ranges.hpp 19 | recursive_generator.hpp 20 | source_location.hpp 21 | strong_type.hpp 22 | fixed_string.hpp 23 | type_traits.hpp 24 | union_find.hpp 25 | ) 26 | 27 | add_gap_library(gap-core "${GAP_CORE_HEADERS}") 28 | -------------------------------------------------------------------------------- /core/include/gap/core/benchmark.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace gap::bench 9 | { 10 | template< typename R > 11 | concept sample = gap::ranges::arithmetic_range< R >; 12 | 13 | template< sample S > 14 | using sample_type = typename S::value_type; 15 | 16 | constexpr double sum(sample auto s) { return gap::ranges::accumulate(s, 0); } 17 | 18 | constexpr double mean(sample auto s) { return sum(s) / ssize(s); } 19 | 20 | constexpr double standard_deviation(sample auto s) { 21 | double avg = mean(s); 22 | auto deviation = [avg](double acc, auto val) { return acc + (avg - val) * (avg - val); }; 23 | return gap::ranges::accumulate(s, 0.0, deviation) / ssize(s); 24 | } 25 | 26 | } // namespace gap::bench 27 | -------------------------------------------------------------------------------- /core/include/gap/core/common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | namespace gap 6 | { 7 | using radix_t = std::uint8_t; 8 | using bitwidth_t = std::size_t; 9 | 10 | } // namespace gap 11 | -------------------------------------------------------------------------------- /core/include/gap/core/concepts.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include // for forward 11 | 12 | namespace gap 13 | { 14 | namespace detail 15 | { 16 | template< typename T > 17 | using with_ref = T&; 18 | } // namespace detail 19 | 20 | /* scalar values concepts */ 21 | template< typename T > 22 | concept arithmetic = std::is_arithmetic_v< T >; 23 | 24 | template< typename T > 25 | concept integral = std::is_integral_v< T >; 26 | 27 | template< typename T > 28 | concept signed_integral = integral< T > && std::is_signed_v< T >; 29 | 30 | template< typename T > 31 | concept unsigned_integral = integral< T > && !signed_integral< T >; 32 | 33 | template< typename T > 34 | concept floating = std::is_floating_point_v< T >; 35 | 36 | /* value concepts */ 37 | template< typename T > 38 | concept class_or_enum = std::is_class_v< T > || std::is_union_v< T > || std::is_enum_v< T >; 39 | 40 | template< typename R > 41 | inline constexpr bool is_array_like = std::is_array_v< std::remove_reference_t< R > >; 42 | 43 | template< typename R > 44 | inline constexpr bool is_class_like = class_or_enum< std::remove_reference_t< R > >; 45 | 46 | template< typename R > 47 | inline constexpr bool is_bounded_array_like 48 | = std::is_bounded_array_v< std::remove_reference_t< R > >; 49 | 50 | template< typename T > 51 | concept pointer_type = std::is_pointer_v< std::remove_reference_t< T > >; 52 | 53 | template< typename T > 54 | concept can_reference = requires { 55 | typename detail::with_ref< T >; 56 | }; 57 | 58 | template< typename T, typename... Args > 59 | concept constructible_from = std::destructible< T > && std::is_constructible_v< T, Args... >; 60 | 61 | /* common concepts */ 62 | template< typename T > 63 | concept printable = requires(std::ostream& os, T t) { 64 | { os << t } -> std::same_as< std::ostream& >; 65 | }; 66 | 67 | template< typename F, typename... Args > 68 | concept invocable = requires(F&& f, Args&&... args) { 69 | std::invoke(std::forward< F >(f), std::forward< Args >(args)...); 70 | }; 71 | 72 | namespace detail { 73 | template< typename B > 74 | concept boolean_testable = std::convertible_to< B, bool >; 75 | } 76 | 77 | template< typename B > 78 | concept boolean_testable = detail::boolean_testable< B > && 79 | requires (B&& b) { 80 | { !std::forward(b) } -> detail::boolean_testable; 81 | }; 82 | 83 | template < class F, class... Args > 84 | concept predicate = std::regular_invocable && boolean_testable>; 85 | 86 | /* operator concepts */ 87 | template< typename T > 88 | concept incrementable = requires(T value) { 89 | ++value; 90 | value++; 91 | }; 92 | 93 | template< typename T > 94 | concept decrementable = requires(T value) { 95 | --value; 96 | value--; 97 | }; 98 | 99 | template< typename A, typename B > 100 | concept comparable = requires(const A& a, const B& b) { 101 | a == b; 102 | }; 103 | 104 | template< typename T, typename U > 105 | concept convertible_to = std::is_convertible_v< T, U >; 106 | 107 | template 108 | concept hashable = requires(T a) { 109 | { std::hash< std::decay_t< T > >{}(a) } -> convertible_to< std::size_t >; 110 | }; 111 | 112 | } // namespace gap 113 | -------------------------------------------------------------------------------- /core/include/gap/core/config.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | // Compiler Detection 6 | 7 | #if defined(_MSC_VER) 8 | #define GAP_COMPILER_MSVC _MSC_FULL_VER 9 | #else 10 | #define GAP_COMPILER_MSVC 0 11 | #endif 12 | 13 | #if defined(__clang__) 14 | #define GAP_COMPILER_CLANG \ 15 | (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) 16 | #else 17 | #define GAP_COMPILER_CLANG 0 18 | #endif 19 | 20 | #if defined(__GNUC__) 21 | #define GAP_COMPILER_GCC (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) 22 | #else 23 | #define GAP_COMPILER_GCC 0 24 | #endif 25 | 26 | #if GAP_COMPILER_CLANG 27 | #if __clang_major__ >= 7 28 | #define GAP_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 1 29 | #endif 30 | #endif 31 | #ifndef GAP_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 32 | #define GAP_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 0 33 | #endif 34 | 35 | #if GAP_COMPILER_MSVC 36 | #if __has_include() 37 | #include 38 | #ifdef __cpp_lib_coroutine 39 | #define GAP_COROHEADER_FOUND_AND_USABLE 40 | #endif 41 | #endif 42 | #else 43 | #if __has_include() 44 | #define GAP_COROHEADER_FOUND_AND_USABLE 45 | #endif 46 | #endif 47 | 48 | #if GAP_COMPILER_MSVC 49 | # define GAP_NOINLINE __declspec(noinline) 50 | #elif GAP_COMPILER_CLANG || GAP_COMPILER_GCC 51 | # define GAP_NOINLINE __attribute__((noinline)) 52 | #endif 53 | 54 | #if GAP_COMPILER_MSVC 55 | # define GAP_FORCE_INLINE __forceinline 56 | #elif GAP_COMPILER_CLANG 57 | # define GAP_FORCE_INLINE __attribute__((always_inline)) 58 | #else 59 | # define GAP_FORCE_INLINE inline 60 | #endif -------------------------------------------------------------------------------- /core/include/gap/core/coroutine.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #error "The gap/core/coroutine.hpp header was deprecated and removed. Please update your include paths to use gap/coro/coroutine.hpp instead." -------------------------------------------------------------------------------- /core/include/gap/core/crtp.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | namespace gap::core { 6 | 7 | template< typename T, template< typename > class crtp_type > 8 | struct crtp 9 | { 10 | T &underlying() { return static_cast< T & >(*this); } 11 | 12 | const T &underlying() const { return static_cast< const T & >(*this); } 13 | 14 | private: 15 | crtp() {} 16 | 17 | friend crtp_type< T >; 18 | }; 19 | 20 | } // namespace gap::core 21 | -------------------------------------------------------------------------------- /core/include/gap/core/dense_map.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace gap 11 | { 12 | 13 | // 14 | // Dense map stores map from integers to values in a flat container. 15 | // 16 | // It keeps empty slots for unmapped keys. 17 | // NOTE: The implementation uses default value as a flag to denote missing 18 | // value in the map. 19 | // 20 | template< 21 | gap::unsigned_integral key_t, 22 | typename value_t, 23 | typename container_t = std::vector< value_t > > 24 | struct dense_map { 25 | using key_type = key_t; 26 | using mapped_type = value_t; 27 | using value_type = std::pair< key_type, mapped_type >; 28 | 29 | using size_type = typename container_t::size_type; 30 | using reference = std::pair< key_type, mapped_type& >; 31 | using const_reference = std::pair< key_type, const mapped_type& >; 32 | 33 | using base_iterator = typename container_t::iterator; 34 | using base_const_iterator = typename container_t::const_iterator; 35 | 36 | container_t _container; 37 | 38 | constexpr dense_map() = default; 39 | 40 | template< typename reference_type > 41 | struct proxy { 42 | reference_type ref; 43 | reference_type* operator->() { return &ref; } 44 | }; 45 | 46 | template< typename base, typename reference_type > 47 | struct iterator_t { 48 | base _iter; 49 | key_type _index; 50 | 51 | constexpr iterator_t(key_type idx, base it) 52 | : _iter(it) 53 | , _index(idx) {} 54 | 55 | constexpr reference_type operator*() { return { _index, *_iter }; } 56 | constexpr proxy< reference_type > operator->() { 57 | return { 58 | {_index, *_iter} 59 | }; 60 | } 61 | 62 | constexpr bool operator<(iterator_t other) const { return _iter < other._iter; } 63 | constexpr bool operator==(iterator_t other) const { return _iter == other._iter; } 64 | 65 | constexpr iterator_t operator+(key_type key) const { 66 | return { _index + key, _iter + key }; 67 | } 68 | 69 | constexpr iterator_t& operator++() { 70 | ++_iter; 71 | ++_index; 72 | return *this; 73 | } 74 | 75 | constexpr iterator_t operator++(int) { 76 | auto rv = *this; 77 | ++*this; 78 | return rv; 79 | } 80 | 81 | constexpr iterator_t operator-(key_type key) const { 82 | return { _index - key, _iter - key }; 83 | } 84 | 85 | constexpr iterator_t& operator--() { 86 | --_iter; 87 | --_index; 88 | return *this; 89 | } 90 | 91 | constexpr iterator_t operator--(int) { 92 | auto rv = *this; 93 | --*this; 94 | return rv; 95 | } 96 | }; 97 | 98 | using const_iterator = iterator_t< base_const_iterator, const_reference >; 99 | using iterator = iterator_t< base_iterator, reference >; 100 | 101 | constexpr auto begin() { return iterator(0, _container.begin()); } 102 | constexpr auto begin() const { return cbegin(); } 103 | constexpr auto cbegin() const { return const_iterator(0, _container.cbegin()); } 104 | 105 | constexpr auto end() { return iterator(size(), _container.end()); } 106 | constexpr auto end() const { return cend(); } 107 | constexpr auto cend() const { return const_iterator(size(), _container.cend()); } 108 | 109 | constexpr auto empty() const { return _container.empty(); } 110 | constexpr key_type size() const { return key_type(_container.size()); } 111 | 112 | constexpr bool full() const { 113 | return std::all_of(_container.begin(), _container.end(), [](const auto& value) { 114 | return value != mapped_type(); 115 | }); 116 | } 117 | 118 | constexpr void clear() noexcept { _container.clear(); } 119 | 120 | constexpr bool _bump(key_type k) { 121 | if (size_t(k) < _container.size()) 122 | return false; 123 | else 124 | return _container.resize(k + 1), true; 125 | } 126 | 127 | constexpr mapped_type& operator[](key_type k) { 128 | _bump(k); 129 | return _container[k]; 130 | } 131 | 132 | constexpr std::pair< iterator, bool > insert(const_reference value) { 133 | key_type idx = value.first; 134 | bool is_new = _bump(value.first); 135 | if (is_new || _container[idx] == mapped_type()) 136 | _container[idx] = value.second; 137 | return { iterator(idx, _container.begin() + idx), is_new }; 138 | } 139 | 140 | constexpr std::pair< iterator, bool > emplace(key_type idx, mapped_type val) { 141 | return insert(std::make_pair(idx, val)); 142 | } 143 | 144 | constexpr void erase(iterator pos) { pos->second = mapped_type(); } 145 | constexpr void erase(key_type key) { _container[key] = mapped_type(); } 146 | 147 | constexpr size_type count(key_type key) const { 148 | return size_t(key) < size() && _container[key] != mapped_type(); 149 | } 150 | 151 | constexpr bool contains(key_type key) const { return count(key) == 1; } 152 | 153 | constexpr iterator find(key_type k) { return count(k) ? begin() + k : end(); } 154 | constexpr const_iterator find(key_type k) const { return count(k) ? cbegin() + k : end(); } 155 | 156 | constexpr bool operator<(const dense_map& o) const { return _container < o._container; } 157 | constexpr bool operator==(const dense_map& o) const { return _container == o._container; } 158 | }; 159 | 160 | } // namespace gap 161 | -------------------------------------------------------------------------------- /core/include/gap/core/fixed_string.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Trail of Bits, Inc. 3 | */ 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace gap 10 | { 11 | // Class 'fixed_string' implements a static string that can 12 | // be passed as a non type template parameter. 13 | 14 | namespace detail 15 | { 16 | template< std::size_t size > 17 | using const_char_array = const char (&)[size + 1]; 18 | } // namespace detail 19 | 20 | template< std::size_t size > 21 | struct fixed_string { 22 | using value_type = char; 23 | using container_t = std::array< value_type, size >; 24 | 25 | using reference = value_type &; 26 | using const_reference = const value_type &; 27 | using pointer = value_type *; 28 | using const_pointer = const value_type *; 29 | using iterator = typename container_t::iterator; 30 | using const_iterator = typename container_t::const_iterator; 31 | 32 | using const_char_array = detail::const_char_array< size >; 33 | 34 | constexpr fixed_string() = default; 35 | 36 | explicit constexpr fixed_string(const_char_array arr) noexcept { 37 | std::copy(arr, std::next(arr, size), _data.begin()); 38 | } 39 | 40 | constexpr fixed_string(const fixed_string &other) = default; 41 | constexpr fixed_string(fixed_string &&other) = default; 42 | 43 | constexpr fixed_string &operator=(const fixed_string &other) = default; 44 | constexpr fixed_string &operator=(fixed_string &&other) = default; 45 | 46 | constexpr iterator begin() noexcept { return _data.begin(); } 47 | constexpr const_iterator begin() const noexcept { return _data.begin(); } 48 | 49 | constexpr iterator end() noexcept { return _data.end(); } 50 | constexpr const_iterator end() const noexcept { return _data.end(); } 51 | 52 | constexpr reference front() noexcept { return _data.front(); } 53 | constexpr const_reference front() const noexcept { return _data.front(); } 54 | 55 | constexpr reference back() noexcept { return _data.back(); } 56 | constexpr const_reference back() const noexcept { return _data.back(); } 57 | 58 | constexpr std::size_t size() const noexcept { return _data.size(); } 59 | constexpr bool empty() const noexcept { return _data.empty(); } 60 | 61 | constexpr bool contains(char c) const noexcept { 62 | return std::find(begin(), end(), c) != end(); 63 | } 64 | 65 | constexpr reference operator[](std::size_t idx) noexcept { return _data[idx]; } 66 | constexpr const_reference operator[](std::size_t idx) const noexcept { return _data[idx]; } 67 | 68 | constexpr operator std::basic_string_view< char >() const noexcept { 69 | return std::basic_string_view< char >{ _data.begin(), _data.size() }; 70 | } 71 | 72 | constexpr const_char_array c_str() const { return _data.data(); } 73 | 74 | template< std::size_t from > 75 | constexpr auto substr() const noexcept { 76 | fixed_string< size - from > result{}; 77 | std::copy(std::next(begin(), from), end(), result.begin()); 78 | return result; 79 | } 80 | 81 | constexpr auto operator<=>(const fixed_string &other) const = default; 82 | 83 | template< typename ostream > 84 | friend ostream &operator<<(ostream &os, const fixed_string &s) { 85 | return os << static_cast< std::string_view >(s); 86 | } 87 | 88 | private: 89 | container_t _data; 90 | } 91 | 92 | template< std::size_t N, std::size_t M > 93 | constexpr auto operator+(const fixed_string< N > &a, const fixed_string< M > &b) { 94 | fixed_string< N + M > result{}; 95 | std::copy(a.begin(), a.end(), result.begin()); 96 | std::copy(b.begin(), b.end(), std::next(result.begin(), N)); 97 | return result; 98 | } 99 | 100 | template< std::size_t N, std::size_t M > 101 | requires(N != M) constexpr bool operator==( 102 | const fixed_string< N > &a, const fixed_string< M > &b) { 103 | return false; 104 | } 105 | 106 | template< std::size_t size > 107 | fixed_string(detail::const_char_array< size >) -> fixed_string< size - 1 >; 108 | 109 | template< std::size_t size > 110 | fixed_string(fixed_string< size >) -> fixed_string< size >; 111 | 112 | namespace 113 | { // compile-time tests 114 | static inline void tests() { 115 | static_assert(fixed_string("abc").size() == 3); 116 | static_assert(fixed_string("abc").back() == 'c'); 117 | static_assert(fixed_string("abc")[1] == 'b'); 118 | 119 | static_assert(fixed_string("a") + fixed_string("b") == fixed_string("ab")); 120 | static_assert(fixed_string("a") != fixed_string("aa")); 121 | } 122 | } // namespace 123 | 124 | } // namespace gap 125 | -------------------------------------------------------------------------------- /core/include/gap/core/generator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #error "The gap/core/generator.hpp header was deprecated and removed. Please update your include paths to use gap/coro/generator.hpp instead." -------------------------------------------------------------------------------- /core/include/gap/core/hash.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace gap 15 | { 16 | template< typename T > 17 | struct hash; 18 | 19 | // 20 | // hash_code serves to keep hash function state and pass it along 21 | // 22 | struct hash_code { 23 | constexpr explicit hash_code(std::size_t s) : state(s) {} 24 | constexpr operator std::size_t() const { return state; } 25 | 26 | std::size_t state; 27 | }; 28 | 29 | template< typename T > 30 | concept hash_code_like = convertible_to< T, hash_code >; 31 | 32 | namespace detail 33 | { 34 | // generic hash_combine inspired by boost.hash implementation 35 | constexpr hash_code hash_combine(std::size_t lhs, std::size_t rhs) { 36 | return hash_code(lhs ^ (rhs * 0x9e3779b9 + (lhs << 6) + (lhs >> 2))); 37 | } 38 | } // namespace detail 39 | 40 | constexpr hash_code operator%(const hash_code &lhs, const hash_code &rhs) { 41 | return hash_code{ detail::hash_combine(lhs.state, rhs.state) }; 42 | } 43 | 44 | namespace detail 45 | { 46 | template< hash_code_like... args_t > 47 | constexpr hash_code hash_combine_impl(const args_t&... args) { 48 | return hash_code((args % ...)); 49 | } 50 | 51 | } // namespace detail 52 | 53 | // 54 | // hash_value 55 | // 56 | template< typename T > 57 | constexpr hash_code hash_value(hash_code code, const T& val); 58 | 59 | 60 | // 61 | // hashing convenience helper functions 62 | // 63 | template< hash_code_like... args_t > 64 | constexpr hash_code hash_combine(const args_t&... args) { 65 | return detail::hash_combine_impl(args...); 66 | } 67 | 68 | template< ranges::range range_t > 69 | constexpr hash_code hash_combine_range(hash_code code, const range_t &range) { 70 | for (const auto &v : range) 71 | code = hash_combine(code, hash_value(code, v)); 72 | return code; 73 | } 74 | 75 | template< ranges::range range_t > 76 | constexpr hash_code hash_sized_container(hash_code code, const range_t &range) { 77 | return hash_combine( 78 | hash_combine_range(code, range), 79 | hash_code(range.size()) 80 | ); 81 | } 82 | 83 | template< typename T, size_t N > 84 | constexpr hash_code hash_value(hash_code code, const std::array< T, N >& arr) { 85 | return hash_sized_container(code, arr); 86 | } 87 | 88 | template< hashable T > 89 | constexpr hash_code hash_value(hash_code h, const T& v) { 90 | return hash_combine(h, hash_code( std::hash< T >{}(v) )); 91 | } 92 | 93 | template< typename T > 94 | concept supports_hash_value = requires(T v) { 95 | hash_value(std::declval< hash_code >(), v); 96 | }; 97 | 98 | // 99 | // tuple hash_value 100 | // 101 | namespace detail 102 | { 103 | template< typename Tuple, size_t... Is > 104 | constexpr hash_code hash_tuple(hash_code code, Tuple&& t, std::index_sequence< Is... >) { 105 | return hash_combine( 106 | hash_value(code, std::get< Is >(std::forward< Tuple >(t)))... 107 | ); 108 | } 109 | } // namespace detail 110 | 111 | template< typename... Ts > 112 | constexpr hash_code hash_value(hash_code code, const std::tuple< Ts... >& t) { 113 | return detail::hash_tuple( 114 | code, std::tuple< Ts... >(t), 115 | std::make_index_sequence< sizeof...(Ts) >() 116 | ); 117 | } 118 | 119 | template< typename T, typename U > 120 | constexpr hash_code hash_value(hash_code code, const std::pair< T, U >& p) { 121 | return hash_combine(code, hash_value(code, p.first), hash_value(code, p.second)); 122 | } 123 | 124 | // 125 | // generic hash interface 126 | // 127 | template< typename T > 128 | struct hash { 129 | template< supports_hash_value U = T > 130 | requires (not hashable< U >) 131 | constexpr std::size_t operator()(const U& u) const { 132 | hash_code state{ 0 }; 133 | return static_cast< std::size_t >( 134 | hash_value(hash_code(state), u) 135 | ); 136 | } 137 | 138 | template< hashable U = T > 139 | constexpr std::size_t operator()(const U& u) const { 140 | return std::hash< std::decay_t< U > >{}(u); 141 | } 142 | }; 143 | 144 | } // namespace gap 145 | -------------------------------------------------------------------------------- /core/include/gap/core/memoize.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace gap 14 | { 15 | namespace detail 16 | { 17 | template< typename T, typename U > 18 | inline constexpr bool is_same_base 19 | = std::is_same_v< std::remove_cvref_t< T >, std::remove_cvref_t< U > >; 20 | 21 | } // namespace detail 22 | 23 | template< typename signature, typename function, bool recursive = false > 24 | struct memoizer; 25 | 26 | template< typename result_t, typename... args_t, typename function, bool recursive > 27 | struct memoizer< result_t(args_t...), function, recursive > { 28 | using function_type = function; 29 | using function_signature = result_t(args_t...); 30 | 31 | using tuple_type = std::tuple< std::remove_reference_t< args_t >... >; 32 | using result_type = result_t; 33 | using return_reference = const result_type &; 34 | 35 | using hash_type = gap::hash< tuple_type >; 36 | using cache_type = std::unordered_map< tuple_type, result_type, hash_type >; 37 | 38 | explicit memoizer(function_type &&f) : fn(std::forward< function_type >(f)) {} 39 | 40 | template< typename... call_args_t > 41 | return_reference operator()(call_args_t &&...args) const { 42 | return call< std::conditional_t< 43 | detail::is_same_base< call_args_t, args_t >, call_args_t &&, 44 | std::remove_cvref_t< args_t > && >... >(std::forward< call_args_t >(args)...); 45 | } 46 | 47 | std::size_t cache_size() const { return cache.size(); } 48 | 49 | template< typename... call_args_t > 50 | bool cached(call_args_t &&...args) const { 51 | auto tuple = std::forward_as_tuple(std::forward< call_args_t >(args)...); 52 | return cache.count(tuple); 53 | } 54 | 55 | private: 56 | template< typename... call_args_t > 57 | return_reference call(call_args_t &&...args) const { 58 | auto tuple = std::forward_as_tuple(std::forward< call_args_t >(args)...); 59 | if (auto res = cache.find(tuple); res != cache.end()) { 60 | return res->second; 61 | } else { 62 | if constexpr (recursive) { 63 | return cache_call([this] (auto && ...as) { 64 | return fn(*this, std::forward(as)...); 65 | }, std::move(tuple))->second; 66 | } else { 67 | return cache_call(fn, std::move(tuple))->second; 68 | } 69 | } 70 | } 71 | 72 | template< typename function_t > 73 | auto cache_call(function_t &&f, tuple_type &&args) const { 74 | auto result = std::apply(std::forward< function_t >(f), args); 75 | return cache.emplace(std::move(args), std::move(result)).first; 76 | } 77 | 78 | function_type fn; 79 | mutable cache_type cache; 80 | }; 81 | 82 | template< typename signature, typename function_t > 83 | inline auto memoize(function_t &&fn) { 84 | return memoizer< signature, function_t >(std::forward< function_t >(fn)); 85 | } 86 | 87 | template< typename function_t > 88 | inline auto memoize(function_t &&fn) { 89 | return memoizer< trait::function_signature_t< function_t >, function_t >( 90 | std::forward< function_t >(fn) 91 | ); 92 | } 93 | 94 | template< typename signature, typename function_t > 95 | inline auto recursive_memoize(function_t &&fn) { 96 | if constexpr (std::is_function_v< signature >) { 97 | return memoizer< signature, function_t, true >(std::forward< function_t >(fn)); 98 | } else { 99 | using rec_sig = trait::recursive_function_signature_t< function_t, signature >; 100 | return memoizer< rec_sig, function_t, true >(std::forward< function_t >(fn)); 101 | } 102 | } 103 | 104 | template< typename function_t > 105 | inline auto recursive_memoize(function_t &&fn) { 106 | return memoizer< 107 | trait::recursive_function_signature_t< function_t >, function_t, true >( 108 | std::forward< function_t >(fn) 109 | ); 110 | } 111 | 112 | } // namespace gap 113 | -------------------------------------------------------------------------------- /core/include/gap/core/on_scope_exit.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/on_scope_exit.hpp from the cppcoro 8 | // project. The original file is licenced under the MIT license and the original 9 | // license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | namespace gap 18 | { 19 | template< typename func_t > 20 | struct scoped_lambda 21 | { 22 | scoped_lambda(func_t&& func) 23 | : m_func(std::forward(func)) 24 | , m_cancelled(false) 25 | {} 26 | 27 | scoped_lambda(const scoped_lambda& other) = delete; 28 | 29 | scoped_lambda(scoped_lambda&& other) 30 | : m_func(std::forward(other.m_func)) 31 | , m_cancelled(other.m_cancelled) 32 | { 33 | other.cancel(); 34 | } 35 | 36 | ~scoped_lambda() 37 | { 38 | if (!m_cancelled) { 39 | m_func(); 40 | } 41 | } 42 | 43 | void cancel() noexcept { 44 | m_cancelled = true; 45 | } 46 | 47 | void call_now() noexcept(noexcept(std::declval()())) { 48 | m_cancelled = true; 49 | m_func(); 50 | } 51 | 52 | private: 53 | 54 | func_t m_func; 55 | bool m_cancelled; 56 | 57 | }; 58 | 59 | /// A scoped lambda that executes the lambda when the object destructs 60 | /// but only if exiting due to an exception (call_on_failure = true) or 61 | /// only if not exiting due to an exception (call_on_failure = false). 62 | template< typename func_t, bool call_on_failure > 63 | struct conditional_scoped_lambda 64 | { 65 | conditional_scoped_lambda(func_t&& func) 66 | : m_func(std::forward(func)) 67 | , m_uncaught_exception_count(std::uncaught_exceptions()) 68 | , m_cancelled(false) 69 | {} 70 | 71 | conditional_scoped_lambda(const conditional_scoped_lambda& other) = delete; 72 | 73 | conditional_scoped_lambda(conditional_scoped_lambda&& other) 74 | noexcept(std::is_nothrow_move_constructible_v) 75 | : m_func(std::forward(other.m_func)) 76 | , m_uncaught_exception_count(other.m_uncaught_exception_count) 77 | , m_cancelled(other.m_cancelled) 78 | { 79 | other.cancel(); 80 | } 81 | 82 | ~conditional_scoped_lambda() noexcept(call_on_failure || noexcept(std::declval()())) 83 | { 84 | if (!m_cancelled && (is_unwinding_due_to_exception() == call_on_failure)) { 85 | m_func(); 86 | } 87 | } 88 | 89 | void cancel() noexcept { 90 | m_cancelled = true; 91 | } 92 | 93 | private: 94 | 95 | bool is_unwinding_due_to_exception() const noexcept { 96 | return std::uncaught_exceptions() > m_uncaught_exception_count; 97 | } 98 | 99 | func_t m_func; 100 | int m_uncaught_exception_count; 101 | bool m_cancelled; 102 | 103 | }; 104 | 105 | /// Returns an object that calls the provided func_ttion when it goes out 106 | /// of scope either normally or due to an uncaught exception unwinding 107 | /// the stack. 108 | /// 109 | /// \param func 110 | /// The function to call when the scope exits. 111 | /// The function must be noexcept. 112 | template< typename func_t > 113 | auto on_scope_exit(func_t&& func) { 114 | return scoped_lambda{ std::forward(func) }; 115 | } 116 | 117 | /// Returns an object that calls the provided func_ttion when it goes out 118 | /// of scope due to an uncaught exception unwinding the stack. 119 | /// 120 | /// \param func 121 | /// The function to be called if unwinding due to an exception. 122 | /// The function must be noexcept. 123 | template< typename func_t > 124 | auto on_scope_failure(func_t&& func) { 125 | return conditional_scoped_lambda{ std::forward(func) }; 126 | } 127 | 128 | /// Returns an object that calls the provided func_ttion when it goes out 129 | /// of scope via normal execution (ie. not unwinding due to an exception). 130 | /// 131 | /// \param func 132 | /// The function to call if the scope exits normally. 133 | /// The function does not necessarily need to be noexcept. 134 | template 135 | auto on_scope_success(func_t&& func) { 136 | return conditional_scoped_lambda{ std::forward(func) }; 137 | } 138 | 139 | } // namespace gap -------------------------------------------------------------------------------- /core/include/gap/core/optional.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace gap { 13 | 14 | // 15 | // optional concepts 16 | // 17 | template< typename T > 18 | concept optional_like = 19 | std::is_default_constructible_v< T > 20 | && requires (T a) { 21 | { a.value() } -> convertible_to< typename T::value_type >; 22 | { a.has_value() } -> convertible_to< bool >; 23 | { *a } -> convertible_to< typename T::value_type >; 24 | }; 25 | 26 | // 27 | // optional-like wrapper details 28 | // 29 | namespace detail { 30 | 31 | template< typename T > 32 | struct value_like_optional_adaptor : T { 33 | using base = T; 34 | using base::base; 35 | 36 | using value_type = T; 37 | 38 | constexpr value_type* operator->() noexcept { return this; } 39 | constexpr const value_type* operator->() const noexcept { return this; } 40 | 41 | constexpr value_type& operator*() & noexcept { return *this; } 42 | constexpr const value_type& operator*() const& noexcept { return *this; } 43 | constexpr value_type&& operator*() && noexcept { return std::move(*this); } 44 | constexpr const value_type&& operator*() const&& noexcept { return std::move(*this); } 45 | }; 46 | 47 | template< typename T > 48 | using optional_adaptor_base = 49 | std::conditional_t< std::is_pointer_v< T >, 50 | pointer_wrapper< T >, 51 | std::conditional_t< std::is_convertible_v< T, bool >, 52 | value_like_optional_adaptor< T >, 53 | T > 54 | >; 55 | 56 | } // namespace detail 57 | 58 | // 59 | // optional-like wrapper 60 | // 61 | template< typename T > 62 | struct optional_adaptor : detail::optional_adaptor_base< T > 63 | { 64 | using base = detail::optional_adaptor_base< T >; 65 | using base::base; 66 | 67 | using value_type = typename base::value_type; 68 | 69 | static constexpr bool copy_constructible = std::is_copy_constructible_v< value_type >; 70 | static constexpr bool move_constructible = std::is_move_constructible_v< value_type >; 71 | 72 | constexpr bool has_value() const noexcept { 73 | return static_cast< bool >(*this); 74 | } 75 | 76 | constexpr value_type& value() & { return **this; } 77 | constexpr const value_type& value() const& { return **this; } 78 | constexpr value_type&& value() && { return std::move(**this); } 79 | constexpr const value_type&& value() const&& { return std::move(**this); } 80 | 81 | template< typename U > 82 | requires(copy_constructible && std::is_convertible_v< U&&, value_type >) 83 | constexpr value_type value_or( U&& default_value ) const& { 84 | return bool(*this) ? **this : static_cast< value_type >(std::forward(default_value)); 85 | } 86 | 87 | template< typename U > 88 | requires(move_constructible && std::is_convertible_v< U&&, value_type >) 89 | constexpr value_type value_or( U&& default_value ) && { 90 | return bool(*this) ? std::move(**this) : static_cast< value_type >(std::forward(default_value)); 91 | } 92 | }; 93 | 94 | // 95 | // optional wrapper 96 | // 97 | namespace detail { 98 | template< typename T > 99 | using optional_wrapper_base = std::conditional_t< 100 | optional_like< T >, T, optional_adaptor< T > 101 | >; 102 | } // namespace detail 103 | 104 | template< typename base > 105 | struct optional_wrapper : detail::optional_wrapper_base< base > 106 | { 107 | using base_type = detail::optional_wrapper_base< base >; 108 | using value_type = typename base_type::value_type; 109 | 110 | using base_type::base_type; 111 | using base_type::value; 112 | 113 | static constexpr bool copy_constructible = std::is_copy_constructible_v< base_type >; 114 | static constexpr bool move_constructible = std::is_copy_constructible_v< base_type >; 115 | 116 | static constexpr bool nothrow_move_constructible 117 | = std::is_nothrow_move_constructible_v< base_type >; 118 | 119 | constexpr optional_wrapper() noexcept = default; 120 | 121 | constexpr optional_wrapper(const base_type &other) 122 | requires(copy_constructible) 123 | : base_type(other) {} 124 | 125 | constexpr optional_wrapper(base_type &&other) 126 | noexcept(nothrow_move_constructible) 127 | requires(move_constructible) 128 | : base_type(std::move(other)) 129 | {} 130 | 131 | template< typename U > 132 | requires(std::is_constructible_v< base_type, optional_wrapper< U > && >) 133 | explicit(!std::is_convertible_v< const U&, value_type >) 134 | constexpr optional_wrapper(optional_wrapper< U > &&other) 135 | : base_type(std::move(other)) 136 | {} 137 | 138 | template< typename U > 139 | requires(std::is_constructible_v< base_type, const optional_wrapper< U > & >) 140 | explicit(!std::is_convertible_v< U&&, value_type >) 141 | constexpr optional_wrapper(const optional_wrapper< U > &other) 142 | : base_type(other) 143 | {} 144 | 145 | template< typename U = value_type > 146 | requires(std::is_constructible_v< base_type, U&& >) 147 | explicit(!std::is_convertible_v< U&&, value_type >) 148 | constexpr optional_wrapper( U &&other ) 149 | : base_type(std::forward< U >(other)) 150 | {} 151 | 152 | base& unwrap() & { return *this; } 153 | const base& unwrap() const& { return *this; } 154 | base&& unwrap() && { return std::move(*this); } 155 | const base&& unwrap() const&& { return std::move(*this); } 156 | 157 | template< typename F, optional_like R = std::invoke_result_t< F, value_type& > > 158 | constexpr auto and_then( F&& f ) & { 159 | if (*this) { 160 | return std::invoke(std::forward< F >(f), this->value()); 161 | } else { 162 | return std::remove_cvref_t< R >(); 163 | } 164 | } 165 | 166 | template< typename F, optional_like R = std::invoke_result_t< F, const value_type& > > 167 | constexpr auto and_then( F&& f ) const& { 168 | if (*this) { 169 | return std::invoke(std::forward< F >(f), this->value()); 170 | } else { 171 | return std::remove_cvref_t< R >(); 172 | } 173 | } 174 | 175 | template< typename F, optional_like R = std::invoke_result_t< F, value_type > > 176 | constexpr auto and_then( F&& f ) && { 177 | if (*this) { 178 | return std::invoke(std::forward< F >(f), std::move(this->value())); 179 | } else { 180 | return std::remove_cvref_t< R >(); 181 | } 182 | } 183 | 184 | template< typename F, optional_like R = std::invoke_result_t< F, value_type > > 185 | constexpr auto and_then( F&& f ) const&& { 186 | if (*this) { 187 | return std::invoke(std::forward< F >(f), std::move(this->value())); 188 | } else { 189 | return std::remove_cvref_t< R >(); 190 | } 191 | } 192 | 193 | template< typename F, typename R = std::remove_cvref_t< std::invoke_result_t< F > > > 194 | requires(convertible_to< R, optional_wrapper >) 195 | constexpr optional_wrapper or_else( F&& f ) const& { 196 | return *this ? *this : std::forward< F >(f)(); 197 | } 198 | 199 | template< typename F, typename R = std::remove_cvref_t< std::invoke_result_t< F > > > 200 | requires(convertible_to< R, optional_wrapper >) 201 | constexpr optional_wrapper or_else( F&& f ) && { 202 | return *this ? std::move(*this) : std::forward< F >(f)(); 203 | } 204 | 205 | template< predicate< value_type > P > 206 | constexpr optional_wrapper keep_if(P &&p) & { 207 | return (*this && std::invoke(std::forward< P >(p), **this)) ? *this : optional_wrapper(); 208 | } 209 | 210 | template< predicate< value_type > P > 211 | constexpr optional_wrapper keep_if(P &&p) const& { 212 | return (*this && std::invoke(std::forward< P >(p), **this)) ? *this : optional_wrapper(); 213 | } 214 | 215 | template< predicate< value_type > P > 216 | constexpr optional_wrapper keep_if(P &&p) && { 217 | return (*this && std::invoke(std::forward< P >(p), **this)) ? *this : optional_wrapper(); 218 | } 219 | 220 | template< predicate< value_type > P > 221 | constexpr optional_wrapper keep_if(P &&p) const&& { 222 | return (*this && std::invoke(std::forward< P >(p), **this)) ? *this : optional_wrapper(); 223 | } 224 | 225 | }; 226 | 227 | } // namespace gap 228 | -------------------------------------------------------------------------------- /core/include/gap/core/overloads.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Trail of Bits, Inc. 3 | */ 4 | 5 | #pragma once 6 | 7 | namespace gap 8 | { 9 | 10 | // helper for variant visitor 11 | template< class... Ts > 12 | struct overloaded : Ts... { 13 | using Ts::operator()...; 14 | }; 15 | template< class... Ts > 16 | overloaded(Ts...) -> overloaded< Ts... >; 17 | 18 | } // namespace gap 19 | -------------------------------------------------------------------------------- /core/include/gap/core/pointer.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | namespace gap { 6 | 7 | template< typename T > 8 | struct pointer_wrapper 9 | { 10 | static_assert( std::is_pointer_v< T > ); 11 | 12 | using pointer_type = T; 13 | using value_type = std::remove_pointer_t< pointer_type >; 14 | 15 | pointer_type _ptr = nullptr; 16 | 17 | constexpr pointer_wrapper() noexcept = default; 18 | constexpr pointer_wrapper( pointer_type v ) noexcept : _ptr( v ) {} 19 | 20 | constexpr pointer_type operator->() const noexcept { return _ptr; } 21 | constexpr value_type& operator*() const noexcept { return *_ptr; } 22 | 23 | constexpr explicit operator bool() const noexcept { 24 | return static_cast< bool >( _ptr ); 25 | } 26 | 27 | constexpr bool operator!() const noexcept { 28 | return !static_cast< bool >( *this ); 29 | } 30 | 31 | constexpr pointer_type& ptr() noexcept { return _ptr; } 32 | constexpr const pointer_type& ptr() const noexcept { return _ptr; } 33 | 34 | constexpr auto operator<=>(const pointer_wrapper &other) const noexcept = default; 35 | 36 | template< typename stream > 37 | friend auto operator<<( stream &o, const pointer_wrapper< T > &p ) 38 | -> decltype( o << std::declval< void * >() ) 39 | { 40 | return o << p._ptr; 41 | } 42 | }; 43 | 44 | } // namespace gap 45 | -------------------------------------------------------------------------------- /core/include/gap/core/ranges.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace gap::ranges 10 | { 11 | template< typename R > 12 | concept range = requires(R&& r) { 13 | typename std::decay_t< R >::value_type; 14 | std::begin(r); 15 | std::end(r); 16 | }; 17 | 18 | template< range R > 19 | constexpr auto size(R&& r) { 20 | return std::size(r); 21 | } 22 | 23 | template< typename R > 24 | concept sized_range = range< R > && requires(R&& r) { ranges::size(r); }; 25 | 26 | template< typename R > 27 | concept arithmetic_range = range< R > && arithmetic< typename R::value_type >; 28 | 29 | template< typename R, typename V > 30 | concept value_range = range< R > && convertible_to< typename R::value_type, V >; 31 | 32 | constexpr auto accumulate(range auto r, auto init) { 33 | return std::accumulate(r.begin(), r.end(), init); 34 | } 35 | 36 | constexpr auto accumulate(range auto r, auto init, auto bop) { 37 | return std::accumulate(r.begin(), r.end(), init, bop); 38 | } 39 | 40 | template< range R > 41 | using iterator_t = decltype(std::begin(std::declval< R& >())); 42 | 43 | template< range R > 44 | using range_value_t = std::iter_value_t< iterator_t< R > >; 45 | 46 | template< typename T > 47 | concept has_reserve = requires(T t, std::size_t s) { t.reserve(s); }; 48 | 49 | template< typename container_t, std::ranges::input_range R > 50 | void move_or_copy_elements(R&& rng, container_t& container) { 51 | if constexpr (std::is_rvalue_reference_v< decltype(rng) >) { 52 | std::move(std::ranges::begin(rng), std::ranges::end(rng), 53 | std::back_inserter(container) 54 | ); 55 | } else { 56 | std::copy(std::ranges::begin(rng), std::ranges::end(rng), 57 | std::back_inserter(container) 58 | ); 59 | } 60 | } 61 | 62 | template< template< typename... > class container_t > 63 | struct to_fn { 64 | template< std::ranges::input_range R > 65 | auto operator()(R&& range) const { 66 | using value_type = std::ranges::range_value_t< R >; 67 | using result_container_t = container_t< value_type >; 68 | 69 | result_container_t container; 70 | 71 | if constexpr (has_reserve< result_container_t >) { 72 | if constexpr (requires { std::ranges::size(range); }) { 73 | container.reserve(std::ranges::size(range)); 74 | } 75 | } 76 | 77 | move_or_copy_elements(std::forward< R >(range), container); 78 | return container; 79 | } 80 | 81 | template< std::ranges::input_range R > 82 | friend auto operator|(R&& range, const to_fn& to) { 83 | return to(std::forward< R >(range)); 84 | } 85 | }; 86 | 87 | template< template< typename... > class container_t > 88 | inline constexpr to_fn< container_t > to{}; 89 | 90 | template< template< typename... > class container_t, std::ranges::input_range R > 91 | auto operator|(R&& range, to_fn< container_t > const& to) { 92 | return to(std::forward< R >(range)); 93 | } 94 | 95 | } // namespace gap::ranges 96 | -------------------------------------------------------------------------------- /core/include/gap/core/recursive_generator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #error "The gap/core/recursive_generator.hpp header was deprecated and removed. Please update your include paths to use gap/coro/recursive_generator.hpp instead." -------------------------------------------------------------------------------- /core/include/gap/core/source_location.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace gap 9 | { 10 | struct source_location { 11 | using file_name = std::string_view; 12 | using func_name = std::string_view; 13 | 14 | static constexpr source_location current( 15 | file_name file = __builtin_FILE(), 16 | func_name function = __builtin_FUNCTION(), 17 | unsigned line = __builtin_LINE()) noexcept { 18 | return source_location(file, function, line); 19 | } 20 | 21 | constexpr source_location(file_name file, func_name func, unsigned line) noexcept 22 | : _file(file) 23 | , _func(func) 24 | , _line(line) {} 25 | 26 | constexpr file_name file() const noexcept { return _file; } 27 | constexpr func_name function() const noexcept { return _func; } 28 | 29 | constexpr unsigned line() const noexcept { return _line; } 30 | 31 | private: 32 | file_name _file; 33 | func_name _func; 34 | unsigned _line; 35 | }; 36 | 37 | template< typename stream > 38 | auto operator<<(stream &os, const source_location &loc) noexcept -> decltype(os << "") { 39 | return os << loc.file() << ":" << loc.function() << ":" << std::to_string(loc.line()); 40 | } 41 | 42 | } // namespace gap 43 | -------------------------------------------------------------------------------- /core/include/gap/core/strong_type.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Trail of Bits, Inc. 3 | */ 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include // for swap 12 | 13 | namespace gap 14 | { 15 | 16 | template< typename underlying, typename tag > 17 | struct strong_type { 18 | using underlying_t = underlying; 19 | 20 | constexpr strong_type() 21 | : _value() {} 22 | 23 | constexpr explicit strong_type(const underlying_t &value) 24 | : _value(value) {} 25 | 26 | constexpr explicit strong_type(underlying_t &&value) noexcept( 27 | std::is_nothrow_move_constructible_v< underlying_t >) 28 | : _value(std::move(value)) {} 29 | 30 | explicit constexpr operator underlying_t &() noexcept { return _value; } 31 | explicit constexpr operator const underlying_t &() const noexcept { return _value; } 32 | explicit constexpr operator underlying_t &&() noexcept { return std::move(_value); } 33 | 34 | constexpr underlying_t &ref() noexcept { return _value; } 35 | constexpr const underlying_t &ref() const noexcept { return _value; } 36 | 37 | strong_type &operator++() requires incrementable< underlying_t > { 38 | ++_value; 39 | return *this; 40 | } 41 | 42 | strong_type operator++(int) requires incrementable< underlying_t > { 43 | strong_type tmp = *this; 44 | ++_value; 45 | return tmp; 46 | } 47 | 48 | strong_type &operator--() requires decrementable< underlying_t > { 49 | --_value; 50 | return *this; 51 | } 52 | 53 | strong_type operator--(int) requires decrementable< underlying_t > { 54 | strong_type tmp = *this; 55 | --_value; 56 | return tmp; 57 | } 58 | 59 | friend constexpr void swap(strong_type &a, strong_type &b) noexcept { 60 | using std::swap; 61 | swap(static_cast< underlying_t & >(a), static_cast< underlying_t & >(b)); 62 | } 63 | 64 | template< typename stream > 65 | friend auto operator<<(stream &out, const strong_type &a) noexcept -> decltype(out << "") { 66 | if constexpr (std::is_integral_v< underlying_t >) { 67 | return out << std::to_string(a.ref()); 68 | } else { 69 | return out << a.ref(); 70 | } 71 | } 72 | 73 | private: 74 | underlying_t _value; 75 | }; 76 | 77 | template< typename U, typename T > 78 | constexpr bool operator==(const strong_type< U, T > &a, const strong_type< U, T > &b) { 79 | return a.ref() == b.ref(); 80 | } 81 | 82 | template< typename U, typename T > 83 | constexpr bool operator>(const strong_type< U, T > &a, const strong_type< U, T > &b) { 84 | return a.ref() > b.ref(); 85 | } 86 | 87 | template< typename U, typename T > 88 | constexpr bool operator<(const strong_type< U, T > &a, const strong_type< U, T > &b) { 89 | return a.ref() < b.ref(); 90 | } 91 | 92 | template< typename U, typename T > 93 | constexpr auto operator<=>(const strong_type< U, T > &a, const U &b) { 94 | return a.ref() <=> b; 95 | } 96 | 97 | template< typename U, typename T > 98 | constexpr auto operator<=>(const U &a, const strong_type< U, T > &b) { 99 | return a <=> b.ref(); 100 | } 101 | 102 | } // namespace gap 103 | 104 | namespace std 105 | { 106 | template< typename underlying_t, typename tag > 107 | struct hash< gap::strong_type< underlying_t, tag > > { 108 | using underlying_hash = std::hash< underlying_t >; 109 | 110 | size_t operator()(const gap::strong_type< underlying_t, tag > &a) const noexcept { 111 | return underlying_hash{}(static_cast< const underlying_t & >(a)); 112 | } 113 | }; 114 | } // namespace std 115 | -------------------------------------------------------------------------------- /core/include/gap/core/type_traits.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace gap::trait 8 | { 9 | 10 | template< typename T > 11 | struct type_wrapper { 12 | template< typename U > 13 | friend constexpr bool operator==(type_wrapper, type_wrapper< U >) { 14 | return std::is_same_v< T, U >; 15 | } 16 | }; 17 | 18 | template< typename T > 19 | inline constexpr auto type = type_wrapper< T >{}; 20 | 21 | template< typename T > 22 | constexpr auto type_of(T &&) { 23 | return type< T >; 24 | } 25 | 26 | namespace detail 27 | { 28 | template< typename T, typename enable_t = void > 29 | struct function_signature; 30 | 31 | template< typename T > 32 | struct function_signature< T, std::enable_if_t< std::is_class_v< std::remove_cvref_t< T > > > > 33 | : function_signature< decltype(&std::remove_cvref_t< T >::operator()) > 34 | {}; 35 | 36 | template< typename T, typename result_t, typename... args_t > 37 | struct function_signature< result_t (T::*)(args_t...) > { 38 | using type = result_t(args_t...); 39 | }; 40 | 41 | template< typename T, typename result_t, typename... args_t > 42 | struct function_signature< result_t (T::*)(args_t...) const > { 43 | using type = result_t(args_t...); 44 | }; 45 | 46 | template< typename result_t, typename... args_t > 47 | struct function_signature< result_t(args_t...) > { 48 | using type = result_t(args_t...); 49 | }; 50 | 51 | template< typename result_t, typename... args_t > 52 | struct function_signature< result_t (&)(args_t...) > { 53 | using type = result_t(args_t...); 54 | }; 55 | 56 | template< typename result_t, typename... args_t > 57 | struct function_signature< result_t (*)(args_t...) > { 58 | using type = result_t(args_t...); 59 | }; 60 | } // namespace detail 61 | 62 | template< typename T > 63 | using function_signature_t = typename detail::function_signature< T >::type; 64 | 65 | namespace detail 66 | { 67 | template< typename T > 68 | struct drop_first_arg; 69 | 70 | template< typename result_t, typename first_t, typename... args_t > 71 | struct drop_first_arg< result_t(first_t, args_t...) > { 72 | using type = result_t(args_t...); 73 | }; 74 | 75 | struct dummy {}; 76 | 77 | template< typename T, typename result_t = dummy, typename enable_t = void > 78 | struct recursive_function_signature; 79 | 80 | template< typename T, typename result_t > 81 | struct recursive_function_signature< 82 | T, 83 | result_t, 84 | std::enable_if_t< std::is_class_v< std::remove_cvref_t< T > > > > 85 | { 86 | using type = typename drop_first_arg< function_signature_t< 87 | decltype(&std::remove_cvref_t< T >::template operator()< result_t (*)(...) >) > >::type; 88 | }; 89 | } // namespace detail 90 | 91 | template< typename... T > 92 | using recursive_function_signature_t = typename detail::recursive_function_signature< T... >::type; 93 | 94 | template< typename T > 95 | struct unwrap_reference { 96 | using type = T; 97 | }; 98 | 99 | template< typename T > 100 | struct unwrap_reference< std::reference_wrapper< T > > { 101 | using type = T; 102 | }; 103 | 104 | template< typename T > 105 | using unwrap_reference_t = typename unwrap_reference< T >::type; 106 | 107 | namespace 108 | { 109 | static_assert(type< int > == type< int >); 110 | static_assert(type< unsigned > != type< double >); 111 | 112 | template< typename T, typename U > 113 | constexpr bool check(T &&a, U &&b) { 114 | if constexpr (type< T > == type< U >) { 115 | return type_of(a) == type_of(b); 116 | } else { 117 | return type_of(a) != type_of(b); 118 | } 119 | } 120 | 121 | static_assert(check(1, 2)); 122 | static_assert(check(1, 2.1)); 123 | static_assert(check("hello", "world")); 124 | 125 | } // anonymous namespace 126 | 127 | } // namespace gap::trait 128 | -------------------------------------------------------------------------------- /coro/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Trail of Bits, Inc. All rights reserved. 2 | 3 | add_headers(coro GAP_CORO_HEADERS 4 | async_manual_reset_event.hpp 5 | awaitable_traits.hpp 6 | broken_promise.hpp 7 | coroutine.hpp 8 | fmap.hpp 9 | generator.hpp 10 | manual_reset_event.hpp 11 | recursive_generator.hpp 12 | shared_task.hpp 13 | single_consumer_event.hpp 14 | sync_wait.hpp 15 | task.hpp 16 | when_all_ready.hpp 17 | ) 18 | 19 | add_sources(coro GAP_CORO_SOURCES 20 | async_manual_reset_event.cpp 21 | ) 22 | 23 | add_gap_static_library(gap-coro "${GAP_CORO_HEADERS}" "${GAP_CORO_SOURCES}") 24 | 25 | target_link_libraries(gap-coro PUBLIC gap-core) -------------------------------------------------------------------------------- /coro/include/gap/coro/async_manual_reset_event.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/async_manual_reset_event.hpp from 8 | // the cppcoro project. The original file is licenced under the MIT license and 9 | // the original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifdef GAP_ENABLE_COROUTINES 15 | 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | namespace gap::coro 22 | { 23 | struct async_manual_reset_event_operation; 24 | 25 | struct async_manual_reset_event 26 | { 27 | async_manual_reset_event(bool initially_set = false) noexcept; 28 | 29 | ~async_manual_reset_event() noexcept; 30 | 31 | async_manual_reset_event_operation operator co_await() const noexcept; 32 | 33 | bool is_set() const noexcept; 34 | 35 | void set() noexcept; 36 | 37 | void reset() noexcept; 38 | private: 39 | friend struct async_manual_reset_event_operation; 40 | 41 | // This variable has 3 states: 42 | // - this - The state is 'set'. 43 | // - nullptr - The state is 'not set' with no waiters. 44 | // - other - The state is 'not set'. 45 | // Points to an 'async_manual_reset_event_operation' that is 46 | // the head of a linked-list of waiters. 47 | mutable std::atomic m_state; 48 | }; 49 | 50 | struct async_manual_reset_event_operation 51 | { 52 | explicit async_manual_reset_event_operation(const async_manual_reset_event& event) noexcept; 53 | 54 | bool await_ready() const noexcept; 55 | bool await_suspend(gap::coroutine_handle<> awaiter) noexcept; 56 | void await_resume() const noexcept {} 57 | 58 | private: 59 | friend struct async_manual_reset_event; 60 | 61 | const async_manual_reset_event& m_event; 62 | async_manual_reset_event_operation* m_next; 63 | gap::coroutine_handle<> m_awaiter; 64 | }; 65 | 66 | } // namespace gap::coro 67 | 68 | #endif -------------------------------------------------------------------------------- /coro/include/gap/coro/awaitable_traits.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #ifdef GAP_ENABLE_COROUTINES 6 | 7 | #include "gap/coro/coroutine.hpp" 8 | 9 | namespace gap::coro 10 | { 11 | template< typename T > 12 | struct is_coroutine_handle : std::false_type {}; 13 | 14 | template< typename promise_t > 15 | struct is_coroutine_handle< gap::coroutine_handle< promise_t > > : std::true_type {}; 16 | 17 | template< typename T > 18 | constexpr bool is_coroutine_handle_v = is_coroutine_handle< T >::value; 19 | 20 | template< typename T > 21 | concept await_suspend_return_type = 22 | std::is_void_v< T > || std::is_same_v< bool, T > || is_coroutine_handle_v< T >; 23 | 24 | template< typename T > 25 | concept awaiter = requires { 26 | { std::declval< T >().await_ready() } -> std::same_as< bool >; 27 | { std::declval< T >().await_suspend(std::declval< gap::coroutine_handle<> >()) } -> await_suspend_return_type; 28 | { std::declval< T >().await_resume() }; 29 | }; 30 | 31 | template< typename T, typename R > 32 | concept awaiter_of = awaiter< T > && requires { 33 | { std::declval< T >().await_resume() } -> std::convertible_to< R >; 34 | }; 35 | 36 | template< typename T > 37 | concept has_member_co_await = requires(T&& t) { 38 | { static_cast< T&& >(t).operator co_await() } -> awaiter; 39 | }; 40 | 41 | template< typename T > 42 | concept has_free_co_await = requires(T&& t) { 43 | { operator co_await(static_cast< T&& >(t)) } -> awaiter; 44 | }; 45 | 46 | template< typename T > 47 | concept awaitable = awaiter< T > || has_member_co_await< T > || has_free_co_await< T >; 48 | 49 | template< typename T > 50 | requires has_member_co_await< T > 51 | decltype(auto) get_awaiter(T &&t) noexcept(noexcept(static_cast< T&& >(t).operator co_await())) { 52 | return static_cast< T&& >(t).operator co_await(); 53 | } 54 | 55 | template< typename T > 56 | requires has_free_co_await< T > 57 | decltype(auto) get_awaiter(T &&t) noexcept(noexcept(operator co_await(static_cast< T&& >(t)))) { 58 | return operator co_await(static_cast< T&& >(t)); 59 | } 60 | 61 | template< typename T > 62 | requires awaiter< T > &&(!has_member_co_await< T > && !has_free_co_await< T >) 63 | T&& get_awaiter(T &&t) noexcept { 64 | return static_cast< T&& >(t); 65 | } 66 | 67 | template< awaitable T > 68 | using awaiter_type_t = decltype(get_awaiter(std::declval< T >())); 69 | 70 | template< awaitable T > 71 | using await_result_t = decltype(std::declval< awaiter_type_t< T >& >().await_resume()); 72 | 73 | } // namespace gap::coro 74 | 75 | #endif -------------------------------------------------------------------------------- /coro/include/gap/coro/broken_promise.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/broken_promise.hpp from 8 | // the cppcoro project. The original file is licenced under the MIT license and 9 | // the original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifdef GAP_ENABLE_COROUTINES 15 | 16 | #include 17 | 18 | namespace gap::coro 19 | { 20 | // Exception thrown when you attempt to retrieve the result of 21 | // a task that has been detached from its promise/coroutine. 22 | struct broken_promise : std::logic_error { 23 | broken_promise() : std::logic_error("broken promise") {} 24 | }; 25 | } // namespace gap::coro 26 | 27 | #endif -------------------------------------------------------------------------------- /coro/include/gap/coro/coroutine.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/coroutine.hpp from the cppcoro 8 | // project. The original file is licenced under the MIT license and the original 9 | // license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifdef GAP_ENABLE_COROUTINES 15 | 16 | #include "gap/core/config.hpp" 17 | 18 | #ifdef GAP_COROHEADER_FOUND_AND_USABLE 19 | 20 | #include 21 | 22 | namespace gap 23 | { 24 | using std::coroutine_handle; 25 | using std::noop_coroutine; 26 | using std::suspend_always; 27 | using std::suspend_never; 28 | 29 | } // namespace gap 30 | 31 | #elif __has_include() 32 | 33 | #include 34 | 35 | namespace gap 36 | { 37 | using std::experimental::coroutine_handle; 38 | using std::experimental::suspend_always; 39 | using std::experimental::suspend_never; 40 | 41 | #if GAP_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER 42 | using std::experimental::noop_coroutine; 43 | #endif 44 | } // namespace gap 45 | 46 | #else 47 | #error gap requires a C++20 compiler with coroutine support 48 | #endif 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /coro/include/gap/coro/fmap.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/fmap.hpp from the cppcoro 8 | // project. The original file is licenced under the MIT license and the original 9 | // license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifdef GAP_ENABLE_COROUTINES 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace gap::coro 25 | { 26 | namespace detail 27 | { 28 | template< typename func_t, awaitable awaitable_t > 29 | struct fmap_awaiter { 30 | using awaiter_t = awaiter_type_t< awaitable_t&& >; 31 | 32 | fmap_awaiter(func_t &&func, awaitable_t &&awaitable) 33 | noexcept( 34 | std::is_nothrow_move_constructible_v< func_t > && 35 | noexcept(get_awaiter(static_cast< awaitable_t&& >(awaitable))) 36 | ) 37 | : m_func(static_cast< func_t&& >(func)) 38 | , m_awaiter(get_awaiter(static_cast< awaitable_t&& >(awaitable))) 39 | {} 40 | 41 | decltype(auto) await_ready() 42 | noexcept(noexcept(static_cast< awaiter_t&& >(m_awaiter).await_ready())) 43 | { 44 | return static_cast< awaiter_t&& >(m_awaiter).await_ready(); 45 | } 46 | 47 | template< typename promise_t > 48 | decltype(auto) await_suspend(gap::coroutine_handle< promise_t > coroutine) 49 | noexcept(noexcept( 50 | static_cast< awaiter_t&& >(std::declval< awaiter_t >()).await_suspend(std::move(coroutine)) 51 | )) 52 | { 53 | return static_cast< awaiter_t&& >(m_awaiter).await_suspend(std::move(coroutine)); 54 | } 55 | 56 | template< typename await_result = decltype(std::declval< awaiter_t >().await_resume()) > 57 | requires std::is_void_v< await_result > 58 | decltype(auto) await_resume() 59 | noexcept(noexcept(std::invoke(static_cast< func_t&& >(std::declval< func_t >())))) 60 | { 61 | static_cast(m_awaiter).await_resume(); 62 | return std::invoke(static_cast< func_t&& >(m_func)); 63 | } 64 | 65 | template< typename await_result = decltype(std::declval< awaiter_t >().await_resume()) > 66 | requires (not std::is_void_v< await_result >) 67 | decltype(auto) await_resume() 68 | noexcept(noexcept( 69 | std::invoke( 70 | static_cast< func_t&& >(std::declval< func_t >()), 71 | static_cast< awaiter_t&& >(std::declval< awaiter_t >()).await_resume()) 72 | )) 73 | { 74 | return std::invoke( 75 | static_cast< func_t&& >(m_func), 76 | static_cast< awaiter_t&& >(m_awaiter).await_resume() 77 | ); 78 | } 79 | private: 80 | func_t &&m_func; 81 | awaiter_t m_awaiter; 82 | }; 83 | 84 | template< typename func_t, awaitable awaitable_t > 85 | struct fmap_awaitable 86 | { 87 | static_assert(!std::is_lvalue_reference_v< func_t >); 88 | static_assert(!std::is_lvalue_reference_v< awaitable_t >); 89 | 90 | template< typename func_arg_t, typename awaitable_arg_t > 91 | requires std::is_constructible_v< func_t, func_arg_t > 92 | && std::is_constructible_v< awaitable_t, awaitable_arg_t > 93 | explicit fmap_awaitable(func_arg_t &&func, awaitable_arg_t &&awaitable) 94 | noexcept( 95 | std::is_nothrow_constructible_v< func_t, func_arg_t > && 96 | std::is_nothrow_constructible_v< awaitable_t, awaitable_arg_t > 97 | ) 98 | : m_func(static_cast< func_arg_t&& >(func)) 99 | , m_awaitable(static_cast< awaitable_arg_t&& >(awaitable)) 100 | {} 101 | 102 | auto operator co_await() const & { 103 | return fmap_awaiter< const func_t &, const awaitable_t & >( 104 | m_func, m_awaitable 105 | ); 106 | } 107 | 108 | auto operator co_await() && { 109 | return fmap_awaiter< func_t &&, awaitable_t && >( 110 | static_cast< func_t&& >(m_func), 111 | static_cast< awaitable_t&& >(m_awaitable) 112 | ); 113 | } 114 | 115 | auto operator co_await() & { 116 | return fmap_awaiter< func_t &, awaitable_t & >( 117 | m_func, m_awaitable 118 | ); 119 | } 120 | private: 121 | func_t m_func; 122 | awaitable_t m_awaitable; 123 | }; 124 | 125 | } // namespace detail 126 | 127 | template< typename func_t > 128 | struct fmap_transform 129 | { 130 | explicit fmap_transform(func_t &&func) 131 | noexcept(std::is_nothrow_move_constructible_v< func_t >) 132 | : m_func(std::forward< func_t >(func)) 133 | {} 134 | 135 | func_t m_func; 136 | }; 137 | 138 | template< typename func_t, awaitable awaitable_t > 139 | auto fmap(func_t &&func, awaitable_t &&awaitable) 140 | { 141 | return detail::fmap_awaitable< 142 | std::remove_cv_t< std::remove_reference_t< func_t > >, 143 | std::remove_cv_t< std::remove_reference_t< awaitable_t > > 144 | >( 145 | std::forward< func_t >(func), 146 | std::forward< awaitable_t >(awaitable) 147 | ); 148 | } 149 | 150 | template< typename func_t > 151 | auto fmap(func_t &&func) 152 | { 153 | return fmap_transform< func_t >( 154 | std::forward< func_t >(func) 155 | ); 156 | } 157 | 158 | template< typename value_t, typename func_t > 159 | decltype(auto) operator|(value_t &&value, fmap_transform< func_t > &&transform) 160 | { 161 | return fmap( 162 | std::forward< func_t >(transform.m_func), 163 | std::forward< value_t >(value) 164 | ); 165 | } 166 | 167 | template< typename value_t, typename func_t > 168 | decltype(auto) operator|(value_t &&value, fmap_transform< func_t > &transform) 169 | { 170 | return fmap( 171 | transform.m_func, 172 | std::forward< value_t >(value) 173 | ); 174 | } 175 | 176 | template< typename value_t, typename func_t > 177 | decltype(auto) operator|(value_t &&value, const fmap_transform< func_t > &transform) 178 | { 179 | return fmap( 180 | transform.m_func, 181 | std::forward< value_t >(value) 182 | ); 183 | } 184 | 185 | } // namespace gap::coro 186 | 187 | #endif -------------------------------------------------------------------------------- /coro/include/gap/coro/generator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/generator.hpp from the cppcoro 8 | // project. The original file is licenced under the MIT license and the original 9 | // license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifdef GAP_ENABLE_COROUTINES 15 | 16 | #include "gap/coro/coroutine.hpp" 17 | #include "gap/core/ranges.hpp" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace gap 27 | { 28 | template< typename T > 29 | struct generator; 30 | 31 | template< gap::ranges::range range_t > 32 | using range_generator = generator< typename std::decay_t< range_t >::value_type >; 33 | 34 | namespace detail 35 | { 36 | template< typename T > 37 | struct generator_promise_type { 38 | using value_type = std::remove_reference_t< T >; 39 | using reference_type = std::conditional_t< std::is_reference_v< T >, T, T& >; 40 | using pointer_type = value_type*; 41 | 42 | generator_promise_type() 43 | : _value(nullptr){}; 44 | 45 | generator< T > get_return_object() noexcept; 46 | 47 | constexpr gap::suspend_always initial_suspend() const noexcept { return {}; } 48 | constexpr gap::suspend_always final_suspend() const noexcept { return {}; } 49 | 50 | gap::suspend_always yield_value(std::remove_reference_t< T >& value) noexcept { 51 | _value = std::addressof(value); 52 | return {}; 53 | } 54 | 55 | gap::suspend_always yield_value(std::remove_reference_t< T >&& value) noexcept { 56 | _value = std::addressof(value); 57 | return {}; 58 | } 59 | 60 | void unhandled_exception() { _exception = std::current_exception(); } 61 | 62 | void return_void() {} 63 | 64 | reference_type value() const noexcept { return static_cast< reference_type >(*_value); } 65 | 66 | // Don't allow any use of 'co_await' inside the generator coroutine. 67 | template< typename U > 68 | gap::suspend_never await_transform(U&& value) = delete; 69 | 70 | void rethrow_if_exception() { 71 | if (_exception) { 72 | std::rethrow_exception(_exception); 73 | } 74 | } 75 | 76 | private: 77 | pointer_type _value; 78 | std::exception_ptr _exception; 79 | }; 80 | 81 | template< typename T > 82 | using promise_type = detail::generator_promise_type< T >; 83 | 84 | template< typename T > 85 | using coroutine_handle = gap::coroutine_handle< promise_type< T > >; 86 | 87 | struct generator_sentinel {}; 88 | 89 | template< typename T > 90 | struct generator_iterator { 91 | using iterator_category = std::input_iterator_tag; 92 | using difference_type = std::ptrdiff_t; 93 | using value_type = typename promise_type< T >::value_type; 94 | using reference = typename promise_type< T >::reference_type; 95 | using pointer = typename promise_type< T >::pointer_type; 96 | 97 | generator_iterator() noexcept = default; 98 | 99 | explicit generator_iterator(coroutine_handle< T > coroutine) noexcept 100 | : _coroutine(coroutine) {} 101 | 102 | friend bool operator==(const generator_iterator& it, generator_sentinel) noexcept { 103 | return !it._coroutine || it._coroutine.done(); 104 | } 105 | 106 | friend bool operator==(generator_sentinel s, const generator_iterator& it) noexcept { 107 | return (it == s); 108 | } 109 | 110 | friend bool operator!=(const generator_iterator& it, generator_sentinel s) noexcept { 111 | return !(it == s); 112 | } 113 | 114 | friend bool operator!=(generator_sentinel s, const generator_iterator& it) noexcept { 115 | return it != s; 116 | } 117 | 118 | generator_iterator& operator++() { 119 | _coroutine.resume(); 120 | if (_coroutine.done()) { 121 | _coroutine.promise().rethrow_if_exception(); 122 | } 123 | 124 | return *this; 125 | } 126 | 127 | void operator++(int) { (void) operator++(); } 128 | 129 | reference operator*() const noexcept { return _coroutine.promise().value(); } 130 | 131 | pointer operator->() const noexcept { return std::addressof(operator*()); } 132 | 133 | private: 134 | coroutine_handle< T > _coroutine = nullptr; 135 | }; 136 | 137 | } // namespace detail 138 | 139 | template< typename T > 140 | struct [[nodiscard]] generator { 141 | using iterator = detail::generator_iterator< T >; 142 | using promise_type = detail::promise_type< T >; 143 | using coroutine_handle = detail::coroutine_handle< T >; 144 | 145 | using value_type = typename promise_type::reference_type; 146 | 147 | generator() noexcept = default; 148 | 149 | generator(generator&& other) noexcept 150 | : _coroutine(other._coroutine) { 151 | other._coroutine = nullptr; 152 | } 153 | 154 | generator(const generator& other) = delete; 155 | 156 | ~generator() { 157 | if (_coroutine) { 158 | _coroutine.destroy(); 159 | } 160 | } 161 | 162 | generator& operator=(generator other) noexcept { 163 | swap(other); 164 | return *this; 165 | } 166 | 167 | iterator begin() { 168 | if (_coroutine) { 169 | _coroutine.resume(); 170 | if (_coroutine.done()) { 171 | _coroutine.promise().rethrow_if_exception(); 172 | } 173 | } 174 | 175 | return iterator{ _coroutine }; 176 | } 177 | 178 | detail::generator_sentinel end() noexcept { return {}; } 179 | 180 | void swap(generator& other) noexcept { std::swap(_coroutine, other._coroutine); } 181 | 182 | private: 183 | friend struct detail::generator_promise_type< T >; 184 | 185 | explicit generator(coroutine_handle coroutine) 186 | : _coroutine(coroutine) {} 187 | 188 | coroutine_handle _coroutine = nullptr; 189 | }; 190 | 191 | namespace detail 192 | { 193 | template< typename T > 194 | generator< T > generator_promise_type< T >::get_return_object() noexcept { 195 | using coroutine_handle = detail::coroutine_handle< T >; 196 | return generator< T >{ coroutine_handle::from_promise(*this) }; 197 | } 198 | } // namespace detail 199 | 200 | } // namespace gap 201 | 202 | #endif -------------------------------------------------------------------------------- /coro/include/gap/coro/manual_reset_event.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/manual_reset_event.hpp from the 8 | // cppcoro project. The original file is licenced under the MIT license and the 9 | // original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifdef GAP_ENABLE_COROUTINES 15 | 16 | #include 17 | #include 18 | 19 | namespace gap::coro 20 | { 21 | struct manual_reset_event { 22 | void set() noexcept { 23 | std::lock_guard lock(m_mutex); 24 | m_is_set = true; 25 | m_cv.notify_all(); 26 | } 27 | 28 | void reset() noexcept { 29 | std::lock_guard lock(m_mutex); 30 | m_is_set = false; 31 | } 32 | 33 | void wait() noexcept { 34 | std::unique_lock lock(m_mutex); 35 | m_cv.wait(lock, [this] { return m_is_set; }); 36 | } 37 | 38 | private: 39 | std::mutex m_mutex; 40 | std::condition_variable m_cv; 41 | bool m_is_set = false; 42 | }; 43 | 44 | } // namespace gap::coro 45 | 46 | #endif -------------------------------------------------------------------------------- /coro/include/gap/coro/single_consumer_event.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/single_consumer_event.hpp from the 8 | // cppcoro project. The original file is licenced under the MIT license and the 9 | // original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifdef GAP_ENABLE_COROUTINES 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace gap::coro 25 | { 26 | // A manual-reset event that supports only a single awaiting 27 | // coroutine at a time. See cppcoro for more details. 28 | 29 | struct single_consumer_event 30 | { 31 | enum class state_kind { notset, set, waiting }; 32 | 33 | single_consumer_event(bool initially_set = false) noexcept 34 | : m_state(initially_set ? state_kind::set : state_kind::notset) 35 | {} 36 | 37 | bool is_set() const noexcept { 38 | return m_state.load(std::memory_order_acquire) == state_kind::set; 39 | } 40 | 41 | void set() noexcept { 42 | if (m_state.exchange(state_kind::set, std::memory_order_acq_rel) == state_kind::waiting) { 43 | m_awaiter.resume(); 44 | } 45 | } 46 | 47 | void reset() noexcept { 48 | state_kind old = state_kind::set; 49 | m_state.compare_exchange_strong(old, state_kind::notset, std::memory_order_relaxed); 50 | } 51 | 52 | auto operator co_await() noexcept { 53 | struct awaiter { 54 | single_consumer_event &m_event; 55 | 56 | bool await_ready() const noexcept { 57 | return m_event.is_set(); 58 | } 59 | 60 | bool await_suspend(gap::coroutine_handle<> handle) noexcept { 61 | m_event.m_awaiter = handle; 62 | state_kind old = state_kind::notset; 63 | return m_event.m_state.compare_exchange_strong( 64 | old, state_kind::waiting, 65 | std::memory_order_release, 66 | std::memory_order_acquire 67 | ); 68 | } 69 | 70 | void await_resume() const noexcept {} 71 | }; 72 | 73 | return awaiter{ *this }; 74 | } 75 | private: 76 | std::atomic< state_kind > m_state = state_kind::notset; 77 | gap::coroutine_handle<> m_awaiter = nullptr; 78 | }; 79 | 80 | } // namespace gap::coro 81 | 82 | #endif -------------------------------------------------------------------------------- /coro/include/gap/coro/sync_wait.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/sync_wait.hpp from the cppcoro 8 | // project. The original file is licenced under the MIT license and the original 9 | // license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | #ifdef GAP_ENABLE_COROUTINES 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace gap::coro 25 | { 26 | template< typename result_t > 27 | struct sync_wait_task; 28 | 29 | template< typename result_t > 30 | struct sync_wait_task_promise { 31 | using coroutine_handle = gap::coroutine_handle< sync_wait_task_promise >; 32 | 33 | sync_wait_task_promise() noexcept = default; 34 | 35 | void start(manual_reset_event &event) noexcept { 36 | m_event = &event; 37 | coroutine_handle::from_promise(*this).resume(); 38 | } 39 | 40 | auto get_return_object() noexcept { 41 | return coroutine_handle::from_promise(*this); 42 | } 43 | 44 | gap::suspend_always initial_suspend() noexcept { 45 | return {}; 46 | } 47 | 48 | auto final_suspend() noexcept { 49 | struct final_awaiter { 50 | bool await_ready() const noexcept { 51 | return false; 52 | } 53 | 54 | void await_suspend(coroutine_handle coroutine) noexcept { 55 | coroutine.promise().m_event->set(); 56 | } 57 | 58 | void await_resume() const noexcept {} 59 | }; 60 | 61 | return final_awaiter{}; 62 | } 63 | 64 | auto yield_value(result_t &&value) noexcept { 65 | m_result = std::addressof(value); 66 | return final_suspend(); 67 | } 68 | 69 | void return_void() noexcept { 70 | assert(false); 71 | } 72 | 73 | void unhandled_exception() noexcept { 74 | m_exception = std::current_exception(); 75 | } 76 | 77 | result_t &&result() { 78 | if (m_exception) { 79 | std::rethrow_exception(m_exception); 80 | } 81 | 82 | return static_cast< result_t&& >(*m_result); 83 | } 84 | private: 85 | manual_reset_event *m_event = nullptr; 86 | std::remove_reference_t< result_t > *m_result = nullptr; 87 | std::exception_ptr m_exception; 88 | }; 89 | 90 | template<> 91 | struct sync_wait_task_promise< void > 92 | { 93 | using coroutine_handle = gap::coroutine_handle< sync_wait_task_promise >; 94 | 95 | sync_wait_task_promise() noexcept = default; 96 | 97 | void start(manual_reset_event &event) noexcept { 98 | m_event = &event; 99 | coroutine_handle::from_promise(*this).resume(); 100 | } 101 | 102 | auto get_return_object() noexcept { 103 | return coroutine_handle::from_promise(*this); 104 | } 105 | 106 | gap::suspend_always initial_suspend() noexcept { 107 | return {}; 108 | } 109 | 110 | auto final_suspend() noexcept { 111 | struct final_awaiter { 112 | bool await_ready() const noexcept { 113 | return false; 114 | } 115 | 116 | void await_suspend(coroutine_handle coroutine) noexcept { 117 | coroutine.promise().m_event->set(); 118 | } 119 | 120 | void await_resume() const noexcept {} 121 | }; 122 | 123 | return final_awaiter{}; 124 | } 125 | 126 | void return_void() noexcept {} 127 | 128 | void unhandled_exception() noexcept { 129 | m_exception = std::current_exception(); 130 | } 131 | 132 | void result() { 133 | if (m_exception) { 134 | std::rethrow_exception(m_exception); 135 | } 136 | } 137 | 138 | private: 139 | manual_reset_event *m_event = nullptr; 140 | std::exception_ptr m_exception; 141 | }; 142 | 143 | template< typename result_t > 144 | struct sync_wait_task final 145 | { 146 | using promise_type = sync_wait_task_promise< result_t >; 147 | using coroutine_handle = gap::coroutine_handle< promise_type >; 148 | 149 | sync_wait_task(coroutine_handle coroutine) noexcept 150 | : m_coroutine(coroutine) 151 | {} 152 | 153 | sync_wait_task(sync_wait_task const &other) = delete; 154 | sync_wait_task(sync_wait_task &&other) noexcept 155 | : m_coroutine(std::exchange(other.m_coroutine, coroutine_handle{})) 156 | {} 157 | 158 | sync_wait_task &operator=(sync_wait_task const &other) = delete; 159 | sync_wait_task &operator=(sync_wait_task &&other) noexcept { 160 | m_coroutine = std::exchange(other.m_coroutine, coroutine_handle{}); 161 | return *this; 162 | } 163 | 164 | void start(manual_reset_event &event) noexcept { 165 | m_coroutine.promise().start(event); 166 | } 167 | 168 | decltype(auto) result() { 169 | return m_coroutine.promise().result(); 170 | } 171 | private: 172 | coroutine_handle m_coroutine; 173 | }; 174 | 175 | template< awaitable awaitable_t, typename result_t = await_result_t< awaitable_t&& > > 176 | requires (not std::is_void_v< result_t >) 177 | sync_wait_task< result_t > make_sync_wait_task(awaitable_t &&awaitable) { 178 | co_yield co_await std::forward< awaitable_t >(awaitable); 179 | } 180 | 181 | template< awaitable awaitable_t, typename result_t = await_result_t< awaitable_t&& > > 182 | requires std::is_void_v< result_t > 183 | sync_wait_task< void > make_sync_wait_task(awaitable_t &&awaitable) { 184 | co_await std::forward< awaitable_t >(awaitable); 185 | } 186 | 187 | template< awaitable awaitable_t > 188 | auto sync_wait(awaitable_t &&awaitable) -> await_result_t< awaitable_t > { 189 | auto task = make_sync_wait_task(std::forward< awaitable_t >(awaitable)); 190 | manual_reset_event event; 191 | task.start(event); 192 | event.wait(); 193 | return task.result(); 194 | } 195 | } // namespace gap::coro 196 | 197 | #endif -------------------------------------------------------------------------------- /coro/src/async_manual_reset_event.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace gap::coro { 13 | 14 | async_manual_reset_event::async_manual_reset_event(bool initially_set) noexcept 15 | : m_state(initially_set ? static_cast(this) : nullptr) {} 16 | 17 | async_manual_reset_event::~async_manual_reset_event() noexcept { 18 | assert( 19 | m_state.load(std::memory_order_relaxed) == nullptr || 20 | m_state.load(std::memory_order_relaxed) == static_cast(this) 21 | ); 22 | } 23 | 24 | bool async_manual_reset_event::is_set() const noexcept { 25 | return m_state.load(std::memory_order_acquire) == static_cast(this); 26 | } 27 | 28 | async_manual_reset_event_operation async_manual_reset_event::operator co_await() const noexcept { 29 | return async_manual_reset_event_operation{ *this }; 30 | } 31 | 32 | void async_manual_reset_event::set() noexcept { 33 | void* const set_state = static_cast(this); 34 | 35 | // Needs 'release' semantics so that prior writes are visible to event awaiters 36 | // that synchronise either via 'is_set()' or 'operator co_await()'. 37 | // Needs 'acquire' semantics in case there are any waiters so that we see 38 | // prior writes to the waiting coroutine's state and to the contents of 39 | // the queued async_manual_reset_event_operation objects. 40 | void* old_state = m_state.exchange(set_state, std::memory_order_acq_rel); 41 | if (old_state != set_state) 42 | { 43 | auto* current = static_cast(old_state); 44 | while (current != nullptr) 45 | { 46 | auto* next = current->m_next; 47 | current->m_awaiter.resume(); 48 | current = next; 49 | } 50 | } 51 | } 52 | 53 | void async_manual_reset_event::reset() noexcept { 54 | void* old_state = static_cast(this); 55 | m_state.compare_exchange_strong(old_state, nullptr, std::memory_order_relaxed); 56 | } 57 | 58 | async_manual_reset_event_operation::async_manual_reset_event_operation( 59 | const async_manual_reset_event& event 60 | ) noexcept 61 | : m_event(event) 62 | {} 63 | 64 | bool async_manual_reset_event_operation::await_ready() const noexcept { 65 | return m_event.is_set(); 66 | } 67 | 68 | bool async_manual_reset_event_operation::await_suspend( 69 | gap::coroutine_handle<> awaiter 70 | ) noexcept { 71 | m_awaiter = awaiter; 72 | 73 | const void* const set_state = static_cast(&m_event); 74 | 75 | void* old_state = m_event.m_state.load(std::memory_order_acquire); 76 | do 77 | { 78 | if (old_state == set_state) 79 | { 80 | // State is now 'set' no need to suspend. 81 | return false; 82 | } 83 | 84 | m_next = static_cast(old_state); 85 | } while (!m_event.m_state.compare_exchange_weak( 86 | old_state, 87 | static_cast(this), 88 | std::memory_order_release, 89 | std::memory_order_acquire) 90 | ); 91 | 92 | // Successfully queued this waiter to the list. 93 | return true; 94 | } 95 | 96 | } // namespace gap::coro -------------------------------------------------------------------------------- /doctest.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /graph/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | add_headers(graph GAP_GRAPH_HEADERS 4 | graph.hpp 5 | ) 6 | 7 | add_gap_library(gap-graph "${GAP_GRAPH_HEADERS}") 8 | 9 | target_link_libraries(gap-graph INTERFACE gap-coro) -------------------------------------------------------------------------------- /graph/include/gap/graph/graph.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2022-present, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #ifdef GAP_ENABLE_COROUTINES 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace gap::graph 13 | { 14 | // 15 | // node concepts 16 | // 17 | template< typename node_type > 18 | concept node_like = requires(node_type n) { 19 | { n.children() } -> ranges::value_range< typename node_type::child_type >; 20 | }; 21 | 22 | // 23 | // edge concepts 24 | // 25 | template< typename edge_type > 26 | concept edge_like = requires(edge_type&& e) { 27 | node_like< typename edge_type::source_type >; 28 | node_like< typename edge_type::target_type >; 29 | 30 | { e.source() } -> convertible_to< typename edge_type::source_type >; 31 | { e.target() } -> convertible_to< typename edge_type::target_type >; 32 | }; 33 | 34 | // 35 | // graph concepts 36 | // 37 | template< typename graph_type > 38 | concept graph_like = requires(graph_type&& g) { 39 | node_like< typename graph_type::node_type >; 40 | edge_like< typename graph_type::edge_type >; 41 | 42 | { g.nodes() } -> ranges::value_range< typename graph_type::node_pointer >; 43 | { g.edges() } -> ranges::value_range< typename graph_type::edge_type >; 44 | }; 45 | 46 | enum class yield_node 47 | { 48 | never, on_open, on_close 49 | }; 50 | 51 | template< typename node_pointer > 52 | using seen_set = std::unordered_set< node_pointer >; 53 | 54 | namespace detail 55 | { 56 | template< yield_node when, typename node_pointer > 57 | requires node_like< typename node_pointer::element_type > 58 | recursive_generator< node_pointer > dfs(node_pointer root, seen_set< node_pointer >& seen) { 59 | seen.insert(root); 60 | 61 | if constexpr (when == yield_node::on_open) { 62 | co_yield root; 63 | } 64 | 65 | for (auto child : root->children()) { 66 | if (!seen.count(child)) { 67 | co_yield dfs< when >(child, seen); 68 | } 69 | } 70 | 71 | if constexpr (when == yield_node::on_close) { 72 | co_yield root; 73 | }; 74 | } 75 | 76 | template< yield_node when, typename node_pointer > 77 | requires node_like< typename node_pointer::element_type > 78 | recursive_generator< node_pointer > dfs(node_pointer root) { 79 | seen_set< node_pointer > seen; 80 | co_yield dfs< when >(root, seen); 81 | } 82 | 83 | } // namespace detail 84 | 85 | template< yield_node when, graph_like graph_type, typename node_pointer = typename graph_type::node_pointer > 86 | recursive_generator< node_pointer > dfs(const graph_type &graph) { 87 | seen_set< node_pointer > seen; 88 | for (auto root : graph.nodes()) { 89 | if (!seen.count(root)) { 90 | co_yield detail::dfs< when >(root, seen); 91 | } 92 | } 93 | } 94 | 95 | template< yield_node when, typename node_pointer > 96 | requires node_like< typename node_pointer::element_type > 97 | recursive_generator< node_pointer > dfs(node_pointer root) { 98 | return detail::dfs< when >(root); 99 | } 100 | 101 | template< typename node_pointer > 102 | requires node_like< typename node_pointer::element_type > 103 | recursive_generator< node_pointer > toposort(node_pointer root) { 104 | return dfs< yield_node::on_close >(root); 105 | } 106 | 107 | } // namespace gap::graph 108 | 109 | #endif // GAP_ENABLE_COROUTINES 110 | -------------------------------------------------------------------------------- /mlir/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024, Trail of Bits, Inc. All rights reserved. 2 | 3 | add_headers(mlir GAP_MLIR_HEADERS 4 | common.hpp 5 | functors.hpp 6 | views.hpp 7 | ) 8 | 9 | add_sources(coro GAP_MLIR_SOURCES 10 | views.cpp 11 | ) 12 | 13 | add_gap_static_library(gap-mlir 14 | "${GAP_MLIR_HEADERS}" 15 | "${GAP_MLIR_SOURCES}" 16 | SYSTEM_INCLUDE_DIRECTORIES 17 | "${MLIR_INCLUDE_DIRS}" 18 | "${LLVM_INCLUDE_DIRS}" 19 | LINK_LIBRARIES 20 | MLIRIR 21 | MLIRSupport 22 | ) 23 | -------------------------------------------------------------------------------- /mlir/include/gap/mlir/common.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace gap::mlir 18 | { 19 | using attr = ::mlir::Attribute; 20 | using type = ::mlir::Type; 21 | using type_range = ::mlir::TypeRange; 22 | using value = ::mlir::Value; 23 | using block = ::mlir::Block; 24 | using region = ::mlir::Region; 25 | using operation = ::mlir::Operation *; 26 | using context = ::mlir::MLIRContext; 27 | using location = ::mlir::Location; 28 | 29 | using module = ::mlir::ModuleOp; 30 | using owning_module_ref = ::mlir::OwningOpRef< module >; 31 | 32 | using builder = ::mlir::Builder; 33 | using op_builder = ::mlir::OpBuilder; 34 | 35 | } // namespace gap::mlir -------------------------------------------------------------------------------- /mlir/include/gap/mlir/functors.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2024, Trail of Bits, Inc. 2 | 3 | #pragma once 4 | 5 | namespace gap::mlir 6 | { 7 | template< typename T > 8 | constexpr auto isa = []< typename U >(U &&u) { 9 | return ::mlir::isa< T >(std::forward< U >(u)); 10 | }; 11 | 12 | template< typename T > 13 | constexpr auto dyn_cast = []< typename U >(U &&u) { 14 | return ::mlir::dyn_cast< T >(std::forward< U >(u)); 15 | }; 16 | 17 | template< typename T > 18 | constexpr auto cast = []< typename U >(U &&u) { 19 | return ::mlir::cast< T >(std::forward< U >(u)); 20 | }; 21 | 22 | } // namespace gap::mlir -------------------------------------------------------------------------------- /mlir/include/gap/mlir/views.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Workaround to allow ABI breaking changes in join_view 6 | #ifdef _LIBCPP_ENABLE_EXPERIMENTAL 7 | #define _LIBCPP_ENABLE_EXPERIMENTAL_WAS_DEFINED 8 | #endif 9 | 10 | #if _LIBCPP_VERSION 11 | #define _LIBCPP_ENABLE_EXPERIMENTAL 12 | #endif 13 | 14 | #include 15 | 16 | #ifndef _LIBCPP_ENABLE_EXPERIMENTAL_WAS_DEFINED 17 | #if _LIBCPP_VERSION 18 | #undef _LIBCPP_ENABLE_EXPERIMENTAL 19 | #endif 20 | #endif 21 | 22 | #include 23 | 24 | namespace gap::mlir::views 25 | { 26 | static inline auto regions(operation op) -> decltype(auto) { 27 | auto regs = op->getRegions(); 28 | return std::ranges::subrange{regs.begin(), regs.end()}; 29 | } 30 | 31 | static inline auto blocks(operation op) -> decltype(auto) { 32 | return regions(op) | std::views::join; 33 | } 34 | 35 | static inline auto operations(operation op) -> decltype(auto) { 36 | return blocks(op) | std::views::join; 37 | } 38 | 39 | template< typename T > 40 | auto isa = std::views::filter(::gap::mlir::isa< T >); 41 | 42 | template< typename T > 43 | auto cast = std::views::transform(::gap::mlir::cast< T >); 44 | 45 | template< typename T > 46 | auto dyn_cast = std::views::transform(::gap::mlir::dyn_cast< T >); 47 | 48 | template< typename T > 49 | auto filter_cast = isa< T > | cast< T >; 50 | 51 | } // namespace gap::mlir::views -------------------------------------------------------------------------------- /mlir/src/views.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace gap::mlir::views 4 | { 5 | 6 | } // namespace gap::mlir::views -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | ## Building & Testing 2 | 3 | ## Structure 4 | 5 | 1. Core GAP 6 | - contracts 7 | - error handling 8 | - parsing 9 | - logging 10 | - coroutines 11 | - ranges 12 | - cmake utils? 13 | 14 | 2. LLVM GAP 15 | - ranges 16 | - sc 17 | 18 | 3. MLIR GAP 19 | - parser 20 | - fmt 21 | - sc 22 | - ranges 23 | 24 | 4. Interpreter? 25 | 26 | 5. Eqsat? 27 | 28 | 6. anyir 29 | 30 | - peter request: copy and move constant around modules 31 | - llvm constant interpreter (ask ian) + value domain: 32 | https://github.com/lifting-bits/anvill/blob/api_simplifications_and_extensions/lib/CrossReferenceFolder.cpp 33 | - https://github.com/trailofbits/multiplier-old/blob/8576bc13b81cb0138fcf2e456db60374d7ddf8f5/multiplier/Util/Init.cpp 34 | - llvm init stuff 35 | - llvm setup: 36 | - https://github.com/lifting-bits/anvill/blob/api_simplifications_and_extensions/cmake/llvm.cmake 37 | -------------------------------------------------------------------------------- /sarif/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2024-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | if (GAP_ENABLE_SARIF) 4 | find_package(nlohmann_json CONFIG REQUIRED) 5 | 6 | add_headers(sarif GAP_SARIF_HEADERS 7 | sarif.hpp 8 | ) 9 | 10 | add_sources(sarif GAP_SARIF_SOURCES 11 | sarif.cpp 12 | ) 13 | 14 | add_gap_static_library(gap-sarif "${GAP_SARIF_HEADERS}" "${GAP_SARIF_SOURCES}") 15 | target_link_libraries(gap-sarif PUBLIC nlohmann_json::nlohmann_json) 16 | target_compile_definitions(gap-sarif PUBLIC GAP_ENABLE_SARIF=1) 17 | endif() -------------------------------------------------------------------------------- /sarif/scripts/sarif-headergen/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm-project.org/#use-with-ide 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /sarif/scripts/sarif-headergen/README.md: -------------------------------------------------------------------------------- 1 | # sarif-headergen 2 | 3 | Generates nlohmann::json (de)serialization files from the JSON schema for SARIF files. 4 | 5 | ## Usage 6 | 7 | $ python3 -m src.sarif_headergen.__cli__ sarif.json out.hpp out.cpp 8 | -------------------------------------------------------------------------------- /sarif/scripts/sarif-headergen/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sarif-headergen" 3 | version = "0.1.0" 4 | description = "Generates nlohmann::json (de)serialization files from the JSON schema for SARIF files" 5 | authors = [ 6 | {name = "Francesco Bertolaccini", email = "francesco.bertolaccini@trailofbits.com"}, 7 | ] 8 | dependencies = [] 9 | requires-python = ">=3.12" 10 | readme = "README.md" 11 | license = {text = "MIT"} 12 | 13 | [build-system] 14 | requires = ["pdm-backend"] 15 | build-backend = "pdm.backend" 16 | 17 | 18 | [tool.pdm] 19 | distribution = true 20 | -------------------------------------------------------------------------------- /sarif/scripts/sarif-headergen/src/sarif_headergen/__init__.py: -------------------------------------------------------------------------------- 1 | from .schema import Schema 2 | 3 | __all__ = [Schema] 4 | -------------------------------------------------------------------------------- /sarif/scripts/sarif-headergen/src/sarif_headergen/output.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | from functools import cache 3 | from dataclasses import dataclass 4 | 5 | from .schema import Schema 6 | 7 | type FieldType = ( 8 | Literal["string", "number", "boolean", "integer", "json"] | "Array" | "Struct" | "Enum" | "ForwardRef" | "Map" 9 | ) 10 | 11 | @dataclass(frozen=True) 12 | class Array: 13 | subtype: FieldType 14 | 15 | 16 | @dataclass(frozen=True) 17 | class Field: 18 | name: str 19 | type: FieldType 20 | description: str | None 21 | required: bool 22 | default: str | int | float | bool | list | None 23 | 24 | 25 | @dataclass(frozen=True) 26 | class Enum: 27 | name: str 28 | values: list[str] 29 | 30 | 31 | @dataclass(frozen=True) 32 | class Struct: 33 | name: str 34 | fields: list[Field] 35 | description: str | None 36 | additional_props: FieldType | None 37 | 38 | @dataclass(frozen=True) 39 | class ForwardRef: 40 | name: str 41 | 42 | @dataclass(frozen=True) 43 | class Map: 44 | subtype: FieldType 45 | 46 | 47 | def get_structs(schema: Schema) -> list[Struct | Enum | ForwardRef]: 48 | assert "definitions" in schema 49 | 50 | res: list[Struct | Enum | ForwardRef] = [] 51 | defs: dict[str, FieldType] = {} 52 | state: dict[str, Literal["visiting", "visited"]] = {} 53 | 54 | definitions = schema["definitions"] 55 | 56 | def get_field(name: str, field: Schema) -> FieldType: 57 | if "$ref" in field: 58 | return get_ref(field["$ref"]) 59 | 60 | if "type" in field: 61 | if field["type"] in ("boolean", "number", "string", "integer"): 62 | return field["type"] 63 | if field["type"] == "array": 64 | items = field["items"] 65 | return Array(subtype=get_field(name, items)) 66 | if field["type"] == "object": 67 | if "properties" not in field and "additionalProperties" in field and field["additionalProperties"] != False: 68 | if field["additionalProperties"] != True: 69 | return Map(subtype=get_field(name, field["additionalProperties"])) 70 | else: 71 | return "json" 72 | return make_struct(name, field) 73 | raise Exception("Invalid type") 74 | 75 | if "enum" in field: 76 | if name in defs: 77 | return defs[name] 78 | enum = Enum(name, field["enum"]) 79 | res.append(enum) 80 | defs[name] = enum 81 | return enum 82 | 83 | raise Exception("Unknown field type") 84 | 85 | @cache 86 | def get_ref(ref: str) -> FieldType: 87 | assert ref.startswith("#/definitions/") 88 | name = ref[len("#/definitions/") :] 89 | if name in state: 90 | if state[name] == "visiting": 91 | if name in defs: 92 | return defs[name] 93 | else: 94 | fwd = ForwardRef(name) 95 | res.append(fwd) 96 | defs[name] = fwd 97 | return fwd 98 | else: 99 | return defs[name] 100 | 101 | state[name] = "visiting" 102 | result = get_field(name, definitions[name]) 103 | defs[name] = result 104 | state[name] = "visited" 105 | return result 106 | 107 | def make_struct(name: str, type: Schema): 108 | assert type["type"] == "object" 109 | additional_props: FieldType | None = None 110 | if "additionalProperties" in type: 111 | if type["additionalProperties"] == True: 112 | additional_props = "json" 113 | elif type["additionalProperties"] != False: 114 | additional_props = get_field("additional_properties", type["additionalProperties"]) 115 | fields: list[Field] = [] 116 | if "properties" in type: 117 | fields = [ 118 | Field( 119 | name=field_name, 120 | type=get_field(field_name, field_type), 121 | description=field_type.get("description", None), 122 | required="required" in type and field_name in type["required"], 123 | default=field_type.get("default", None), 124 | ) 125 | for field_name, field_type in type["properties"].items() 126 | if field_name != "$schema" 127 | ] 128 | struct = Struct( 129 | name, 130 | fields=fields, 131 | description=type.get("description", None), 132 | additional_props=additional_props, 133 | ) 134 | res.append(struct) 135 | defs[name] = struct 136 | return struct 137 | 138 | make_struct("root", schema) 139 | 140 | for name in schema["definitions"]: 141 | get_ref(f"#/definitions/{name}") 142 | 143 | return res 144 | -------------------------------------------------------------------------------- /sarif/scripts/sarif-headergen/src/sarif_headergen/schema.py: -------------------------------------------------------------------------------- 1 | from typing import TypedDict, NotRequired, Union 2 | from enum import Enum 3 | 4 | 5 | class Type(str, Enum): 6 | null = "null" 7 | boolean = "boolean" 8 | object = "object" 9 | array = "array" 10 | number = "number" 11 | string = "string" 12 | integer = "integer" 13 | 14 | 15 | Schema = TypedDict( 16 | "Schema", 17 | { 18 | "title": NotRequired[str], 19 | "description": NotRequired[str], 20 | "type": NotRequired[Type], 21 | "$ref": NotRequired[str], 22 | "items": NotRequired["Schema"], 23 | "properties": NotRequired[dict[str, "Schema"]], 24 | "additionalProperties": NotRequired[Union["Schema", bool]], 25 | "definitions": NotRequired[dict[str, "Schema"]], 26 | "enum": NotRequired[list[str]], 27 | "required": NotRequired[list[str]], 28 | "default": NotRequired[str | int | float | bool | list], 29 | }, 30 | ) 31 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2021-present, Trail of Bits, Inc. All rights reserved. 2 | 3 | find_package(doctest CONFIG REQUIRED) 4 | find_package(spdlog CONFIG REQUIRED) 5 | 6 | set(GAP_TEST_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" CACHE INTERNAL "GAP test source directory") 7 | 8 | function(add_gap_test name) 9 | add_executable(${name} ${GAP_TEST_SOURCE_DIR}/main.cpp ${ARGN}) 10 | target_compile_features(${name} PRIVATE cxx_std_20) 11 | target_link_libraries(${name} 12 | PRIVATE 13 | gap-settings 14 | gap-core 15 | 16 | doctest::doctest 17 | spdlog::spdlog 18 | ) 19 | 20 | target_include_directories(${name} 21 | PUBLIC 22 | ${DOCTEST_INCLUDE_DIR} 23 | PRIVATE 24 | $ 25 | ) 26 | 27 | add_test( 28 | NAME ${name} 29 | COMMAND "$" 30 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 31 | ) 32 | endfunction() 33 | 34 | add_subdirectory(core) 35 | add_subdirectory(coro) 36 | add_subdirectory(graph) 37 | 38 | if (${GAP_ENABLE_SARIF}) 39 | add_subdirectory(sarif) 40 | endif() 41 | 42 | if (${GAP_ENABLE_MLIR}) 43 | add_subdirectory(mlir) 44 | endif() 45 | -------------------------------------------------------------------------------- /test/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2024, Trail of Bits, Inc. All rights reserved. 2 | 3 | add_gap_test(test-gap-core 4 | bigint.cpp 5 | benchmark.cpp 6 | crtp.cpp 7 | dense_map.cpp 8 | hash.cpp 9 | memoize.cpp 10 | optional.cpp 11 | parser.cpp 12 | ranges.cpp 13 | strong_type.cpp 14 | union_find.cpp 15 | ) 16 | 17 | target_link_libraries(test-gap-core 18 | PUBLIC 19 | gap-core 20 | ) -------------------------------------------------------------------------------- /test/core/benchmark.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gap::test 9 | { 10 | namespace bench = gap::bench; 11 | 12 | static_assert(bench::sum(std::array{ 1, 2, 3 }) == 6); 13 | 14 | static_assert(bench::mean(std::array{ 1, 2, 3 }) == 2); 15 | static_assert(bench::mean(std::array{ 2, 4, 4, 4, 5, 5, 7, 9 }) == 5); 16 | static_assert(bench::standard_deviation(std::array{ 2, 4, 4, 4, 5, 5, 7, 9 }) == 4); 17 | 18 | } // namespace gap::test 19 | -------------------------------------------------------------------------------- /test/core/concepts.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace gap::test 9 | { 10 | struct dummy {}; 11 | enum dummy_enum : int 12 | { 13 | }; 14 | 15 | static_assert(integral< int >); 16 | static_assert(integral< dummy > == false); 17 | static_assert(integral< dummy_enum > == false); 18 | static_assert(integral< float > == false); 19 | static_assert(integral< const int >); 20 | static_assert(integral< bool >); 21 | 22 | static_assert(arithmetic< int >); 23 | static_assert(arithmetic< dummy > == false); 24 | static_assert(arithmetic< dummy_enum > == false); 25 | static_assert(arithmetic< float >); 26 | static_assert(arithmetic< const double >); 27 | static_assert(arithmetic< const int >); 28 | static_assert(arithmetic< bool >); 29 | 30 | static_assert(floating< int > == false); 31 | static_assert(floating< dummy > == false); 32 | static_assert(floating< dummy_enum > == false); 33 | static_assert(floating< float >); 34 | static_assert(floating< const double >); 35 | static_assert(floating< const int > == false); 36 | static_assert(floating< bool > == false); 37 | 38 | } // namespace gap::test 39 | -------------------------------------------------------------------------------- /test/core/crtp.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace gap::test 8 | { 9 | template< typename derived > 10 | struct base : gap::core::crtp< derived, base > { 11 | using gap::core::crtp< derived, base >::underlying; 12 | 13 | int foo() { return underlying().bar(); } 14 | }; 15 | 16 | struct derived : base< derived > { 17 | int bar() { return 10; } 18 | }; 19 | 20 | TEST_CASE("CRTP test") { 21 | derived d; 22 | CHECK(d.foo() == 10); 23 | } 24 | 25 | } // namespace gap::test 26 | -------------------------------------------------------------------------------- /test/core/dense_map.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #include 4 | #include 5 | 6 | namespace gap::test 7 | { 8 | TEST_SUITE("dense map") { 9 | TEST_CASE("basic") { 10 | dense_map< unsigned, char > map; 11 | 12 | CHECK(map.full()); 13 | CHECK(map.empty()); 14 | 15 | map[3] = 'c'; 16 | map[1] = 'a'; 17 | 18 | CHECK(map.contains(1)); 19 | CHECK(map[1] == 'a'); 20 | 21 | CHECK(map.contains(3)); 22 | CHECK(map[3] == 'c'); 23 | 24 | CHECK(!map.contains(2)); 25 | 26 | CHECK(map.size() == 4); 27 | 28 | CHECK(!map.full()); 29 | 30 | map[0] = 'd'; 31 | map[2] = 'e'; 32 | CHECK(map.full()); 33 | } 34 | 35 | TEST_CASE("default value") { 36 | dense_map< unsigned, int > nums; 37 | 38 | nums[1] = 0; 39 | CHECK(!nums.contains(1)); 40 | nums[1] = -1; 41 | CHECK(nums.contains(1)); 42 | CHECK(nums[1] == -1); 43 | } 44 | 45 | TEST_CASE("iterators") { 46 | dense_map< unsigned, int > map; 47 | 48 | map[3] = 3; 49 | map[1] = 1; 50 | 51 | SUBCASE("begin") { 52 | auto it = map.begin(); 53 | CHECK(it->first == 0); 54 | CHECK(it->second == int()); 55 | } 56 | 57 | SUBCASE("end") { 58 | auto it = --map.end(); 59 | CHECK(it->first == 3); 60 | CHECK(it->second == 3); 61 | } 62 | } 63 | 64 | TEST_CASE("comparison") { 65 | dense_map< unsigned, char > a; 66 | a[1] = 'a'; 67 | dense_map< unsigned, char > b; 68 | b[1] = 'b'; 69 | 70 | CHECK(a != b); 71 | 72 | dense_map< unsigned, char > c; 73 | c[1] = 'a'; 74 | 75 | CHECK(a == c); 76 | } 77 | } // test suite dense map 78 | } // namespace gap::test 79 | -------------------------------------------------------------------------------- /test/core/hash.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace gap::test 10 | { 11 | static_assert(hashable< int >); 12 | static_assert(hashable< const int & >); 13 | static_assert(hashable< int && >); 14 | 15 | TEST_CASE("std hash") { 16 | CHECK_EQ(gap::hash{}(0), gap::hash{}(0)); 17 | CHECK_EQ(gap::hash{}(+0.0f), gap::hash{}(-0.0f)); 18 | } 19 | 20 | TEST_CASE("std const hash") { 21 | const int v = 0; 22 | CHECK_EQ(gap::hash{}(v), gap::hash{}(0)); 23 | } 24 | 25 | static_assert(gap::supports_hash_value< std::tuple< int > >); 26 | 27 | TEST_CASE("tuple hash") { 28 | using point = std::tuple< int, int, int >; 29 | auto lhs = gap::hash{}(point{1, 2, 3}); 30 | auto rhs = gap::hash{}(point{1, 2, 3}); 31 | CHECK_EQ(lhs, rhs); 32 | 33 | auto other = gap::hash{}(point{3, 2, 1}); 34 | CHECK_NE(lhs, other); 35 | } 36 | 37 | TEST_CASE("tuple hash key") { 38 | using point = std::tuple< int, int, int >; 39 | (void)gap::hash< point >{}; 40 | std::unordered_set< point, gap::hash< point > > set; 41 | 42 | set.insert(point{0, 0, 0}); 43 | set.insert(point{0, 0, 1}); 44 | set.insert(point{0, 0, 0}); 45 | CHECK_EQ(set.size(), 2); 46 | } 47 | 48 | static_assert(gap::supports_hash_value< std::tuple< int, std::tuple< int, int > > >); 49 | 50 | TEST_CASE("nested tuple hash") { 51 | using nested = std::tuple< int, std::tuple< int, int > >; 52 | std::unordered_set< nested, gap::hash< nested > > set; 53 | 54 | set.insert(nested{1,{0, 0}}); 55 | set.insert(nested{1,{1, 0}}); 56 | set.insert(nested{1,{0, 1}}); 57 | CHECK_EQ(set.size(), 3); 58 | } 59 | 60 | TEST_CASE("pair hash") { 61 | using pair = std::pair< int, char >; 62 | 63 | auto lhs = gap::hash{}(pair{1, 'a'}); 64 | auto rhs = gap::hash{}(pair{1, 'a'}); 65 | CHECK_EQ(lhs, rhs); 66 | 67 | auto other = gap::hash{}(pair{1, 'b'}); 68 | CHECK_NE(lhs, other); 69 | } 70 | 71 | TEST_CASE("hash array") { 72 | auto lhs = std::array{1, 2, 3}; 73 | auto rhs = std::array{1, 2, 3}; 74 | 75 | CHECK_EQ( 76 | gap::hash< decltype(lhs) >{}(lhs), 77 | gap::hash< decltype(rhs) >{}(rhs) 78 | ); 79 | 80 | auto other = std::array{3, 2, 1}; 81 | CHECK_NE( 82 | gap::hash< decltype(lhs) >{}(lhs), 83 | gap::hash< decltype(other) >{}(other) 84 | ); 85 | 86 | auto one = std::array{1}; 87 | auto ones = std::array{1, 1}; 88 | 89 | CHECK_NE( 90 | gap::hash< decltype(one) >{}(one), 91 | gap::hash< decltype(ones) >{}(ones) 92 | ); 93 | } 94 | 95 | } // namespace gap::test 96 | -------------------------------------------------------------------------------- /test/core/memoize.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace gap::test 10 | { 11 | void checks_addition(auto fn) { 12 | CHECK_EQ(fn(1, 2), 3); 13 | 14 | CHECK_EQ(fn.cache_size(), 1); 15 | CHECK( fn.cached(1, 2)); 16 | CHECK(!fn.cached(2, 1)); 17 | 18 | CHECK_EQ(fn(2, 1), 3); 19 | 20 | CHECK_EQ(fn.cache_size(), 2); 21 | CHECK(fn.cached(2, 1)); 22 | } 23 | 24 | void checks_fib(auto fn) { 25 | CHECK_EQ(fn(1), 1); 26 | CHECK_EQ(fn.cache_size(), 1); 27 | CHECK(!fn.cached(0)); 28 | 29 | CHECK_EQ(fn(5), 5); 30 | CHECK_EQ(fn.cache_size(), 6); 31 | CHECK(!fn.cached(6)); 32 | 33 | CHECK_EQ(fn(10), 55); 34 | CHECK_EQ(fn.cache_size(), 11); 35 | } 36 | 37 | TEST_SUITE("memoization") { 38 | int free_function(int a, int b) { return a + b; } 39 | TEST_CASE("free-function") { 40 | auto fn = memoize(&free_function); 41 | checks_addition(fn); 42 | } 43 | 44 | TEST_CASE("lambda-move") { 45 | auto fn = memoize([](int a, int b) { return a + b; }); 46 | checks_addition(fn); 47 | } 48 | 49 | TEST_CASE("lambda-ref") { 50 | auto add = [] (int a, int b) { return a + b; }; 51 | auto fn = memoize(add); 52 | checks_addition(fn); 53 | } 54 | 55 | struct function_object { 56 | int operator() (int a, int b) const { return a + b; } 57 | }; 58 | 59 | TEST_CASE("function object") { 60 | auto fn = memoize(function_object()); 61 | checks_addition(fn); 62 | } 63 | 64 | struct poly_function_object { 65 | int operator() (int a, int b) const { return a + b; } 66 | double operator() (double a, double b) const { return a + b; } 67 | }; 68 | 69 | TEST_CASE("function object") { 70 | auto fn = memoize(poly_function_object()); 71 | checks_addition(fn); 72 | } 73 | 74 | auto fib = [] (auto &self, int n) -> int { 75 | if (n == 0) return 0; 76 | if (n == 1) return 1; 77 | return self(n - 1) + self(n - 2); 78 | }; 79 | 80 | TEST_CASE("result-type-signature") { 81 | auto fn = recursive_memoize(fib); 82 | checks_fib(fn); 83 | } 84 | 85 | TEST_CASE("explicit-signature") { 86 | auto fn = recursive_memoize(fib); 87 | checks_fib(fn); 88 | } 89 | 90 | TEST_CASE("count-calls") { 91 | unsigned calls = 0; 92 | auto fn = memoize([&calls](int a, int b) { 93 | return ++calls, a + b; 94 | }); 95 | 96 | CHECK_EQ(fn(1, 2), 3); 97 | CHECK_EQ(fn(1, 2), 3); 98 | CHECK_EQ(fn(1, 2), 3); 99 | CHECK_EQ(calls, 1); 100 | } 101 | } 102 | 103 | } // namespace gap::test 104 | -------------------------------------------------------------------------------- /test/core/ranges.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace gap::test 13 | { 14 | namespace gr = gap::ranges; 15 | 16 | struct dummy {}; 17 | 18 | static_assert(gr::range< std::vector< int > >); 19 | static_assert(gr::range< std::vector< dummy > >); 20 | static_assert(gr::range< std::array< int, 10 > >); 21 | static_assert(gr::range< int > == false); 22 | 23 | static_assert(gr::arithmetic_range< std::vector< int > >); 24 | static_assert(gr::arithmetic_range< std::vector< float > >); 25 | static_assert(gr::arithmetic_range< std::vector< dummy > > == false); 26 | 27 | 28 | static_assert(std::is_same_v< gr::range_value_t< std::vector< int > >, int >); 29 | static_assert(std::is_same_v< gr::range_value_t< std::vector< int* > >, int* >); 30 | static_assert(std::is_same_v< 31 | gr::range_value_t< std::vector< int > >, std::vector< int >::value_type 32 | >); 33 | 34 | static_assert(std::is_same_v< 35 | gr::range_value_t< std::vector< dummy > >, std::vector< dummy >::value_type 36 | >); 37 | 38 | TEST_SUITE("ranges") { 39 | TEST_CASE("to vector") { 40 | std::vector< int > v = {1, 2, 3, 4, 5}; 41 | auto v2 = v | gr::to< std::vector >; 42 | CHECK(v == v2); 43 | } 44 | 45 | TEST_CASE("to list") { 46 | std::vector< int > v = {1, 2, 3, 4, 5}; 47 | auto l = v | gr::to< std::list >; 48 | 49 | CHECK(std::equal(v.begin(), v.end(), l.begin(), l.end())); 50 | } 51 | 52 | TEST_CASE("to with filter") { 53 | std::vector< int > v = {1, 2, 3, 4, 5}; 54 | auto v2 = v | std::views::filter([] (int x) { return x % 2 == 0; }) 55 | | gr::to< std::vector >; 56 | CHECK(v2 == std::vector{2, 4}); 57 | } 58 | } 59 | 60 | } // namespace gap::test 61 | -------------------------------------------------------------------------------- /test/core/strong_type.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-present, Trail of Bits, Inc. 2 | 3 | #include // for operator<, strong_ordering 4 | #include 5 | #include // for strong_type 6 | 7 | namespace gap::test 8 | { 9 | struct int_tag {}; 10 | using tagged_int = gap::strong_type< int, int_tag >; 11 | 12 | static_assert(tagged_int(1) < tagged_int(2)); 13 | static_assert(tagged_int(1) == tagged_int(1)); 14 | static_assert(tagged_int(2) > tagged_int(0)); 15 | 16 | TEST_CASE("tagged int") { 17 | tagged_int a(1), b(1); 18 | CHECK(a == b); 19 | } 20 | 21 | struct simple_tag {}; 22 | struct empty_struct { 23 | friend constexpr auto operator<=>(const empty_struct&, const empty_struct&) = default; 24 | }; 25 | 26 | using tagged_struct = gap::strong_type< empty_struct, simple_tag >; 27 | 28 | static_assert(tagged_struct() == tagged_struct()); 29 | 30 | struct string_tag {}; 31 | using tagged_string = gap::strong_type< std::string, string_tag >; 32 | 33 | TEST_CASE("tagged string") { 34 | tagged_string a1("a"), a2("a"); 35 | CHECK(a1 == a2); 36 | } 37 | 38 | } // namespace gap::test 39 | -------------------------------------------------------------------------------- /test/core/union_find.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace gap::test 8 | { 9 | using union_type = gap::union_type; 10 | TEST_CASE("union-find-size-constructor") { 11 | constexpr int size = 5; 12 | union_find uf(size); 13 | 14 | auto zero = union_type(0u); 15 | CHECK_EQ(uf.parent(zero), zero); 16 | CHECK_EQ(uf.rank(zero), rank_type(0u)); 17 | } 18 | 19 | TEST_CASE("resizable-union-find") { 20 | resizable_union_find uf(0); 21 | 22 | auto a = uf.make_new_set(); 23 | auto b = uf.make_new_set(); 24 | CHECK_EQ(uf.size(), 2); 25 | 26 | CHECK_NE(uf.parent(a), uf.parent(b)); 27 | 28 | uf.merge(uf.parent(a), uf.parent(b)); 29 | CHECK_EQ(uf.parent(a), uf.parent(b)); 30 | } 31 | 32 | } // namespace gap::test 33 | -------------------------------------------------------------------------------- /test/coro/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2024, Trail of Bits, Inc. All rights reserved. 2 | 3 | add_gap_test(test-gap-coro 4 | counted.cpp 5 | generator.cpp 6 | recursive_generator.cpp 7 | shared_task.cpp 8 | sync_wait.cpp 9 | task.cpp 10 | when_all_ready.cpp 11 | ) 12 | 13 | target_link_libraries(test-gap-coro 14 | PUBLIC 15 | gap-coro 16 | ) -------------------------------------------------------------------------------- /test/coro/counted.cpp: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Lewis Baker 3 | // Licenced under MIT license. See LICENSE.txt for details. 4 | /////////////////////////////////////////////////////////////////////////////// 5 | // This file is a modified version of cppcoro/test/counted.cpp from the cppcoro 6 | // project. The original file is licenced under the MIT license and the original 7 | // license is included above. 8 | /////////////////////////////////////////////////////////////////////////////// 9 | 10 | #include "counted.hpp" 11 | 12 | int counted::default_construction_count; 13 | int counted::copy_construction_count; 14 | int counted::move_construction_count; 15 | int counted::destruction_count; -------------------------------------------------------------------------------- /test/coro/counted.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/test/counted.hpp from the cppcoro 8 | // project. The original file is licenced under the MIT license and the original 9 | // license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #pragma once 13 | 14 | struct counted 15 | { 16 | static int default_construction_count; 17 | static int copy_construction_count; 18 | static int move_construction_count; 19 | static int destruction_count; 20 | 21 | int id; 22 | 23 | static void reset_counts() 24 | { 25 | default_construction_count = 0; 26 | copy_construction_count = 0; 27 | move_construction_count = 0; 28 | destruction_count = 0; 29 | } 30 | 31 | static int construction_count() 32 | { 33 | return default_construction_count + copy_construction_count + move_construction_count; 34 | } 35 | 36 | static int active_count() 37 | { 38 | return construction_count() - destruction_count; 39 | } 40 | 41 | counted() : id(default_construction_count++) {} 42 | counted(const counted& other) : id(other.id) { ++copy_construction_count; } 43 | counted(counted&& other) : id(other.id) { ++move_construction_count; other.id = -1; } 44 | ~counted() { ++destruction_count; } 45 | 46 | }; -------------------------------------------------------------------------------- /test/coro/generator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-present, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/test/generator.cpp from the 8 | // cppcoro project. The original file is licenced under the MIT license and the 9 | // original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #ifdef GAP_ENABLE_COROUTINES 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | namespace gap::test 21 | { 22 | generator< int > iota() { 23 | int value = 0; 24 | while (true) { 25 | co_yield value; 26 | ++value; 27 | } 28 | } 29 | 30 | TEST_CASE("tagged int") { 31 | int i = 0; 32 | for (int v : iota()) { 33 | CHECK_EQ(i++, v); 34 | if (i == 5) 35 | break; 36 | } 37 | } 38 | 39 | template< gap::ranges::range R > 40 | auto from_range(R &&rng) -> range_generator< R > { 41 | for (auto v : rng) { 42 | co_yield v; 43 | } 44 | } 45 | 46 | TEST_CASE("range generator") { 47 | std::vector< int > t{1, 2, 3}; 48 | auto g = from_range(t); 49 | auto b = from_range(g); 50 | } 51 | 52 | generator< int > empty_generator() { 53 | return {}; 54 | } 55 | 56 | TEST_CASE("empty generator") { 57 | auto v = empty_generator(); 58 | } 59 | 60 | } // namespace gap::test 61 | 62 | #endif // GAP_ENABLE_COROUTINES 63 | -------------------------------------------------------------------------------- /test/coro/recursive_generator.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-present, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/test/recursive_generator.cpp from 8 | // the cppcoro project. The original file is licenced under the MIT license and 9 | // the original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #ifdef GAP_ENABLE_COROUTINES 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace gap::test 19 | { 20 | TEST_CASE("empty recursive generator") { 21 | recursive_generator< std::uint32_t > ints; 22 | CHECK(ints.begin() == ints.end()); 23 | } 24 | 25 | TEST_CASE("non-recursive use of recursive_generator") { 26 | auto f = []() -> recursive_generator< float > { 27 | co_yield 1.0f; 28 | co_yield 2.0f; 29 | }; 30 | 31 | auto gen = f(); 32 | auto iter = gen.begin(); 33 | CHECK_EQ(*iter, 1.0f); 34 | ++iter; 35 | CHECK_EQ(*iter, 2.0f); 36 | ++iter; 37 | CHECK_EQ(iter, gen.end()); 38 | } 39 | 40 | TEST_CASE("throw before first yield") { 41 | class test_exception : public std::exception {}; 42 | 43 | auto f = []() -> recursive_generator< std::uint32_t > { 44 | throw test_exception{}; 45 | co_return; 46 | }; 47 | 48 | auto gen = f(); 49 | CHECK_THROWS_AS(gen.begin(), test_exception); 50 | } 51 | 52 | TEST_CASE("generator doesn't start executing until begin is called") { 53 | bool reachedA = false; 54 | bool reachedB = false; 55 | bool reachedC = false; 56 | auto f = [&]() -> recursive_generator< std::uint32_t > { 57 | reachedA = true; 58 | co_yield 1; 59 | reachedB = true; 60 | co_yield 2; 61 | reachedC = true; 62 | }; 63 | 64 | auto gen = f(); 65 | CHECK(!reachedA); 66 | auto iter = gen.begin(); 67 | CHECK(reachedA); 68 | CHECK(!reachedB); 69 | CHECK_EQ(*iter, 1u); 70 | ++iter; 71 | CHECK(reachedB); 72 | CHECK(!reachedC); 73 | CHECK_EQ(*iter, 2u); 74 | ++iter; 75 | CHECK(reachedC); 76 | CHECK_EQ(iter, gen.end()); 77 | } 78 | 79 | TEST_CASE("simple recursive yield") { 80 | auto f = [](std::uint32_t n, auto& self) -> recursive_generator< const std::uint32_t > { 81 | co_yield n; 82 | if (n > 0) { 83 | co_yield self(n - 1, self); 84 | co_yield n; 85 | } 86 | }; 87 | 88 | auto f2 = [&f](std::uint32_t n) { return f(n, f); }; 89 | 90 | { 91 | auto gen = f2(1); 92 | auto iter = gen.begin(); 93 | CHECK_EQ(*iter, 1u); 94 | ++iter; 95 | CHECK_EQ(*iter, 0u); 96 | ++iter; 97 | CHECK_EQ(*iter, 1u); 98 | ++iter; 99 | CHECK_EQ(iter, gen.end()); 100 | } 101 | 102 | { 103 | auto gen = f2(2); 104 | auto iter = gen.begin(); 105 | CHECK_EQ(*iter, 2u); 106 | ++iter; 107 | CHECK_EQ(*iter, 1u); 108 | ++iter; 109 | CHECK_EQ(*iter, 0u); 110 | ++iter; 111 | CHECK_EQ(*iter, 1u); 112 | ++iter; 113 | CHECK_EQ(*iter, 2u); 114 | ++iter; 115 | CHECK_EQ(iter, gen.end()); 116 | } 117 | } 118 | 119 | TEST_CASE("nested yield that yields nothing") { 120 | auto f = []() -> recursive_generator< std::uint32_t > { co_return; }; 121 | 122 | auto g = [&f]() -> recursive_generator< std::uint32_t > { 123 | co_yield 1; 124 | co_yield f(); 125 | co_yield 2; 126 | }; 127 | 128 | auto gen = g(); 129 | auto iter = gen.begin(); 130 | CHECK_EQ(*iter, 1u); 131 | ++iter; 132 | CHECK_EQ(*iter, 2u); 133 | ++iter; 134 | CHECK_EQ(iter, gen.end()); 135 | } 136 | 137 | } // namespace gap::test 138 | 139 | #endif // GAP_ENABLE_COROUTINES 140 | -------------------------------------------------------------------------------- /test/coro/shared_task.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-present, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/test/shared_task.cpp from 8 | // the cppcoro project. The original file is licenced under the MIT license and 9 | // the original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | 13 | #ifdef GAP_ENABLE_COROUTINES 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include "counted.hpp" 27 | 28 | using namespace gap::coro; 29 | 30 | namespace gap::test 31 | { 32 | TEST_SUITE_BEGIN("shared_task"); 33 | 34 | TEST_CASE("coroutine doesn't start executing until awaited") 35 | { 36 | bool startedExecuting = false; 37 | auto f = [&]() -> gap::coro::shared_task<> 38 | { 39 | startedExecuting = true; 40 | co_return; 41 | }; 42 | 43 | auto t = f(); 44 | 45 | CHECK(!t.is_ready()); 46 | CHECK(!startedExecuting); 47 | 48 | gap::coro::sync_wait([](gap::coro::shared_task<> o) -> gap::coro::task<> 49 | { 50 | co_await o; 51 | }(t)); 52 | 53 | CHECK(t.is_ready()); 54 | CHECK(startedExecuting); 55 | } 56 | 57 | TEST_CASE("result is destroyed when last reference is destroyed") 58 | { 59 | counted::reset_counts(); 60 | 61 | { 62 | auto t = []() -> gap::coro::shared_task 63 | { 64 | co_return counted{}; 65 | }(); 66 | 67 | CHECK(counted::active_count() == 0); 68 | 69 | gap::coro::sync_wait(t); 70 | 71 | CHECK(counted::active_count() == 1); 72 | } 73 | 74 | CHECK(counted::active_count() == 0); 75 | } 76 | 77 | TEST_CASE("multiple awaiters") 78 | { 79 | gap::coro::single_consumer_event event; 80 | bool startedExecution = false; 81 | auto produce = [&]() -> gap::coro::shared_task 82 | { 83 | startedExecution = true; 84 | co_await event; 85 | co_return 1; 86 | }; 87 | 88 | auto consume = [](gap::coro::shared_task t) -> gap::coro::task<> 89 | { 90 | int result = co_await t; 91 | CHECK(result == 1); 92 | }; 93 | 94 | auto sharedTask = produce(); 95 | 96 | gap::coro::sync_wait(gap::coro::when_all_ready( 97 | consume(sharedTask), 98 | consume(sharedTask), 99 | consume(sharedTask), 100 | [&]() -> gap::coro::task<> 101 | { 102 | event.set(); 103 | CHECK(sharedTask.is_ready()); 104 | co_return; 105 | }())); 106 | 107 | CHECK(sharedTask.is_ready()); 108 | } 109 | 110 | TEST_CASE("waiting on shared_task in loop doesn't cause stack-overflow") 111 | { 112 | // This test checks that awaiting a shared_task that completes 113 | // synchronously doesn't recursively resume the awaiter inside the 114 | // call to start executing the task. If it were to do this then we'd 115 | // expect that this test would result in failure due to stack-overflow. 116 | 117 | auto completesSynchronously = []() -> gap::coro::shared_task 118 | { 119 | co_return 1; 120 | }; 121 | 122 | gap::coro::sync_wait([&]() -> gap::coro::task<> 123 | { 124 | int result = 0; 125 | for (int i = 0; i < 1'000'000; ++i) 126 | { 127 | result += co_await completesSynchronously(); 128 | } 129 | CHECK(result == 1'000'000); 130 | }()); 131 | } 132 | 133 | TEST_CASE("make_shared_task") 134 | { 135 | bool startedExecution = false; 136 | 137 | auto f = [&]() -> gap::coro::task 138 | { 139 | startedExecution = false; 140 | co_return "test"; 141 | }; 142 | 143 | auto t = f(); 144 | 145 | gap::coro::shared_task sharedT = 146 | gap::coro::make_shared_task(std::move(t)); 147 | 148 | CHECK(!sharedT.is_ready()); 149 | CHECK(!startedExecution); 150 | 151 | auto consume = [](gap::coro::shared_task o) -> gap::coro::task<> 152 | { 153 | auto x = co_await std::move(o); 154 | CHECK(x == "test"); 155 | }; 156 | 157 | gap::coro::sync_wait(gap::coro::when_all_ready( 158 | consume(sharedT), 159 | consume(sharedT))); 160 | } 161 | 162 | TEST_CASE("make_shared_task of void" 163 | * doctest::description{ "Tests that workaround for 'co_return ' bug is operational if required" }) 164 | { 165 | bool startedExecution = false; 166 | 167 | auto f = [&]() -> gap::coro::task<> 168 | { 169 | startedExecution = true; 170 | co_return; 171 | }; 172 | 173 | auto t = f(); 174 | 175 | gap::coro::shared_task<> sharedT = gap::coro::make_shared_task(std::move(t)); 176 | 177 | CHECK(!sharedT.is_ready()); 178 | CHECK(!startedExecution); 179 | 180 | auto consume = [](gap::coro::shared_task<> o) -> gap::coro::task<> 181 | { 182 | co_await o; 183 | }; 184 | 185 | auto c1 = consume(sharedT); 186 | gap::coro::sync_wait(c1); 187 | 188 | CHECK(startedExecution); 189 | 190 | auto c2 = consume(sharedT); 191 | gap::coro::sync_wait(c2); 192 | 193 | CHECK(c1.is_ready()); 194 | CHECK(c2.is_ready()); 195 | } 196 | 197 | TEST_CASE("shared_task fmap operator") 198 | { 199 | gap::coro::single_consumer_event event; 200 | int value = 0; 201 | 202 | auto setNumber = [&]() -> gap::coro::shared_task<> 203 | { 204 | co_await event; 205 | value = 123; 206 | }; 207 | 208 | gap::coro::sync_wait(gap::coro::when_all_ready( 209 | [&]() -> gap::coro::task<> 210 | { 211 | auto numericStringTask = 212 | setNumber() 213 | | gap::coro::fmap([&]() { return std::to_string(value); }); 214 | 215 | auto result = co_await numericStringTask; 216 | CHECK(result == "123"); 217 | }(), 218 | [&]() -> gap::coro::task<> 219 | { 220 | CHECK(value == 0); 221 | event.set(); 222 | CHECK(value == 123); 223 | co_return; 224 | }())); 225 | } 226 | 227 | TEST_CASE("shared_task fmap operator") 228 | { 229 | gap::coro::single_consumer_event event; 230 | 231 | auto getNumber = [&]() -> gap::coro::shared_task 232 | { 233 | co_await event; 234 | co_return 123; 235 | }; 236 | 237 | gap::coro::sync_wait(gap::coro::when_all_ready( 238 | [&]() -> gap::coro::task<> 239 | { 240 | auto numericStringTask = 241 | getNumber() 242 | | gap::coro::fmap([](int x) { return std::to_string(x); }); 243 | auto result = co_await numericStringTask; 244 | CHECK(result == "123"); 245 | }(), 246 | [&]() -> gap::coro::task<> 247 | { 248 | event.set(); 249 | co_return; 250 | }())); 251 | } 252 | 253 | TEST_SUITE_END(); 254 | } // namespace gap::test 255 | 256 | #endif -------------------------------------------------------------------------------- /test/coro/sync_wait.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-present, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/test/sync_wait.cpp from 8 | // the cppcoro project. The original file is licenced under the MIT license and 9 | // the original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #ifdef GAP_ENABLE_COROUTINES 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | using namespace gap::coro; 27 | 28 | namespace gap::test 29 | { 30 | TEST_SUITE_BEGIN("sync_wait"); 31 | 32 | static_assert(std::is_same< 33 | decltype(gap::coro::sync_wait(std::declval>())), 34 | std::string&&>::value); 35 | static_assert(std::is_same< 36 | decltype(gap::coro::sync_wait(std::declval&>())), 37 | std::string&>::value); 38 | 39 | TEST_CASE("sync_wait(task)") 40 | { 41 | auto makeTask = []() -> gap::coro::task 42 | { 43 | co_return "foo"; 44 | }; 45 | 46 | auto task = makeTask(); 47 | CHECK(gap::coro::sync_wait(task) == "foo"); 48 | 49 | CHECK(gap::coro::sync_wait(makeTask()) == "foo"); 50 | } 51 | 52 | TEST_CASE("sync_wait(shared_task)") 53 | { 54 | auto makeTask = []() -> gap::coro::shared_task 55 | { 56 | co_return "foo"; 57 | }; 58 | 59 | auto task = makeTask(); 60 | 61 | CHECK(gap::coro::sync_wait(task) == "foo"); 62 | CHECK(gap::coro::sync_wait(makeTask()) == "foo"); 63 | } 64 | 65 | TEST_SUITE_END(); 66 | 67 | } // namespace gap::test 68 | #endif -------------------------------------------------------------------------------- /test/coro/when_all_ready.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-present, Trail of Bits, Inc. 2 | 3 | /////////////////////////////////////////////////////////////////////////////// 4 | // Copyright (c) Lewis Baker 5 | // Licenced under MIT license. See LICENSE.txt for details. 6 | /////////////////////////////////////////////////////////////////////////////// 7 | // This file is a modified version of cppcoro/test/when_all_ready.cpp from 8 | // the cppcoro project. The original file is licenced under the MIT license and 9 | // the original license is included above. 10 | /////////////////////////////////////////////////////////////////////////////// 11 | 12 | #ifdef GAP_ENABLE_COROUTINES 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | using namespace gap::coro; 22 | 23 | namespace gap::test 24 | { 25 | TEST_SUITE_BEGIN("when_all_ready"); 26 | 27 | template class TASK, typename T> 28 | TASK when_event_set_return(gap::coro::async_manual_reset_event& event, T value) 29 | { 30 | co_await event; 31 | co_return std::move(value); 32 | } 33 | 34 | TEST_CASE("when_all_ready() with no args") 35 | { 36 | [[maybe_unused]] std::tuple<> result = gap::coro::sync_wait(gap::coro::when_all_ready()); 37 | } 38 | 39 | TEST_CASE("when_all_ready() with one task") 40 | { 41 | bool started = false; 42 | auto f = [&](gap::coro::async_manual_reset_event& event) -> gap::coro::task<> 43 | { 44 | started = true; 45 | co_await event; 46 | }; 47 | 48 | gap::coro::async_manual_reset_event event; 49 | auto when_all_awaitable = gap::coro::when_all_ready(f(event)); 50 | CHECK(!started); 51 | 52 | bool finished = false; 53 | gap::coro::sync_wait(gap::coro::when_all_ready( 54 | [&]() -> gap::coro::task<> 55 | { 56 | auto&[t] = co_await when_all_awaitable; 57 | finished = true; 58 | t.result(); 59 | }(), 60 | [&]() -> gap::coro::task<> 61 | { 62 | CHECK(started); 63 | CHECK(!finished); 64 | event.set(); 65 | CHECK(finished); 66 | co_return; 67 | }())); 68 | } 69 | 70 | TEST_CASE("when_all_ready() with multiple task") 71 | { 72 | auto make_task = [&](bool& started, gap::coro::async_manual_reset_event& event, int result) -> gap::coro::task 73 | { 74 | started = true; 75 | co_await event; 76 | co_return result; 77 | }; 78 | 79 | gap::coro::async_manual_reset_event event1; 80 | gap::coro::async_manual_reset_event event2; 81 | bool started1 = false; 82 | bool started2 = false; 83 | auto when_all_awaitable = gap::coro::when_all_ready( 84 | make_task(started1, event1, 1), 85 | make_task(started2, event2, 2)); 86 | CHECK(!started1); 87 | CHECK(!started2); 88 | 89 | bool when_all_awaitable_finished = false; 90 | 91 | gap::coro::sync_wait(gap::coro::when_all_ready( 92 | [&]() -> gap::coro::task<> 93 | { 94 | auto[t1, t2] = co_await std::move(when_all_awaitable); 95 | when_all_awaitable_finished = true; 96 | CHECK(t1.result() == 1); 97 | CHECK(t2.result() == 2); 98 | }(), 99 | [&]() -> gap::coro::task<> 100 | { 101 | CHECK(started1); 102 | CHECK(started2); 103 | 104 | event2.set(); 105 | 106 | CHECK(!when_all_awaitable_finished); 107 | 108 | event1.set(); 109 | 110 | CHECK(when_all_awaitable_finished); 111 | 112 | co_return; 113 | }())); 114 | } 115 | 116 | TEST_CASE("when_all_ready() with all task types") 117 | { 118 | gap::coro::async_manual_reset_event event; 119 | auto t0 = when_event_set_return(event, 1); 120 | auto t1 = when_event_set_return(event, 2); 121 | 122 | auto all_tasks = gap::coro::when_all_ready(std::move(t0), t1); 123 | 124 | gap::coro::sync_wait(gap::coro::when_all_ready( 125 | [&]() -> gap::coro::task<> 126 | { 127 | auto [r0, r1] = co_await std::move(all_tasks); 128 | 129 | CHECK(r0.result() == 1); 130 | CHECK(r1.result() == 2); 131 | }(), 132 | [&]() -> gap::coro::task<> 133 | { 134 | event.set(); 135 | co_return; 136 | }())); 137 | } 138 | 139 | TEST_CASE("when_all_ready() with std::vector>") 140 | { 141 | gap::coro::async_manual_reset_event event; 142 | 143 | std::uint32_t started_count = 0; 144 | std::uint32_t finished_count = 0; 145 | 146 | auto make_task = [&]() -> gap::coro::task<> 147 | { 148 | ++started_count; 149 | co_await event; 150 | ++finished_count; 151 | }; 152 | 153 | std::vector> tasks; 154 | for (std::uint32_t i = 0; i < 10; ++i) 155 | { 156 | tasks.emplace_back(make_task()); 157 | } 158 | 159 | auto all_tasks = gap::coro::when_all_ready_vec(std::move(tasks)); 160 | 161 | // Shouldn't have started any tasks yet. 162 | CHECK(started_count == 0u); 163 | 164 | gap::coro::sync_wait(gap::coro::when_all_ready( 165 | [&]() -> gap::coro::task<> 166 | { 167 | auto result_tasks = co_await std::move(all_tasks); 168 | CHECK(result_tasks.size() == 10u); 169 | 170 | for (auto& t : result_tasks) 171 | { 172 | CHECK_NOTHROW(t.result()); 173 | } 174 | }(), 175 | [&]() -> gap::coro::task<> 176 | { 177 | CHECK(started_count == 10u); 178 | CHECK(finished_count == 0u); 179 | 180 | event.set(); 181 | 182 | CHECK(finished_count == 10u); 183 | 184 | co_return; 185 | }())); 186 | } 187 | 188 | TEST_CASE("when_all_ready() with std::vector>") 189 | { 190 | gap::coro::async_manual_reset_event event; 191 | 192 | std::uint32_t started_count = 0; 193 | std::uint32_t finished_count = 0; 194 | 195 | auto make_task = [&]() -> gap::coro::shared_task<> 196 | { 197 | ++started_count; 198 | co_await event; 199 | ++finished_count; 200 | }; 201 | 202 | std::vector> tasks; 203 | for (std::uint32_t i = 0; i < 10; ++i) 204 | { 205 | tasks.emplace_back(make_task()); 206 | } 207 | 208 | auto all_tasks = gap::coro::when_all_ready_vec(std::move(tasks)); 209 | 210 | // Shouldn't have started any tasks yet. 211 | CHECK(started_count == 0u); 212 | 213 | gap::coro::sync_wait(gap::coro::when_all_ready( 214 | [&]() -> gap::coro::task<> 215 | { 216 | auto resultTasks = co_await std::move(all_tasks); 217 | CHECK(resultTasks.size() == 10u); 218 | 219 | for (auto& t : resultTasks) 220 | { 221 | CHECK_NOTHROW(t.result()); 222 | } 223 | }(), 224 | [&]() -> gap::coro::task<> 225 | { 226 | CHECK(started_count == 10u); 227 | CHECK(finished_count == 0u); 228 | 229 | event.set(); 230 | 231 | CHECK(finished_count == 10u); 232 | 233 | co_return; 234 | }())); 235 | } 236 | 237 | TEST_CASE("when_all_ready() doesn't rethrow exceptions") 238 | { 239 | auto make_task = [](bool throwException) -> gap::coro::task 240 | { 241 | if (throwException) 242 | { 243 | throw std::exception{}; 244 | } 245 | else 246 | { 247 | co_return 123; 248 | } 249 | }; 250 | 251 | gap::coro::sync_wait([&]() -> gap::coro::task<> 252 | { 253 | try 254 | { 255 | auto[t0, t1] = co_await gap::coro::when_all_ready(make_task(true), make_task(false)); 256 | 257 | // You can obtain the exceptions by re-awaiting the returned tasks. 258 | CHECK_THROWS_AS(t0.result(), const std::exception&); 259 | CHECK(t1.result() == 123); 260 | } 261 | catch (...) 262 | { 263 | FAIL("Shouldn't throw"); 264 | } 265 | }()); 266 | } 267 | 268 | TEST_SUITE_END(); 269 | 270 | } // namespace gap::test 271 | 272 | #endif // GAP_ENABLE_COROUTINES 273 | -------------------------------------------------------------------------------- /test/graph/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2024, Trail of Bits, Inc. All rights reserved. 2 | 3 | add_gap_test(test-gap-graph 4 | graph.cpp 5 | ) 6 | 7 | target_link_libraries(test-gap-graph 8 | PUBLIC 9 | gap-graph 10 | ) -------------------------------------------------------------------------------- /test/graph/graph.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022-present, Trail of Bits, Inc. 2 | 3 | #ifdef GAP_ENABLE_COROUTINES 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace gap::test 12 | { 13 | namespace concepts 14 | { 15 | struct node_t { 16 | using node_pointer = std::shared_ptr< node_t >; 17 | using child_type = node_pointer; 18 | 19 | generator< child_type > children() const; 20 | }; 21 | 22 | static_assert(graph::node_like< node_t >); 23 | 24 | struct broken_node { 25 | generator< int > children(); 26 | }; 27 | 28 | static_assert(not graph::node_like< broken_node >); 29 | 30 | struct edge_t { 31 | using node_type = node_t; 32 | using node_pointer = node_type::node_pointer; 33 | 34 | using source_type = node_pointer; 35 | using target_type = node_pointer; 36 | 37 | source_type source() const { return src; } 38 | target_type target() const { return dst; } 39 | 40 | private: 41 | node_pointer src, dst; 42 | }; 43 | 44 | static_assert(graph::edge_like< edge_t >); 45 | 46 | struct graph_t { 47 | using node_type = node_t; 48 | using edge_type = edge_t; 49 | using node_pointer = node_type::node_pointer; 50 | 51 | generator< node_pointer > nodes() const; 52 | generator< edge_type > edges() const; 53 | }; 54 | 55 | static_assert(graph::graph_like< graph_t >); 56 | } // namespace concepts 57 | 58 | // 59 | // test graph 60 | // 61 | struct node_t { 62 | using node_pointer = std::shared_ptr< node_t >; 63 | using child_type = node_pointer; 64 | 65 | explicit node_t(char val) 66 | : value(val) {} 67 | 68 | generator< child_type > children() const { 69 | for (auto ch : _children) 70 | co_yield ch; 71 | } 72 | 73 | char value; 74 | 75 | std::vector< child_type > _children; 76 | }; 77 | 78 | static_assert(graph::node_like< node_t >); 79 | 80 | template< graph::node_like node_t_ > 81 | struct edge_t { 82 | using node_type = node_t_; 83 | using node_pointer = typename node_type::node_pointer; 84 | 85 | using source_type = node_pointer; 86 | using target_type = node_pointer; 87 | 88 | source_type source() const { return _src; } 89 | target_type target() const { return _dst; } 90 | 91 | node_pointer _src, _dst; 92 | }; 93 | 94 | static_assert(graph::edge_like< edge_t< node_t > >); 95 | 96 | struct graph_t { 97 | using node_type = node_t; 98 | using edge_type = edge_t< node_t >; 99 | 100 | using node_pointer = node_type::node_pointer; 101 | 102 | generator< node_pointer > nodes() const { 103 | for (auto ch : _nodes) 104 | co_yield ch; 105 | } 106 | 107 | generator< edge_type > edges() const { 108 | for (auto node : _nodes) { 109 | for (auto child : node->children()) { 110 | co_yield edge_type{ node, child }; 111 | } 112 | } 113 | } 114 | 115 | std::vector< node_pointer > _nodes; 116 | }; 117 | 118 | static_assert(graph::graph_like< graph_t >); 119 | 120 | using node_ptr = node_t::node_pointer; 121 | 122 | TEST_CASE("DFS") { 123 | graph_t g{ 124 | {std::make_shared< node_t >('A'), 125 | std::make_shared< node_t >('B'), 126 | std::make_shared< node_t >('C'), 127 | std::make_shared< node_t >('D'), 128 | std::make_shared< node_t >('E')} 129 | }; 130 | 131 | auto &nodes = g._nodes; 132 | 133 | nodes[0]->_children = { nodes[1], nodes[2] }; 134 | nodes[1]->_children = { nodes[3] }; 135 | nodes[2]->_children = { nodes[3] }; 136 | nodes[3]->_children = { nodes[0] }; 137 | 138 | constexpr auto yield_on_close = graph::yield_node::on_close; 139 | 140 | SUBCASE("DFS from single root") { 141 | std::vector topo = { 'D', 'B', 'C', 'A' }; 142 | auto expected = std::begin(topo); 143 | for (auto n : graph::dfs< yield_on_close >(nodes[0])) { 144 | CHECK(n->value == *(expected++)); 145 | } 146 | } 147 | 148 | SUBCASE("DFS over full graph") { 149 | std::vector topo = { 'D', 'B', 'C', 'A', 'E' }; 150 | auto expected = std::begin(topo); 151 | for (auto n : graph::dfs< yield_on_close >(g)) { 152 | CHECK(n->value == *(expected++)); 153 | } 154 | } 155 | 156 | SUBCASE("toposort from single root") { 157 | std::vector topo = { 'D', 'B', 'C', 'A' }; 158 | auto expected = std::begin(topo); 159 | for (auto n : graph::toposort(nodes[0])) { 160 | CHECK(n->value == *(expected++)); 161 | } 162 | } 163 | } 164 | 165 | TEST_CASE("DFS bigger") { 166 | graph_t g{ 167 | {std::make_shared< node_t >('A'), 168 | std::make_shared< node_t >('B'), 169 | std::make_shared< node_t >('C'), 170 | std::make_shared< node_t >('D'), 171 | std::make_shared< node_t >('E'), 172 | std::make_shared< node_t >('F'), 173 | std::make_shared< node_t >('G'), 174 | std::make_shared< node_t >('H'), 175 | } 176 | }; 177 | 178 | auto &nodes = g._nodes; 179 | 180 | nodes[0]->_children = { nodes[1], nodes[2] }; 181 | nodes[1]->_children = { nodes[3] }; 182 | nodes[2]->_children = { nodes[3], nodes[4] }; 183 | nodes[3]->_children = { nodes[5] }; 184 | nodes[4]->_children = { nodes[5], nodes[7] }; 185 | nodes[5]->_children = { nodes[6], nodes[7] }; 186 | 187 | constexpr auto yield_on_close = graph::yield_node::on_close; 188 | 189 | SUBCASE("DFS over full graph") { 190 | std::vector topo = { 'G', 'H', 'F', 'D', 'B', 'E', 'C', 'A' }; 191 | auto expected = std::begin(topo); 192 | for (auto n : graph::dfs< yield_on_close >(g)) { 193 | CHECK(n->value == *(expected++)); 194 | } 195 | } 196 | } 197 | 198 | } // namespace gap::test 199 | 200 | #endif // GAP_ENABLE_COROUTINES 201 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021-present, Trail of Bits, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed in accordance with the terms specified in 6 | * the LICENSE file found in the root directory of this source tree. 7 | */ 8 | 9 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 10 | #include 11 | -------------------------------------------------------------------------------- /test/mlir/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_gap_test(test-gap-mlir 2 | functors.cpp 3 | views.cpp 4 | ) 5 | 6 | target_link_libraries(test-gap-mlir 7 | PRIVATE 8 | gap-mlir 9 | MLIRFuncDialect 10 | ) -------------------------------------------------------------------------------- /test/mlir/functors.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace gap::test 12 | { 13 | using namespace gap::mlir; 14 | 15 | TEST_SUITE("functors") { 16 | 17 | auto mctx = std::make_unique< context >(); 18 | 19 | auto i32 = ::mlir::IntegerType::get(mctx.get(), 32); 20 | 21 | TEST_CASE("isa") { 22 | CHECK(gap::mlir::isa< ::mlir::IntegerType >(i32)); 23 | } 24 | 25 | TEST_CASE("cast") { 26 | CHECK(gap::mlir::cast< ::mlir::IntegerType >(i32)); 27 | } 28 | 29 | TEST_CASE("dyn_cast") { 30 | CHECK(gap::mlir::dyn_cast< ::mlir::IntegerType >(i32)); 31 | CHECK(!gap::mlir::dyn_cast< ::mlir::FloatType >(i32)); 32 | } 33 | 34 | } // TEST_SUITE("functors") 35 | 36 | } // namespace gap::mlir::test 37 | -------------------------------------------------------------------------------- /test/mlir/mlir_context_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace gap::test 6 | { 7 | struct MLIRContextTestFixture 8 | { 9 | MLIRContextTestFixture() 10 | : mctx(std::make_unique< gap::mlir::context >()) 11 | {} 12 | 13 | explicit MLIRContextTestFixture(std::unique_ptr< gap::mlir::context > &&mctx) 14 | : mctx(std::move(mctx)) 15 | {} 16 | 17 | std::unique_ptr< gap::mlir::context > mctx; 18 | }; 19 | 20 | } // namespace gap::test -------------------------------------------------------------------------------- /test/mlir/mlir_module_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "mlir_context_fixture.hpp" 6 | 7 | namespace gap::test 8 | { 9 | struct MLIRModuleTestFixture : MLIRContextTestFixture 10 | { 11 | using base = MLIRContextTestFixture; 12 | 13 | MLIRModuleTestFixture() 14 | : base(std::make_unique< gap::mlir::context >()) 15 | , bld(mctx.get()) 16 | , mod(bld.create< gap::mlir::module >(bld.getUnknownLoc())) 17 | {} 18 | 19 | gap::mlir::op_builder bld; 20 | gap::mlir::owning_module_ref mod; 21 | }; 22 | 23 | } // namespace gap::test -------------------------------------------------------------------------------- /test/mlir/views.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, Trail of Bits, Inc. 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "mlir_module_fixture.hpp" 13 | 14 | namespace gap::test 15 | { 16 | struct MLIRTestFixture : MLIRModuleTestFixture 17 | { 18 | using base = MLIRModuleTestFixture; 19 | 20 | MLIRTestFixture() : base() { 21 | ::mlir::DialectRegistry registry; 22 | registry.insert< ::mlir::func::FuncDialect >(); 23 | mctx->appendDialectRegistry(registry); 24 | 25 | mctx->loadAllAvailableDialects(); 26 | 27 | auto uloc = bld.getUnknownLoc(); 28 | auto fty = bld.getFunctionType({}, {}); 29 | 30 | bld.setInsertionPointToStart(mod->getBody()); 31 | fn = bld.create< ::mlir::func::FuncOp >(uloc, "test", fty); 32 | 33 | auto mk_block = [&] (auto ®ion) { 34 | auto& blk = region.emplaceBlock(); 35 | bld.setInsertionPointToEnd(&blk); 36 | bld.create< ::mlir::func::ReturnOp >(uloc); 37 | }; 38 | 39 | mk_block(fn.getBody()); 40 | mk_block(fn.getBody()); 41 | mk_block(fn.getBody()); 42 | } 43 | 44 | ::mlir::func::FuncOp fn; 45 | }; 46 | 47 | using namespace gap::mlir::views; 48 | 49 | TEST_SUITE("views") { 50 | 51 | TEST_CASE_FIXTURE(MLIRTestFixture, "operation views") { 52 | CHECK(std::ranges::distance(regions(fn)) == 1); 53 | CHECK(std::ranges::distance(blocks(fn)) == 3); 54 | CHECK(std::ranges::distance(operations(fn)) == 3); 55 | 56 | CHECK(std::ranges::distance(regions(mod->getOperation())) == 1); 57 | CHECK(std::ranges::distance(blocks(mod->getOperation())) == 1); 58 | CHECK(std::ranges::distance(operations(mod->getOperation())) == 1); 59 | } 60 | 61 | TEST_CASE_FIXTURE(MLIRTestFixture, "cast views") { 62 | auto rets = operations(fn) | isa< ::mlir::func::ReturnOp >; 63 | CHECK(std::ranges::distance(rets) == 3); 64 | 65 | auto rets_casted = operations(fn) | filter_cast< ::mlir::func::ReturnOp >; 66 | CHECK(std::ranges::distance(rets_casted) == 3); 67 | 68 | auto fns = operations(mod->getOperation()) | isa< ::mlir::func::FuncOp >; 69 | CHECK(std::ranges::distance(fns) == 1); 70 | } 71 | 72 | } // TEST_SUITE("views") 73 | 74 | } // namespace gap::test 75 | -------------------------------------------------------------------------------- /test/sarif/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2024, Trail of Bits, Inc. All rights reserved. 2 | 3 | if (GAP_ENABLE_SARIF) 4 | add_gap_test(test-gap-sarif 5 | sarif.cpp 6 | ) 7 | 8 | target_link_libraries(test-gap-sarif 9 | PUBLIC 10 | gap-sarif 11 | ) 12 | endif() -------------------------------------------------------------------------------- /test/sarif/sarif.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024-present, Trail of Bits, Inc. 2 | 3 | #include 4 | #include 5 | 6 | namespace gap::sarif { 7 | TEST_CASE("Struct to JSON roundtrip") { 8 | // Sample SARIF output from https://github.com/microsoft/sarif-tutorials/blob/main/docs/1-Introduction.md 9 | root obj{ 10 | .version = version::k2_1_0, 11 | .runs = { 12 | { 13 | .tool = { 14 | .driver = { 15 | .name = "ESLint", 16 | .informationUri = "https://eslint.org", 17 | .rules = { 18 | { 19 | .id = "no-unused-vars", 20 | .shortDescription = {{ 21 | .text = "disallow unused variables", 22 | }}, 23 | .helpUri = "https://eslint.org/docs/rules/no-unused-vars", 24 | .properties = {{ 25 | .additional_properties = { 26 | {"category", "Variable"}, 27 | }, 28 | }}, 29 | } 30 | }, 31 | }, 32 | }, 33 | .artifacts = { 34 | { 35 | .location = {{ 36 | .uri = "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js", 37 | }}, 38 | }, 39 | }, 40 | .results = {{ 41 | { 42 | .ruleId = "no-unused-vars", 43 | .ruleIndex = 0, 44 | .level = level::kError, 45 | .message = { 46 | .text = "'x' is assigned a value but never used.", 47 | }, 48 | .locations = {{ 49 | { 50 | .physicalLocation = {{ 51 | .artifactLocation = {{ 52 | .uri = "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js", 53 | .index = 0, 54 | }}, 55 | }}, 56 | .properties = {{ 57 | .additional_properties = { 58 | {"region", { 59 | {"startLine", 1}, 60 | {"startColumn", 5}, 61 | }}, 62 | }, 63 | }}, 64 | }, 65 | }}, 66 | }, 67 | }}, 68 | } 69 | }, 70 | }; 71 | 72 | json obj_json = obj; 73 | root deser = obj; 74 | json deser_json = deser; 75 | 76 | CHECK_EQ(obj_json, deser_json); 77 | } 78 | 79 | TEST_CASE("JSON to struct roundtrip") { 80 | auto j = json::parse(R"({ 81 | "version": "2.1.0", 82 | "runs": [ 83 | { 84 | "tool": { 85 | "driver": { 86 | "name": "ESLint", 87 | "informationUri": "https://eslint.org", 88 | "rules": [ 89 | { 90 | "id": "no-unused-vars", 91 | "shortDescription": { 92 | "text": "disallow unused variables" 93 | }, 94 | "helpUri": "https://eslint.org/docs/rules/no-unused-vars", 95 | "properties": { 96 | "category": "Variables" 97 | } 98 | } 99 | ] 100 | } 101 | }, 102 | "artifacts": [ 103 | { 104 | "location": { 105 | "uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js" 106 | } 107 | } 108 | ], 109 | "results": [ 110 | { 111 | "level": "error", 112 | "message": { 113 | "text": "'x' is assigned a value but never used." 114 | }, 115 | "locations": [ 116 | { 117 | "physicalLocation": { 118 | "artifactLocation": { 119 | "uri": "file:///C:/dev/sarif/sarif-tutorials/samples/Introduction/simple-example.js", 120 | "index": 0 121 | }, 122 | "region": { 123 | "startLine": 1, 124 | "startColumn": 5 125 | } 126 | } 127 | } 128 | ], 129 | "ruleId": "no-unused-vars", 130 | "ruleIndex": 0 131 | } 132 | ] 133 | } 134 | ] 135 | })"); 136 | 137 | root root = j; 138 | 139 | json j2 = root; 140 | 141 | CHECK_EQ(j, j2); 142 | } 143 | } -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "version-string": "latest", 4 | "dependencies": [ 5 | "spdlog" 6 | ], 7 | "features": { 8 | "with-doctest": { 9 | "description": "Use doctest package", 10 | "dependencies": [ 11 | "doctest" 12 | ] 13 | }, 14 | "sarif": { 15 | "description": "Enable SARIF support", 16 | "dependencies": [ 17 | "nlohmann-json" 18 | ] 19 | } 20 | } 21 | } 22 | --------------------------------------------------------------------------------