[clang] [clang] increase default constexpr step limit (PR #143785)

via cfe-commits cfe-commits at lists.llvm.org
Wed Jun 11 14:12:17 PDT 2025


https://github.com/Tsche created https://github.com/llvm/llvm-project/pull/143785

Currently clang limits the number of constant evaluation steps to 1'048'576 (or 2^20) by default. This default comes from [[implimits]/1.39](https://eel.is/c++draft/implimits#1.39).

This limit is easily reached - for example in libc++ tests we override this default in many places:
<details>
<summary>overrides in libc++ tests</summary>

| Step Limit | Test |
|----------------|--------|
| 2000000 | libcxx/test/std/algorithms/alg.modifying.operations/alg.move/move.pass.cpp |
| 2000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.contains/ranges.contains_subrange.pass.cpp |
| 2000000 | libcxx/test/std/containers/sequences/vector/vector.modifiers/append_range.pass.cpp |
| 2000000 | libcxx/test/std/containers/sequences/vector/vector.modifiers/assign_range.pass.cpp |
| 2000000 | libcxx/test/std/containers/sequences/vector/vector.modifiers/insert_range.pass.cpp |
| 2000000 | libcxx/test/std/containers/sequences/vector.bool/append_range.pass.cpp |
| 2000000 | libcxx/test/std/containers/sequences/vector.bool/assign_range.pass.cpp |
| 2000000 | libcxx/test/std/containers/sequences/vector.bool/insert_range.pass.cpp |
| 2000000 | libcxx/test/std/numerics/complex.number/complex.ops/complex_divide_complex.pass.cpp |
| 2000000 | libcxx/test/std/numerics/complex.number/complex.ops/complex_times_complex.pass.cpp |
| 2000000 | libcxx/test/std/utilities/template.bitset/bitset.members/op_and_eq.pass.cpp |
| 3000000 | libcxx/test/std/containers/sequences/forwardlist/forwardlist.ops/splice_after_range.pass.cpp |
| 9000000 | libcxx/test/libcxx/input.output/iostream.format/print.fun/transcoding.pass.cpp |
| 9000000 | libcxx/test/std/containers/sequences/vector/vector.modifiers/emplace.pass.cpp |
| 9000000 | libcxx/test/std/strings/basic.string/string.nonmembers/string_op+/string.string_view.pass.cpp |
| 10000000 | libcxx/test/std/strings/basic.string/string.modifiers/string_replace/replace_with_range.pass.cpp |
| 12712420 | libcxx/test/std/utilities/charconv/charconv.from.chars/integral.roundtrip.pass.cpp |
| 12712420 | libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp |
| 12712420 | libcxx/test/std/utilities/template.bitset/bitset.members/left_shift.pass.cpp |
| 15000000 | libcxx/test/std/utilities/template.bitset/bitset.members/left_shift_eq.pass.cpp |
| 15000000 | libcxx/test/std/utilities/template.bitset/bitset.members/op_or_eq.pass.cpp |
| 15000000 | libcxx/test/std/utilities/template.bitset/bitset.members/right_shift_eq.pass.cpp |
| 20000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.count/count.pass.cpp |
| 20000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.count/ranges.count.pass.cpp |
| 20000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.find/find.pass.cpp |
| 20000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find.pass.cpp |
| 20000000 | libcxx/test/std/containers/sequences/forwardlist/forwardlist.modifiers/insert_range_after.pass.cpp |
| 50000000 | libcxx/test/std/algorithms/alg.nonmodifying/mismatch/mismatch.pass.cpp |
| 200000000 | libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort_comp.pass.cpp |
| 200000000 | libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp |

</details>

In libc++ tests that override both `-fconstexpr-steps` and GCC's `-fconstexpr-ops-limit`, we see a factor of 0.25 applied in most cases:
<details>
<summary>overrides in libc++ tests</summary>

| Factor | `-fconstexpr-steps` | `-fconstexpr-ops-limit` | Test |
|--------|---------------------|-------------------------|------|
| 0.14 | 10000000 | 70000000 | libcxx/test/std/strings/basic.string/string.modifiers/string_replace/replace_with_range.pass.cpp |
| 0.25 | 20000000 | 80000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.count/count.pass.cpp |
| 0.25 | 20000000 | 80000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.count/ranges.count.pass.cpp |
| 0.25 | 20000000 | 80000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.find/find.pass.cpp |
| 0.25 | 20000000 | 80000000 | libcxx/test/std/algorithms/alg.nonmodifying/alg.find/ranges.find.pass.cpp |
| 0.25 | 12712420 | 50000000 | libcxx/test/std/utilities/charconv/charconv.to.chars/integral.pass.cpp |
| 0.5 | 50000000 | 100000000 | libcxx/test/std/algorithms/alg.nonmodifying/mismatch/mismatch.pass.cpp |
| 1 | 200000000 | 200000000 | libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort_comp.pass.cpp |
| 1 | 200000000 | 200000000 | libcxx/test/std/algorithms/alg.sorting/alg.sort/stable.sort/stable_sort.pass.cpp |

</details>

So, assuming `0.25` as a conversion factor, this would require increasing the limit to at least 8'388'608 to match gcc's default of 33'554'432. 

With heavy metaprogramming features like reflection still on track for C++26, I believe it is reasonable to increase this limit. For instance, recursively walking all namespaces and performing a set of checks for every reflected member of that namespace immediately hits this limit for the global namespace - which roughly yields 3000 member reflections even without descending.


This patch increases the default limit to 20'000'000. With this default all but the last 3 tests in the first table would not have to override the default. 

I think this is a more reasonable default - on my machine 20 million steps take roughly 10 seconds.

>From b251a36491172b5876b252ef9a312a2b1db0dfeb Mon Sep 17 00:00:00 2001
From: Tsche <che at pydong.org>
Date: Wed, 11 Jun 2025 22:37:06 +0200
Subject: [PATCH] [clang] increase default constexpr step limit

---
 clang/docs/UsersManual.rst                | 2 +-
 clang/include/clang/Basic/LangOptions.def | 2 +-
 clang/include/clang/Driver/Options.td     | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/docs/UsersManual.rst b/clang/docs/UsersManual.rst
index 62844f7e6a2fa..c948ba218c219 100644
--- a/clang/docs/UsersManual.rst
+++ b/clang/docs/UsersManual.rst
@@ -3992,7 +3992,7 @@ Controlling implementation limits
   Sets the limit for the number of full-expressions evaluated in a single
   constant expression evaluation. This also controls the maximum size
   of array and dynamic array allocation that can be constant evaluated.
-  The default is 1048576.
+  The default is 20000000.
 
 .. option:: -ftemplate-depth=N
 
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 789761c1f3647..ba3c8dac8b35d 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -415,7 +415,7 @@ BENIGN_LANGOPT(InstantiationDepth, 32, 1024,
                "maximum template instantiation depth")
 BENIGN_LANGOPT(ConstexprCallDepth, 32, 512,
                "maximum constexpr call depth")
-BENIGN_LANGOPT(ConstexprStepLimit, 32, 1048576,
+BENIGN_LANGOPT(ConstexprStepLimit, 32, 20000000,
                "maximum constexpr evaluation steps")
 BENIGN_LANGOPT(EnableNewConstInterp, 1, 0,
                "enable the experimental new constant interpreter")
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 152df89118a6a..4886e7b0a45e0 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2002,7 +2002,7 @@ def fconstexpr_depth_EQ : Joined<["-"], "fconstexpr-depth=">, Group<f_Group>,
 def fconstexpr_steps_EQ : Joined<["-"], "fconstexpr-steps=">, Group<f_Group>,
   Visibility<[ClangOption, CC1Option]>,
   HelpText<"Set the maximum number of steps in constexpr function evaluation">,
-  MarshallingInfoInt<LangOpts<"ConstexprStepLimit">, "1048576">;
+  MarshallingInfoInt<LangOpts<"ConstexprStepLimit">, "20000000">;
 def fexperimental_new_constant_interpreter : Flag<["-"], "fexperimental-new-constant-interpreter">, Group<f_Group>,
   HelpText<"Enable the experimental new constant interpreter">,
   Visibility<[ClangOption, CC1Option]>,



More information about the cfe-commits mailing list