Skip to content

Pin Eigen's ABI on the Eigen target - #297

Merged
zfergus merged 3 commits into
mainfrom
fix/eigen-abi-define
Nov 9, 2026
Merged

zfergus merged 3 commits into
mainfrom
fix/eigen-abi-define

Conversation

@zfergus

@zfergus zfergus commented Nov 8, 2026

Copy link
Copy Markdown
Member

Description

Moves the Eigen ABI settings (EIGEN_DONT_VECTORIZE and the CUDA alignment pins) from ipc_toolkit and individual dependencies onto the Eigen3::Eigen target. It touches the CMake setup, ipc/utils, the tests and CI.

  • cmake/ipc_toolkit/ipc_toolkit_eigen_abi.cmake adds ipc_toolkit_pin_eigen_abi(<eigen_target> [DONT_VECTORIZE] [PIN_ALIGNMENT]). It appends the definitions to the Eigen target's INTERFACE_COMPILE_DEFINITIONS, so every target that links Eigen inherits them.
  • The per-dependency defines in finite_diff.cmake and meshfem_sparse.cmake are removed, since those targets now inherit the settings.
  • ipc/utils/eigen_abi.hpp adds a runtime check that the caller and the library agree on Eigen's ABI.
  • .github/workflows/odr-check.yml links the tests with LTO and -Wodr, and fails on any ODR violation.

Why. Eigen picks its alignment per translation unit from the compile flags. That alignment decides the layout of fixed-size types and which allocator Eigen uses. Before this PR only ipc_toolkit and a hand-patched list of dependencies carried EIGEN_DONT_VECTORIZE.

Built with EIGEN_MAX_ALIGN_BYTES sizeof(struct { double; Vector2d; }) sizeof(VectorMax<double, 18>) Heap allocator
EIGEN_DONT_VECTORIZE (IPC Toolkit) 0 24 152 malloc/free
No flags (a dependency by default) 16 32 160 malloc/free
-march=x86-64-v3, no define 32 32 160 Eigen's offset allocator
  • Reproduced in a two-file program (GCC 15.2, Ubuntu 26.04). A mismatched layout gives wrong values and silent buffer overruns in arrays of structs. Returning a fixed-max vector by value fails with *** stack smashing detected ***. A mismatched allocator fails with free(): invalid pointer.
  • Why failures look flaky. Whether this crashes depends on which inline Eigen copy the linker keeps and what gets inlined. At -O0 the linker keeps one consistent copy and nothing fails, so passing tests don't rule it out.
  • Real instance. On dynamics, polysolve_linear was built without the define. LTO -Wodr found its Vector2d (16-byte aligned) conflicting with the IPC Toolkit's (8-byte aligned) in PointEdgeDistance<double, 2>::point_edge_closest_point_direction.
  • Also unpinned on main: tight-inclusion, scalable-ccd and tinyad were built without the define. They don't currently share a mismatched type, but they now inherit the pin.

API changes

  • New: ipc::EigenAbi, ipc::eigen_abi(), ipc::library_eigen_abi() and ipc::eigen_abi_matches_library().
    • eigen_abi() and eigen_abi_matches_library() are static on purpose. As inline functions the linker would merge them, and the library would report the caller's values.
  • Downstream builds that link ipc::toolkit now get the definitions through Eigen3::Eigen. That also applies to the parent project's other targets that link the same Eigen target.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How Has This Been Tested?

  • [eigen_abi] test passes.
  • A consumer compiled without EIGEN_DONT_VECTORIZE and linked against eigen_abi.cpp.o reports a mismatch. With matching flags it reports a match.
  • Full test suite: 457/457 on Ubuntu 26.04 (GCC 15.2.0, Release, -march=x86-64-v3). 456/456 on macOS arm64 (Apple clang 21, Debug) before the CMake helper refactor.
  • Every third-party target that links Eigen now compiles with EIGEN_DONT_VECTORIZE (finite-diff, MeshFEM, tight-inclusion, scalable-ccd, tinyad). I checked this per target from the build commands.
  • The LTO -Wodr build reports 0 violations on this branch. Its check step reports the violation above on a dynamics build without the polysolve fix.
  • The new ODR Check workflow has not run on GitHub yet; this PR is its first run.
  • CUDA builds: not tested locally. The CUDA alignment pins moved to the Eigen target unchanged.

Test Configuration:

  • OS and Version: Ubuntu 26.04.1 (KVM guest, AMD Ryzen 9 5900XT); macOS (arm64)
  • Compiler and Version: GCC 15.2.0; Apple clang 21

Checklist

  • My code follows the clang-format style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have followed the project style guide
  • My changes generate no new warnings
  • Any dependent changes have been merged and published in downstream modules

- Define EIGEN_DONT_VECTORIZE (and the CUDA alignment pins) on
  Eigen3::Eigen via ipc_toolkit_pin_eigen_abi(), so every dependency
  that links Eigen inherits them
- Drop the per-dependency defines for finite-diff and MeshFEMSparse
- Add ipc::eigen_abi_matches_library() and a test for it
- Add an LTO -Wodr workflow that fails on ODR violations

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.86%. Comparing base (182537a) to head (2adb383).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #297   +/-   ##
=======================================
  Coverage   96.86%   96.86%           
=======================================
  Files         227      229    +2     
  Lines       23035    23047   +12     
  Branches     1619     1620    +1     
=======================================
+ Hits        22312    22325   +13     
+ Misses        723      722    -1     
Flag Coverage Δ
unittests 96.86% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

zfergus and others added 2 commits November 8, 2026 20:32
- Drop the weekly scheduled run
- Run on pushes to main so PRs can restore its cache

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Pin a parent project's Eigen target before the recipe's early return
- Skip definitions the target already has, since the recipe is included repeatedly

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zfergus
zfergus marked this pull request as ready for review November 9, 2026 01:47
@zfergus
zfergus merged commit 6f3de04 into main Nov 9, 2026
18 checks passed
@zfergus
zfergus deleted the fix/eigen-abi-define branch November 9, 2026 01:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant