Skip to content

CGAL_ASSUME should use __builtin_assume() or [[assume]] (#7334)#9355

Open
RajdeepKushwaha5 wants to merge 1 commit intoCGAL:mainfrom
RajdeepKushwaha5:enhance/cgal-assume-builtin
Open

CGAL_ASSUME should use __builtin_assume() or [[assume]] (#7334)#9355
RajdeepKushwaha5 wants to merge 1 commit intoCGAL:mainfrom
RajdeepKushwaha5:enhance/cgal-assume-builtin

Conversation

@RajdeepKushwaha5
Copy link
Contributor

Problem:
The CGAL_ASSUME(EX) macro (in Installation/include/CGAL/config.h) used the following implementation for GCC/Clang:

#define CGAL_ASSUME(EX) if(!(EX)) { __builtin_unreachable(); }

This evaluates EX at runtime as part of the if-condition, even in optimised builds. That has non-zero overhead and potential side-effects, even though the entire purpose of the macro is to give the compiler a zero-cost optimisation hint.

Clang (and Intel ICC) support __builtin_assume(expr) which is a true assumption:
the expression is never evaluated and the compiler uses it purely as a hint.

Fix: Added a new #elif __has_builtin(__builtin_assume) branch before the __builtin_unreachable fallback branch. Priority order is now:

  1. C++23 [[assume(EX)]] — no expression evaluation, portable
  2. __builtin_assume(EX) — Clang/ICC, no evaluation (new)
  3. if(!(EX)){ __builtin_unreachable(); } — GCC fallback, evaluates EX
  4. __assume(EX) — MSVC, no evaluation

Also improved comments to clearly document which forms evaluate the expression and which do not.

Files changed:

  • Installation/include/CGAL/config.h (+9/-2 lines)

Copilot AI review requested due to automatic review settings March 1, 2026 17:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates CGAL’s portability/config layer to make CGAL_ASSUME(EX) a true “assumption” (i.e., zero-cost and without runtime evaluation of EX) on more compilers, improving optimization opportunities and avoiding unintended side-effects in optimized builds.

Changes:

  • Add a __builtin_assume(EX) implementation path (via __has_builtin(__builtin_assume)) ahead of the __builtin_unreachable fallback.
  • Clarify comments to document which CGAL_ASSUME variants do vs. do not evaluate EX at runtime.
Comments suppressed due to low confidence (1)

Installation/include/CGAL/config.h:370

  • The __has_builtin(__builtin_assume) branch unconditionally defines CGAL_UNREACHABLE() as __builtin_unreachable(), but the preprocessor condition does not verify that __builtin_unreachable is available. For portability/feature-detection correctness, either include __has_builtin(__builtin_unreachable) in the condition, or provide a separate fallback definition for CGAL_UNREACHABLE() when __builtin_unreachable is not supported.
#elif __has_builtin(__builtin_assume)
// Clang (and compilers supporting __builtin_assume): the expression is NOT
// evaluated, making it a true zero-cost assumption hint.
#  define CGAL_ASSUME(EX) __builtin_assume(EX)
#  define CGAL_UNREACHABLE() __builtin_unreachable()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mglisse
Copy link
Member

mglisse commented Mar 3, 2026

Note that [[assume(EX)]] is not strictly better than __builtin_unreachable(). With gcc, it is different. It is guaranteed not to evaluate EX at runtime (no side effects, unlike unreachable), but that does not guarantee no overhead (its presence may accidentally delay some optimization to a later pass, and eventually result in less optimization, same as unreachable), and most importantly it enables a much more restricted set of optimizations (to guarantee no side effects, it is often just ignored, unlike unreachable).
It should be fine for k>0 (equivalent to unreachable), but n!=m may already be too much for it. If [[assume(EX)]] becomes the default, someone would need to go through all the uses to check, and possibly introduce a CGAL_ASSUME_STRONG for cases where gcc can benefit from the stronger unreachable optimizations.
The situation with other compilers may be different.

…in_unreachable() on GCC

Add __builtin_assume(EX) path for Clang/ICC (zero-cost, no evaluation).
Exclude GCC from C++23 [[assume(EX)]] path because __builtin_unreachable()
enables stronger optimizations on GCC (where [[assume]] is often ignored).

Priority order:
1. [[assume(EX)]]        - C++23, but NOT on GCC
2. __builtin_assume(EX)  - Clang/ICC, no evaluation
3. __builtin_unreachable  - GCC (always), stronger optimizations
4. __assume(EX)          - MSVC, no evaluation

Fixes CGAL#7334
@RajdeepKushwaha5 RajdeepKushwaha5 force-pushed the enhance/cgal-assume-builtin branch from 99e7a1d to e7ee58f Compare March 20, 2026 09:34
@RajdeepKushwaha5
Copy link
Contributor Author

Note that [[assume(EX)]] is not strictly better than __builtin_unreachable(). With gcc, it is different. It is guaranteed not to evaluate EX at runtime (no side effects, unlike unreachable), but that does not guarantee no overhead (its presence may accidentally delay some optimization to a later pass, and eventually result in less optimization, same as unreachable), and most importantly it enables a much more restricted set of optimizations (to guarantee no side effects, it is often just ignored, unlike unreachable). It should be fine for k>0 (equivalent to unreachable), but n!=m may already be too much for it. If [[assume(EX)]] becomes the default, someone would need to go through all the uses to check, and possibly introduce a CGAL_ASSUME_STRONG for cases where gcc can benefit from the stronger unreachable optimizations. The situation with other compilers may be different.

Thanks for the detailed feedback @mglisse — that's a really important nuance about GCC's [[assume]] vs __builtin_unreachable().

I've updated the PR to address this. The changes:

  1. GCC is now excluded from the C++23 [[assume(EX)]] path, so it always falls through to if(!(EX)){__builtin_unreachable();} regardless of C++ standard mode. This preserves the stronger optimization behavior you described.

  2. The __builtin_assume(EX) path (new) only triggers on compilers that actually support it (Clang/ICC via __has_builtin), where it's genuinely a zero-cost non-evaluating hint.

The guard is: #if defined(CGAL_CXX23) && !(defined(__GNUC__) && !defined(__clang__)) — this excludes "real" GCC (and ICC, which also defines __GNUC__) while still allowing Clang (which defines both __GNUC__ and __clang__) and MSVC to use [[assume]].

Updated priority order:

Compiler C++23 mode Pre-C++23
GCC if(!(EX)){__builtin_unreachable();} same
Clang [[assume(EX)]] __builtin_assume(EX)
MSVC [[assume(EX)]] __assume(EX)
ICC __builtin_assume(EX) __builtin_assume(EX)

This way GCC keeps the stronger __builtin_unreachable() path (which works well for all existing uses like k>0 and n!=m), while Clang/MSVC benefit from the non-evaluating assumption hints. No CGAL_ASSUME_STRONG split should be needed with this approach.

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.

3 participants