From a5e436ba2c1a5f678616178c7ed6623748e9f85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Tue, 8 Sep 2026 11:52:08 -0700 Subject: [PATCH 01/18] Reduce Unix CI matrix and improve compiler caching --- .github/workflows/continuous.yaml | 40 ++++++++++++++++++------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index bfa9289e..cffe9382 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -24,21 +24,19 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-24.04, macos-15-intel, macos-15] - config: [RelWithDebInfo, Debug] - compiler: [gcc, apple, llvm] - sanitizer: ["Address", "Thread"] # TODO: Add Memory+Undefined Sanitizer - exclude: - - os: macos-15-intel - compiler: gcc - - os: macos-15-intel - sanitizer: Thread - - os: macos-15-intel - config: Debug - - os: macos-15 - compiler: gcc - - os: ubuntu-24.04 - compiler: apple + include: + # Linux release builds exercise tests disabled in Debug mode, and ASan also enables LSan. + - { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: Address } + - { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: Thread } + - { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: Address } + - { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: Thread } + + # Keep AppleClang release coverage on Intel and full sanitizer coverage on Apple Silicon. + - { os: macos-15-intel, compiler: apple, config: RelWithDebInfo, sanitizer: Address } + - { os: macos-15, compiler: apple, config: RelWithDebInfo, sanitizer: Address } + - { os: macos-15, compiler: apple, config: RelWithDebInfo, sanitizer: Thread } + - { os: macos-15, compiler: llvm, config: Debug, sanitizer: Address } + - { os: macos-15, compiler: llvm, config: Debug, sanitizer: Thread } steps: - name: Free Disk Space if: runner.os == 'Linux' @@ -133,7 +131,9 @@ jobs: - name: Ccache uses: hendrikmuhs/ccache-action@v1.2 with: - key: ${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.sanitizer }}-${{ matrix.config }} + key: ${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.config }}-${{ matrix.sanitizer }} + max-size: 500M + verbose: 1 - name: Setup Xcode version if: matrix.os == 'macos-15-intel' @@ -190,6 +190,12 @@ jobs: - name: Tests run: cd ${{ env.build_dir }}; ctest --verbose -j ${{ steps.cpu-cores.outputs.count }} + - name: Clean and report ccache + if: always() + run: | + ccache --cleanup + ccache --show-stats --verbose + #################### # Windows #################### @@ -250,7 +256,7 @@ jobs: id: cpu-cores - name: Sccache - uses: mozilla-actions/sccache-action@v0.0.10 + uses: mozilla-actions/sccache-action@v0.0.11 - name: Set x64 vars if: matrix.os == 'windows-2025' From 9345831c1d8046d49b0aa974b8fdb5c0ba787a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Tue, 8 Sep 2026 21:45:31 -0700 Subject: [PATCH 02/18] Restore UBSan matrix before resource-pressure fixes --- .github/workflows/continuous.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index cffe9382..6a41ff02 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -26,16 +26,16 @@ jobs: matrix: include: # Linux release builds exercise tests disabled in Debug mode, and ASan also enables LSan. - - { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: Address } + - { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: "Address;Undefined" } - { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: Thread } - - { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: Address } + - { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: "Address;Undefined" } - { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: Thread } # Keep AppleClang release coverage on Intel and full sanitizer coverage on Apple Silicon. - - { os: macos-15-intel, compiler: apple, config: RelWithDebInfo, sanitizer: Address } - - { os: macos-15, compiler: apple, config: RelWithDebInfo, sanitizer: Address } + - { os: macos-15-intel, compiler: apple, config: RelWithDebInfo, sanitizer: "Address;Undefined" } + - { os: macos-15, compiler: apple, config: RelWithDebInfo, sanitizer: "Address;Undefined" } - { os: macos-15, compiler: apple, config: RelWithDebInfo, sanitizer: Thread } - - { os: macos-15, compiler: llvm, config: Debug, sanitizer: Address } + - { os: macos-15, compiler: llvm, config: Debug, sanitizer: "Address;Undefined" } - { os: macos-15, compiler: llvm, config: Debug, sanitizer: Thread } steps: - name: Free Disk Space @@ -133,6 +133,7 @@ jobs: with: key: ${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.config }}-${{ matrix.sanitizer }} max-size: 500M + evict-old-files: job verbose: 1 - name: Setup Xcode version From 9a5492007c8c446ba0728618b77b5785e52af000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Tue, 8 Sep 2026 22:29:16 -0700 Subject: [PATCH 03/18] Limit Embree ISA variants in macOS CI --- .github/workflows/continuous.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 6a41ff02..59bc18f3 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -154,11 +154,11 @@ jobs: - name: Select embree isa (macOS x64) if: runner.os == 'macOS' && runner.arch == 'X64' - run: echo "embree_max_isa=DEFAULT" >> $GITHUB_ENV + run: echo "embree_max_isa=SSE2" >> $GITHUB_ENV - name: Select embree isa (macOS arm64) if: runner.os == 'macOS' && runner.arch == 'ARM64' - run: echo "embree_max_isa=NONE" >> $GITHUB_ENV + run: echo "embree_max_isa=NEON" >> $GITHUB_ENV - name: Get number of CPU cores uses: SimenB/github-actions-cpu-cores@v1 From 01495e71c0d88c567cac036663ab22e8655f284d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Wed, 9 Sep 2026 07:22:48 -0700 Subject: [PATCH 04/18] Reduce sanitizer build pressure on Linux --- .github/workflows/continuous.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 59bc18f3..73633cdb 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -171,6 +171,7 @@ jobs: -DCMAKE_BUILD_TYPE=${{ matrix.config }} \ -DLAGRANGE_JENKINS=ON \ -DLAGRANGE_ALL=ON \ + -DLAGRANGE_EXAMPLES=OFF \ -DLAGRANGE_LIMIT_PARALLELISM=ON \ -DOPENVDB_CORE_SHARED=ON \ -DOPENVDB_CORE_STATIC=OFF \ @@ -179,6 +180,12 @@ jobs: -DEMBREE_MAX_ISA=${{ env.embree_max_isa }} \ -DUSE_SANITIZER="${{ matrix.sanitizer }}" + # SSE2 is Embree's lowest x86 ISA, but its large translation units still exhaust + # the Linux runner when four Clang ASan+UBSan compilations run concurrently. + - name: Build Embree with limited parallelism + if: runner.os == 'Linux' && matrix.compiler == 'llvm' && matrix.sanitizer == 'Address;Undefined' + run: cmake --build ${{ env.build_dir }} --target embree -j 2 + - name: Build run: cmake --build ${{ env.build_dir }} -j ${{ steps.cpu-cores.outputs.count }} From 1f3d2972d8dea38fc13003076b404e34db00a1af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Wed, 9 Sep 2026 07:36:14 -0700 Subject: [PATCH 05/18] Limit Embree compile concurrency on Linux --- .github/workflows/continuous.yaml | 7 ------- cmake/lagrange/lagrange_limit_parallelism.cmake | 2 +- cmake/recipes/external/embree.cmake | 6 ++++++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 73633cdb..59bc18f3 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -171,7 +171,6 @@ jobs: -DCMAKE_BUILD_TYPE=${{ matrix.config }} \ -DLAGRANGE_JENKINS=ON \ -DLAGRANGE_ALL=ON \ - -DLAGRANGE_EXAMPLES=OFF \ -DLAGRANGE_LIMIT_PARALLELISM=ON \ -DOPENVDB_CORE_SHARED=ON \ -DOPENVDB_CORE_STATIC=OFF \ @@ -180,12 +179,6 @@ jobs: -DEMBREE_MAX_ISA=${{ env.embree_max_isa }} \ -DUSE_SANITIZER="${{ matrix.sanitizer }}" - # SSE2 is Embree's lowest x86 ISA, but its large translation units still exhaust - # the Linux runner when four Clang ASan+UBSan compilations run concurrently. - - name: Build Embree with limited parallelism - if: runner.os == 'Linux' && matrix.compiler == 'llvm' && matrix.sanitizer == 'Address;Undefined' - run: cmake --build ${{ env.build_dir }} --target embree -j 2 - - name: Build run: cmake --build ${{ env.build_dir }} -j ${{ steps.cpu-cores.outputs.count }} diff --git a/cmake/lagrange/lagrange_limit_parallelism.cmake b/cmake/lagrange/lagrange_limit_parallelism.cmake index 8dd9d046..1f67ed19 100644 --- a/cmake/lagrange/lagrange_limit_parallelism.cmake +++ b/cmake/lagrange/lagrange_limit_parallelism.cmake @@ -53,7 +53,7 @@ function(lagrange_limit_parallelism) message(STATUS "Parallelism: Link job memory budget: ${_link_memory} MB (${_build_type})") message(STATUS "Parallelism: Limiting link pool to ${num_link_jobs}") - set_property(GLOBAL PROPERTY JOB_POOLS pool-link=${num_link_jobs}) + set_property(GLOBAL PROPERTY JOB_POOLS pool-link=${num_link_jobs} pool-heavy-compile=1) set(CMAKE_JOB_POOL_LINK "pool-link" CACHE STRING "Job pool for linking" FORCE) endif() endfunction() diff --git a/cmake/recipes/external/embree.cmake b/cmake/recipes/external/embree.cmake index a323f51c..10adf902 100644 --- a/cmake/recipes/external/embree.cmake +++ b/cmake/recipes/external/embree.cmake @@ -129,6 +129,12 @@ function(embree_import_target) unignore_package(TBB) + # Embree has several memory-intensive translation units. Limit their concurrency on + # Linux when the project's memory-aware parallelism policy is enabled. + if(LAGRANGE_LIMIT_PARALLELISM AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_GENERATOR MATCHES "^Ninja") + set_property(TARGET embree PROPERTY JOB_POOL_COMPILE pool-heavy-compile) + endif() + # Disable warnings if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") # Embree's subgrid.h is known for causing array subscript out of bound From 2f184af6eaa9747978567d26416872e62f8e92b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Wed, 9 Sep 2026 07:40:43 -0700 Subject: [PATCH 06/18] Preserve compiler cache entries across CI runs --- .github/workflows/continuous.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 59bc18f3..30dcce20 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -132,8 +132,10 @@ jobs: uses: hendrikmuhs/ccache-action@v1.2 with: key: ${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.config }}-${{ matrix.sanitizer }} - max-size: 500M - evict-old-files: job + # A 500 MB cache retains only about half of this build's cacheable objects. + # Do not evict entries unused by the current job: cancelled jobs would save + # that partially populated cache as the newest restore candidate. + max-size: 750M verbose: 1 - name: Setup Xcode version From e71436226a8f760555b491a6b43d2e70034e5a47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Wed, 9 Sep 2026 08:00:15 -0700 Subject: [PATCH 07/18] Clarify compiler cache retention --- .github/workflows/continuous.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 30dcce20..aa4ff461 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -132,9 +132,8 @@ jobs: uses: hendrikmuhs/ccache-action@v1.2 with: key: ${{ matrix.os }}-${{ matrix.compiler }}-${{ matrix.config }}-${{ matrix.sanitizer }} - # A 500 MB cache retains only about half of this build's cacheable objects. - # Do not evict entries unused by the current job: cancelled jobs would save - # that partially populated cache as the newest restore candidate. + # Keep more objects, and do not prune untouched entries because cancelled jobs + # would save an incomplete cache as the newest restore candidate. max-size: 750M verbose: 1 From 096ebbc526d32a3178e4e1d2fbec830d0ee6fa2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Wed, 9 Sep 2026 08:07:09 -0700 Subject: [PATCH 08/18] Warm compiler caches from main --- .github/workflows/continuous.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index aa4ff461..4f86e4c1 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -4,6 +4,9 @@ on: pull_request: branches: - main + push: + branches: + - main concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -21,6 +24,8 @@ jobs: Unix: name: ${{ matrix.os }} (${{ matrix.compiler }}, ${{ matrix.config }}, ${{ matrix.sanitizer }}Sanitizer) runs-on: ${{ matrix.os }} + env: + CCACHE_COMPRESSLEVEL: 5 strategy: fail-fast: false matrix: From 077d438496fd05ac1e1bb75e82b0169a7fc423c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Wed, 9 Sep 2026 08:09:15 -0700 Subject: [PATCH 09/18] Increase sccache compression level --- .github/workflows/continuous.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 4f86e4c1..5d14c93d 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -212,6 +212,7 @@ jobs: runs-on: ${{ matrix.os }} env: SCCACHE_GHA_ENABLED: "true" + SCCACHE_CACHE_ZSTD_LEVEL: "5" strategy: fail-fast: false matrix: From cf9a03d8eb8eda8ec25ab80f3f77b8e2103f9088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Wed, 9 Sep 2026 11:03:41 -0700 Subject: [PATCH 10/18] Prebuild Embree with bounded parallelism --- .github/workflows/continuous.yaml | 4 ++++ cmake/lagrange/lagrange_limit_parallelism.cmake | 2 +- cmake/recipes/external/embree.cmake | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 5d14c93d..eae69793 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -185,6 +185,10 @@ jobs: -DEMBREE_MAX_ISA=${{ env.embree_max_isa }} \ -DUSE_SANITIZER="${{ matrix.sanitizer }}" + - name: Build Embree + if: runner.os == 'Linux' + run: cmake --build ${{ env.build_dir }} --target embree -j 2 + - name: Build run: cmake --build ${{ env.build_dir }} -j ${{ steps.cpu-cores.outputs.count }} diff --git a/cmake/lagrange/lagrange_limit_parallelism.cmake b/cmake/lagrange/lagrange_limit_parallelism.cmake index 1f67ed19..ac00e45b 100644 --- a/cmake/lagrange/lagrange_limit_parallelism.cmake +++ b/cmake/lagrange/lagrange_limit_parallelism.cmake @@ -53,7 +53,7 @@ function(lagrange_limit_parallelism) message(STATUS "Parallelism: Link job memory budget: ${_link_memory} MB (${_build_type})") message(STATUS "Parallelism: Limiting link pool to ${num_link_jobs}") - set_property(GLOBAL PROPERTY JOB_POOLS pool-link=${num_link_jobs} pool-heavy-compile=1) + set_property(GLOBAL PROPERTY JOB_POOLS pool-link=${num_link_jobs} pool-heavy-compile=2) set(CMAKE_JOB_POOL_LINK "pool-link" CACHE STRING "Job pool for linking" FORCE) endif() endfunction() diff --git a/cmake/recipes/external/embree.cmake b/cmake/recipes/external/embree.cmake index 10adf902..52b31c8a 100644 --- a/cmake/recipes/external/embree.cmake +++ b/cmake/recipes/external/embree.cmake @@ -133,6 +133,7 @@ function(embree_import_target) # Linux when the project's memory-aware parallelism policy is enabled. if(LAGRANGE_LIMIT_PARALLELISM AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_GENERATOR MATCHES "^Ninja") set_property(TARGET embree PROPERTY JOB_POOL_COMPILE pool-heavy-compile) + set_property(TARGET embree PROPERTY JOB_POOL_LINK pool-link) endif() # Disable warnings From d2adc86a1d25d9093acc13af7c4bee9caca2acf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Thu, 10 Sep 2026 10:16:28 -0700 Subject: [PATCH 11/18] Serialize LLVM sanitizer Embree compilation --- cmake/lagrange/lagrange_limit_parallelism.cmake | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/cmake/lagrange/lagrange_limit_parallelism.cmake b/cmake/lagrange/lagrange_limit_parallelism.cmake index ac00e45b..5f543e61 100644 --- a/cmake/lagrange/lagrange_limit_parallelism.cmake +++ b/cmake/lagrange/lagrange_limit_parallelism.cmake @@ -44,6 +44,14 @@ function(lagrange_limit_parallelism) set(num_link_jobs 1) endif() + set(num_heavy_compile_jobs 2) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" + AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" + AND USE_SANITIZER MATCHES "([Aa]ddress)" + AND USE_SANITIZER MATCHES "([Uu]ndefined)") + set(num_heavy_compile_jobs 1) + endif() + if(CMAKE_SCRIPT_MODE_FILE) # Script mode: echo the number of physical cores for use as the -j flag in Jenkins. # Link parallelism is handled separately via Ninja job pools at configure time. @@ -52,8 +60,12 @@ function(lagrange_limit_parallelism) message(STATUS "Parallelism: Total physical memory: ${TOTAL_PHYSICAL_MEMORY} MB") message(STATUS "Parallelism: Link job memory budget: ${_link_memory} MB (${_build_type})") message(STATUS "Parallelism: Limiting link pool to ${num_link_jobs}") + message(STATUS "Parallelism: Limiting heavy compile pool to ${num_heavy_compile_jobs}") - set_property(GLOBAL PROPERTY JOB_POOLS pool-link=${num_link_jobs} pool-heavy-compile=2) + set_property(GLOBAL PROPERTY JOB_POOLS + pool-link=${num_link_jobs} + pool-heavy-compile=${num_heavy_compile_jobs} + ) set(CMAKE_JOB_POOL_LINK "pool-link" CACHE STRING "Job pool for linking" FORCE) endif() endfunction() From 5965a38c21b3f659d8d5170a10d304b4d76dce9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Thu, 10 Sep 2026 10:44:36 -0700 Subject: [PATCH 12/18] Build OpenVDB explicit instantiations in heavy pool --- .github/workflows/continuous.yaml | 6 +++++- cmake/recipes/external/OpenVDB.cmake | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index eae69793..70a5af09 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -180,7 +180,7 @@ jobs: -DLAGRANGE_LIMIT_PARALLELISM=ON \ -DOPENVDB_CORE_SHARED=ON \ -DOPENVDB_CORE_STATIC=OFF \ - -DUSE_EXPLICIT_INSTANTIATION=OFF \ + -DUSE_EXPLICIT_INSTANTIATION=ON \ -DLAGRANGE_POLYSCOPE_MOCK_BACKEND=ON \ -DEMBREE_MAX_ISA=${{ env.embree_max_isa }} \ -DUSE_SANITIZER="${{ matrix.sanitizer }}" @@ -189,6 +189,10 @@ jobs: if: runner.os == 'Linux' run: cmake --build ${{ env.build_dir }} --target embree -j 2 + - name: Build OpenVDB + if: runner.os == 'Linux' + run: cmake --build ${{ env.build_dir }} --target openvdb_shared -j 2 + - name: Build run: cmake --build ${{ env.build_dir }} -j ${{ steps.cpu-cores.outputs.count }} diff --git a/cmake/recipes/external/OpenVDB.cmake b/cmake/recipes/external/OpenVDB.cmake index c0bae13e..b811af08 100644 --- a/cmake/recipes/external/OpenVDB.cmake +++ b/cmake/recipes/external/OpenVDB.cmake @@ -176,6 +176,12 @@ function(openvdb_import_target) # Inject real Boost dependencies instead of dummy Boost:headers one foreach(name IN ITEMS openvdb_static openvdb_shared) if(TARGET ${name}) + if(LAGRANGE_LIMIT_PARALLELISM + AND CMAKE_SYSTEM_NAME STREQUAL "Linux" + AND CMAKE_GENERATOR MATCHES "^Ninja") + set_property(TARGET ${name} PROPERTY JOB_POOL_COMPILE pool-heavy-compile) + set_property(TARGET ${name} PROPERTY JOB_POOL_LINK pool-link) + endif() target_link_libraries(${name} PUBLIC Boost::algorithm From 9445cbf12342b6226f202e592290673167ef7e1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Thu, 10 Sep 2026 10:51:08 -0700 Subject: [PATCH 13/18] Scope heavy compile pools to GitHub Actions --- .github/workflows/continuous.yaml | 1 + CMakeLists.txt | 4 +++ .../lagrange/lagrange_limit_parallelism.cmake | 27 ++++++++++--------- cmake/recipes/external/OpenVDB.cmake | 3 +-- cmake/recipes/external/embree.cmake | 5 ++-- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 70a5af09..50dffecb 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -178,6 +178,7 @@ jobs: -DLAGRANGE_JENKINS=ON \ -DLAGRANGE_ALL=ON \ -DLAGRANGE_LIMIT_PARALLELISM=ON \ + -DLAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM=ON \ -DOPENVDB_CORE_SHARED=ON \ -DOPENVDB_CORE_STATIC=OFF \ -DUSE_EXPLICIT_INSTANTIATION=ON \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 56321d21..eb333513 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -258,6 +258,7 @@ option(LAGRANGE_EXAMPLES "Build all examples" option(LAGRANGE_FMT_EIGEN_FIX "Avoid MSVC C1001 error releated to fmt and eigen" ${LAGRANGE_FMT_EIGEN_FIX_DEFAULT}) option(LAGRANGE_INSTALL "Enable installation" ${LAGRANGE_TOPLEVEL_PROJECT}) option(LAGRANGE_LIMIT_PARALLELISM "Limit parallelism according to available cpu/memory" OFF) +option(LAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM "Limit heavy compile jobs on GitHub Actions" OFF) option(LAGRANGE_MORE_WARNINGS "Increase the level of warnings when compiling" OFF) option(LAGRANGE_PROPAGATE_WARNINGS "Propagate warning flags to dependent targets" ON) option(LAGRANGE_PERFORMANCE_TESTS "Build all performance tests" OFF) @@ -441,6 +442,9 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) if(LAGRANGE_LIMIT_PARALLELISM) lagrange_limit_parallelism() endif() +if(LAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM) + lagrange_limit_github_actions_parallelism() +endif() set(TBB_ENABLE_WASM_THREADS ${LAGRANGE_USE_WASM_THREADS}) diff --git a/cmake/lagrange/lagrange_limit_parallelism.cmake b/cmake/lagrange/lagrange_limit_parallelism.cmake index 5f543e61..2cf9c7e5 100644 --- a/cmake/lagrange/lagrange_limit_parallelism.cmake +++ b/cmake/lagrange/lagrange_limit_parallelism.cmake @@ -44,14 +44,6 @@ function(lagrange_limit_parallelism) set(num_link_jobs 1) endif() - set(num_heavy_compile_jobs 2) - if(CMAKE_SYSTEM_NAME STREQUAL "Linux" - AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" - AND USE_SANITIZER MATCHES "([Aa]ddress)" - AND USE_SANITIZER MATCHES "([Uu]ndefined)") - set(num_heavy_compile_jobs 1) - endif() - if(CMAKE_SCRIPT_MODE_FILE) # Script mode: echo the number of physical cores for use as the -j flag in Jenkins. # Link parallelism is handled separately via Ninja job pools at configure time. @@ -60,16 +52,25 @@ function(lagrange_limit_parallelism) message(STATUS "Parallelism: Total physical memory: ${TOTAL_PHYSICAL_MEMORY} MB") message(STATUS "Parallelism: Link job memory budget: ${_link_memory} MB (${_build_type})") message(STATUS "Parallelism: Limiting link pool to ${num_link_jobs}") - message(STATUS "Parallelism: Limiting heavy compile pool to ${num_heavy_compile_jobs}") - set_property(GLOBAL PROPERTY JOB_POOLS - pool-link=${num_link_jobs} - pool-heavy-compile=${num_heavy_compile_jobs} - ) + set_property(GLOBAL PROPERTY JOB_POOLS pool-link=${num_link_jobs}) set(CMAKE_JOB_POOL_LINK "pool-link" CACHE STRING "Job pool for linking" FORCE) endif() endfunction() +function(lagrange_limit_github_actions_parallelism) + set(num_heavy_compile_jobs 2) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux" + AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang" + AND USE_SANITIZER MATCHES "([Aa]ddress)" + AND USE_SANITIZER MATCHES "([Uu]ndefined)") + set(num_heavy_compile_jobs 1) + endif() + + message(STATUS "Parallelism: Limiting heavy compile pool to ${num_heavy_compile_jobs}") + set_property(GLOBAL APPEND PROPERTY JOB_POOLS pool-heavy-compile=${num_heavy_compile_jobs}) +endfunction() + # If this file is run in script mode, it echoes the number of physical cores for use as # the -j flag for cmake --build and ctest. Link parallelism is not relevant here — it is # enforced by Ninja job pools set during the configure step. diff --git a/cmake/recipes/external/OpenVDB.cmake b/cmake/recipes/external/OpenVDB.cmake index b811af08..cbf41161 100644 --- a/cmake/recipes/external/OpenVDB.cmake +++ b/cmake/recipes/external/OpenVDB.cmake @@ -176,11 +176,10 @@ function(openvdb_import_target) # Inject real Boost dependencies instead of dummy Boost:headers one foreach(name IN ITEMS openvdb_static openvdb_shared) if(TARGET ${name}) - if(LAGRANGE_LIMIT_PARALLELISM + if(LAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_GENERATOR MATCHES "^Ninja") set_property(TARGET ${name} PROPERTY JOB_POOL_COMPILE pool-heavy-compile) - set_property(TARGET ${name} PROPERTY JOB_POOL_LINK pool-link) endif() target_link_libraries(${name} PUBLIC diff --git a/cmake/recipes/external/embree.cmake b/cmake/recipes/external/embree.cmake index 52b31c8a..e159fc6e 100644 --- a/cmake/recipes/external/embree.cmake +++ b/cmake/recipes/external/embree.cmake @@ -131,9 +131,10 @@ function(embree_import_target) # Embree has several memory-intensive translation units. Limit their concurrency on # Linux when the project's memory-aware parallelism policy is enabled. - if(LAGRANGE_LIMIT_PARALLELISM AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_GENERATOR MATCHES "^Ninja") + if(LAGRANGE_LIMIT_GITHUB_ACTIONS_PARALLELISM + AND CMAKE_SYSTEM_NAME STREQUAL "Linux" + AND CMAKE_GENERATOR MATCHES "^Ninja") set_property(TARGET embree PROPERTY JOB_POOL_COMPILE pool-heavy-compile) - set_property(TARGET embree PROPERTY JOB_POOL_LINK pool-link) endif() # Disable warnings From ea8d0ec22a64ba97fcf40cdbc0e3ad36484da3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Fri, 11 Sep 2026 09:58:08 -0700 Subject: [PATCH 14/18] Show more disk space. --- .github/workflows/continuous.yaml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 50dffecb..d3b76c68 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -187,13 +187,25 @@ jobs: -DUSE_SANITIZER="${{ matrix.sanitizer }}" - name: Build Embree - if: runner.os == 'Linux' + if: runner.os == 'Linux' || matrix.compiler == 'llvm' run: cmake --build ${{ env.build_dir }} --target embree -j 2 + - name: Show disk space + if: always() + run: | + echo "disk usage:" + df -h + - name: Build OpenVDB - if: runner.os == 'Linux' + if: runner.os == 'Linux' || matrix.compiler == 'llvm' run: cmake --build ${{ env.build_dir }} --target openvdb_shared -j 2 + - name: Show disk space + if: always() + run: | + echo "disk usage:" + df -h + - name: Build run: cmake --build ${{ env.build_dir }} -j ${{ steps.cpu-cores.outputs.count }} From 6da7181cb0af24643320798e650b74fe6ba72b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Fri, 11 Sep 2026 12:30:41 -0700 Subject: [PATCH 15/18] Free disk space on macOS. --- .github/workflows/continuous.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index d3b76c68..7b802084 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -43,7 +43,7 @@ jobs: - { os: macos-15, compiler: llvm, config: Debug, sanitizer: "Address;Undefined" } - { os: macos-15, compiler: llvm, config: Debug, sanitizer: Thread } steps: - - name: Free Disk Space + - name: Free Disk Space (Linux) if: runner.os == 'Linux' uses: jlumbroso/free-disk-space@main with: @@ -60,6 +60,14 @@ jobs: docker-images: true swap-storage: true + - name: Free Disk Space (macOS) + if: runner.os == 'macOS' + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - name: Set swap space if: runner.os == 'Linux' && matrix.compiler == 'gcc' && matrix.config == 'RelWithDebInfo' uses: pierotofy/set-swap-space@fc79b3f67fa8a838184ce84a674ca12238d2c761 From f64e320b803a97eb53e01ca48cd700a1815f861b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Fri, 11 Sep 2026 17:25:29 -0700 Subject: [PATCH 16/18] Reclaim macOS simulator and Android storage --- .github/workflows/continuous.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index 7b802084..c0b7dea6 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -63,10 +63,21 @@ jobs: - name: Free Disk Space (macOS) if: runner.os == 'macOS' run: | - sudo rm -rf /usr/share/dotnet + echo "Disk usage before cleanup:" + df -h + sudo rm -rf /Library/Developer/CoreSimulator + sudo rm -rf "$HOME/Library/Developer/CoreSimulator" + sudo rm -rf /System/Library/AssetsV2/com_apple_MobileAsset_iOSSimulatorRuntime + sudo rm -rf /System/Library/AssetsV2/com_apple_MobileAsset_xrOSSimulatorRuntime + sudo rm -rf /System/Library/AssetsV2/com_apple_MobileAsset_watchOSSimulatorRuntime + sudo rm -rf /System/Library/AssetsV2/com_apple_MobileAsset_appleTVOSSimulatorRuntime + sudo rm -rf "$ANDROID_HOME" + sudo rm -rf /usr/local/share/dotnet sudo rm -rf /opt/ghc sudo rm -rf "/usr/local/share/boost" sudo rm -rf "$AGENT_TOOLSDIRECTORY" + echo "Disk usage after cleanup:" + df -h - name: Set swap space if: runner.os == 'Linux' && matrix.compiler == 'gcc' && matrix.config == 'RelWithDebInfo' From 3e5fedb7aa205f6fad181db7f83f53cce67dd6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Sat, 12 Sep 2026 07:57:59 -0700 Subject: [PATCH 17/18] Use compressed split DWARF for GCC sanitizer CI --- .github/workflows/continuous.yaml | 3 ++- CMakeLists.txt | 1 + cmake/lagrange/lagrange_global_flags.cmake | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yaml b/.github/workflows/continuous.yaml index c0b7dea6..64be9f5e 100644 --- a/.github/workflows/continuous.yaml +++ b/.github/workflows/continuous.yaml @@ -31,7 +31,7 @@ jobs: matrix: include: # Linux release builds exercise tests disabled in Debug mode, and ASan also enables LSan. - - { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: "Address;Undefined" } + - { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: "Address;Undefined", split_debug: ON } - { os: ubuntu-24.04, compiler: gcc, config: RelWithDebInfo, sanitizer: Thread } - { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: "Address;Undefined" } - { os: ubuntu-24.04, compiler: llvm, config: Debug, sanitizer: Thread } @@ -202,6 +202,7 @@ jobs: -DOPENVDB_CORE_STATIC=OFF \ -DUSE_EXPLICIT_INSTANTIATION=ON \ -DLAGRANGE_POLYSCOPE_MOCK_BACKEND=ON \ + -DLAGRANGE_USE_SPLIT_DEBUG_INFO=${{ matrix.split_debug }} \ -DEMBREE_MAX_ISA=${{ env.embree_max_isa }} \ -DUSE_SANITIZER="${{ matrix.sanitizer }}" diff --git a/CMakeLists.txt b/CMakeLists.txt index eb333513..9d2ed5cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -264,6 +264,7 @@ option(LAGRANGE_PROPAGATE_WARNINGS "Propagate warning flags to dependent ta option(LAGRANGE_PERFORMANCE_TESTS "Build all performance tests" OFF) option(LAGRANGE_UNIT_TESTS "Build all unit tests" ${LAGRANGE_TOPLEVEL_PROJECT}) option(LAGRANGE_USE_PCH "Enable precompiled headers" OFF) +option(LAGRANGE_USE_SPLIT_DEBUG_INFO "Store debug information in separate compressed files" OFF) option(LAGRANGE_USE_WASM_EXCEPTIONS "Use -fwasm-exception flag with Emscripten" ON) option(LAGRANGE_USE_WASM_THREADS "Enable threads (-pthread) with Emscripten" ON) option(LAGRANGE_WITH_TRACY "Build tracy client with Lagrange" OFF) diff --git a/cmake/lagrange/lagrange_global_flags.cmake b/cmake/lagrange/lagrange_global_flags.cmake index 4f2db768..13aa95c7 100644 --- a/cmake/lagrange/lagrange_global_flags.cmake +++ b/cmake/lagrange/lagrange_global_flags.cmake @@ -49,6 +49,11 @@ else() add_compile_options(${LAGRANGE_GLOBAL_FLAGS}) endif() +if(LAGRANGE_USE_SPLIT_DEBUG_INFO) + add_compile_options(-gsplit-dwarf -gz=zstd) + add_link_options(-Wl,--compress-debug-sections=zstd) +endif() + if(LAGRANGE_WITH_TRACY) include(lagrange_filter_flags) set(LAGRANGE_GLOBAL_FLAGS From b25471d696949f2a377c0e4fbc5c831430dba048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Je=CC=81re=CC=81mie=20Dumas?= Date: Wed, 16 Sep 2026 03:02:13 +0000 Subject: [PATCH 18/18] Fix Ubuntu UBSan failures --- cmake/lagrange/lagrange_set_sanitizers.cmake | 12 ++++ cmake/recipes/external/embree.cmake | 19 ++++++ cmake/recipes/external/ufbx.cmake | 4 ++ modules/core/src/SurfaceMesh.cpp | 63 +++++++++++--------- 4 files changed, 70 insertions(+), 28 deletions(-) diff --git a/cmake/lagrange/lagrange_set_sanitizers.cmake b/cmake/lagrange/lagrange_set_sanitizers.cmake index 184e7cd7..93a46985 100644 --- a/cmake/lagrange/lagrange_set_sanitizers.cmake +++ b/cmake/lagrange/lagrange_set_sanitizers.cmake @@ -12,6 +12,18 @@ include(sanitizers) +# Some bundled dependencies intentionally use operations rejected by UBSan (for example, +# function-pointer casts in Embree). Keep the sanitizer enabled for Lagrange while allowing +# concrete third-party targets to opt out without propagating the flag to their consumers. +function(lagrange_disable_ubsan_for_external target) + if(TARGET "${target}" AND USE_SANITIZER MATCHES "([Uu]ndefined)") + target_compile_options("${target}" PRIVATE + $<$:-fno-sanitize=undefined> + $<$:-fno-sanitize=undefined> + ) + endif() +endfunction() + if(MSVC AND USE_SANITIZER) # To use sanitizers with MSVC you need to disable incompatible options. See details here: # https://learn.microsoft.com/en-us/cpp/sanitizers/asan?view=msvc-170#ide-msbuild diff --git a/cmake/recipes/external/embree.cmake b/cmake/recipes/external/embree.cmake index e159fc6e..46081ec1 100644 --- a/cmake/recipes/external/embree.cmake +++ b/cmake/recipes/external/embree.cmake @@ -154,6 +154,25 @@ function(embree_import_target) target_compile_options(embree PRIVATE "-Wno-unused-but-set-variable") endif() + # Embree intentionally dispatches through ABI-compatible function pointers and uses + # type-punning downcasts internally. Exclude only its implementation from UBSan; consumers + # and Lagrange's raycasting code remain instrumented. + foreach(target IN ITEMS + embree + embree_sse42 + embree_avx + embree_avx2 + embree_avx512 + algorithms + lexers + math + simd + sys + tasking + ) + lagrange_disable_ubsan_for_external(${target}) + endforeach() + # Now we need to do some juggling to propagate the include directory properties # along with the `embree` target add_library(embree::embree INTERFACE IMPORTED GLOBAL) diff --git a/cmake/recipes/external/ufbx.cmake b/cmake/recipes/external/ufbx.cmake index 7ac2c981..d55b097a 100644 --- a/cmake/recipes/external/ufbx.cmake +++ b/cmake/recipes/external/ufbx.cmake @@ -32,6 +32,10 @@ target_include_directories(ufbx PUBLIC set_target_properties(ufbx PROPERTIES FOLDER third_party) set_target_properties(ufbx PROPERTIES POSITION_INDEPENDENT_CODE ON) +# ufbx's bit reader intentionally shifts by the encoded bit count before validating it. +# UBSan diagnoses this in valid FBX inputs, so exclude only the bundled implementation. +lagrange_disable_ubsan_for_external(ufbx) + # Install rules set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME ufbx) install(DIRECTORY ${ufbx_SOURCE_DIR} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) diff --git a/modules/core/src/SurfaceMesh.cpp b/modules/core/src/SurfaceMesh.cpp index 9874f50d..41671431 100644 --- a/modules/core/src/SurfaceMesh.cpp +++ b/modules/core/src/SurfaceMesh.cpp @@ -38,6 +38,7 @@ // clang-format on #include +#include #include #include @@ -3697,40 +3698,46 @@ SurfaceMesh to_surface_mesh(const SurfaceMeshInfo& info) if (ai->is_indexed) { switch (value_type) { -#define LA_X_restore_indexed(_, ValueType) \ - case make_attribute_value_type(): { \ - auto values = span( \ - reinterpret_cast(ai->values_bytes.data()), \ - ai->values_bytes.size() / sizeof(ValueType)); \ - auto indices = span( \ - reinterpret_cast(ai->indices_bytes.data()), \ - ai->indices_bytes.size() / sizeof(Index)); \ - id = mesh.template create_attribute_internal( \ - ai->name, \ - element, \ - usage, \ - ai->values_num_channels, \ - values, \ - indices); \ - break; \ +#define LA_X_restore_indexed(_, ValueType) \ + case make_attribute_value_type(): { \ + la_runtime_assert(ai->values_bytes.size() % sizeof(ValueType) == 0); \ + la_runtime_assert(ai->indices_bytes.size() % sizeof(Index) == 0); \ + std::vector values(ai->values_bytes.size() / sizeof(ValueType)); \ + std::vector indices(ai->indices_bytes.size() / sizeof(Index)); \ + if (!values.empty()) { \ + std::memcpy(values.data(), ai->values_bytes.data(), ai->values_bytes.size()); \ + } \ + if (!indices.empty()) { \ + std::memcpy(indices.data(), ai->indices_bytes.data(), ai->indices_bytes.size()); \ + } \ + id = mesh.template create_attribute_internal( \ + ai->name, \ + element, \ + usage, \ + ai->values_num_channels, \ + span(values.data(), values.size()), \ + span(indices.data(), indices.size())); \ + break; \ } LA_ATTRIBUTE_X(restore_indexed, 0) #undef LA_X_restore_indexed } } else { switch (value_type) { -#define LA_X_restore_attr(_, ValueType) \ - case make_attribute_value_type(): { \ - auto data = span( \ - reinterpret_cast(ai->data_bytes.data()), \ - ai->data_bytes.size() / sizeof(ValueType)); \ - id = mesh.template create_attribute_internal( \ - ai->name, \ - element, \ - usage, \ - ai->num_channels, \ - data); \ - break; \ +#define LA_X_restore_attr(_, ValueType) \ + case make_attribute_value_type(): { \ + la_runtime_assert(ai->data_bytes.size() % sizeof(ValueType) == 0); \ + std::vector data(ai->data_bytes.size() / sizeof(ValueType)); \ + if (!data.empty()) { \ + std::memcpy(data.data(), ai->data_bytes.data(), ai->data_bytes.size()); \ + } \ + id = mesh.template create_attribute_internal( \ + ai->name, \ + element, \ + usage, \ + ai->num_channels, \ + span(data.data(), data.size())); \ + break; \ } LA_ATTRIBUTE_X(restore_attr, 0) #undef LA_X_restore_attr