[clang] [clang] increase default constexpr step limit (PR #143785)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jun 11 14:13:10 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: None (Tsche)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/143785.diff
3 Files Affected:
- (modified) clang/docs/UsersManual.rst (+1-1)
- (modified) clang/include/clang/Basic/LangOptions.def (+1-1)
- (modified) clang/include/clang/Driver/Options.td (+1-1)
``````````diff
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]>,
``````````
</details>
https://github.com/llvm/llvm-project/pull/143785
More information about the cfe-commits
mailing list