Skip to content

Commit 43e10b9

Browse files
mborlanddaveedvdv-nvidia
authored andcommitted
Fix accounting of interpreter call depth not to double-count calls in arguments. [GH #178]
1 parent cd4cea1 commit 43e10b9

6 files changed

Lines changed: 67 additions & 2 deletions

File tree

‎src/Changes‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,23 @@ is for decltype(auto). For example:
1414

1515
Forms that are not a plain "auto", such as "auto *", still fail deduction.
1616

17+
10/8/26 [GH #178]
18+
Constexpr call depth limit and calls in arguments
19+
20+
The constexpr interpreter limits the nesting of calls to the value of the
21+
--max_depth_constexpr_call option by charging each call a large up-front cost
22+
that is refunded when the call completes. That cost was charged before the
23+
arguments of the call were evaluated, so a call appearing in an argument was
24+
treated as nested in the call to which the argument is passed, even though
25+
the latter has not yet been entered. For example, with --c++17 and
26+
--max_depth_constexpr_call=300:
27+
28+
constexpr int add(int a, int b) { return a + b; }
29+
constexpr int f(int n) { return n == 0 ? 0 : add(1, f(n - 1)); }
30+
static_assert(f(256) == 256, ""); // Previously not a constant.
31+
32+
Constructor calls had the same problem. This is now fixed.
33+
1734

1835
10/7/26 [GH #176]
1936
C-generating back end: always_inline attribute and inlined setjmp calls

‎src/interpret.c‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22952,7 +22952,6 @@ callee; the iwp_1st_resume visit completes the call in the latter case.
2295222952
a_boolean eval_right_to_left =
2295322953
call_node->variant.operation.eval_right_to_left;
2295422954
work_item_result_cap(item, &result_cap);
22955-
up_front_cost = charge_call_cost(ips);
2295622955
/* Set up arguments, starting with "this" if applicable. */
2295722956
/* This process must happen in two phases. First, the arguments must be
2295822957
allocated and evaluated. Only then can we map parameter variables onto
@@ -23165,6 +23164,7 @@ callee; the iwp_1st_resume visit completes the call in the latter case.
2316523164
should perform some more work (like mapping parameters) in case the
2316623165
intrinsic handling falls back to the provided definition (as, e.g.,
2316723166
std::construct_at does). */
23167+
up_front_cost = charge_call_cost(ips);
2316823168
push_new_call_frame(ips, callee, &call_node->position, result_cap);
2316923169
result = do_constexpr_intrinsic_call(
2317023170
ips, callee, call_node, (a_byte**)arg_ptrs,
@@ -23254,6 +23254,11 @@ callee; the iwp_1st_resume visit completes the call in the latter case.
2325423254
p_arg_ptr += 1;
2325523255
arg_size += 1;
2325623256
} /* for */
23257+
/* Charge for the call only now that the arguments have been evaluated.
23258+
A call nested in an argument (e.g., the "f(n-1)" in "g(f(n-1))") is
23259+
not nested in this call, and charging earlier would make it count
23260+
twice toward the call depth limit. */
23261+
up_front_cost = charge_call_cost(ips);
2325723262
/* Set up the call frame. */
2325823263
push_new_call_frame(ips, callee, &call_node->position, result_cap);
2325923264
/* Record what finish_call_work needs to undo all of the above. */
@@ -23499,7 +23504,6 @@ after the constructor body has been interpreted.
2349923504
class_type, ips);
2350023505
goto done;
2350123506
} /* if */
23502-
up_front_cost = charge_call_cost(ips);
2350323507
/* Set up arguments, starting with "this" if applicable. */
2350423508
/* This process must happen in two phases. First, the arguments must be
2350523509
allocated and evaluated. Only then can we map parameter variables onto
@@ -23656,6 +23660,9 @@ after the constructor body has been interpreted.
2365623660
memzero(result_storage+sizeof(void*),
2365723661
size_t_arg(n_class_bytes-sizeof(void*)));
2365823662
} /* if */
23663+
/* Charge for the call only now that the arguments have been evaluated
23664+
(see process_call_work). */
23665+
up_front_cost = charge_call_cost(ips);
2365923666
/* Set up the call frame. */
2366023667
push_new_call_frame(ips, callee, pos, cap);
2366123668
/* Mark all the empty base class subobjects as initialized. */
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fe_only -DTEST_NUMBER=1 --c++17 --max_depth_constexpr_call=300 Test_name.c
2+
"Test_name.c", line 11: error: expression must have a constant value
3+
static_assert(f(300) == 300, "");
4+
^
5+
"Test_name.c", line 11: note: expression not folded to a constant due to excessive constexpr function call complexity
6+
static_assert(f(300) == 300, "");
7+
^
8+
9+
1 error detected in the compilation of "Test_name.c".
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fe_only -DTEST_NUMBER=1 --c++17 --max_depth_constexpr_call=300 Test_name.c
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//type:fn
2+
//options_all:--c++17 --max_depth_constexpr_call=300
3+
4+
// Companion to gh-178.sft.cpp: a call in an argument no longer counts as
5+
// nested in the call it is passed to, but the depth limit is still enforced.
6+
7+
constexpr int add(int a, int b) { return a + b; }
8+
constexpr int f(int n) { return n == 0 ? 0 : add(1, f(n - 1)); }
9+
10+
// 301 nested calls of f: one call too many, still beyond the limit.
11+
static_assert(f(300) == 300, "");
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//type:fp
2+
//options_all:--c++17 --max_depth_constexpr_call=300
3+
4+
// A constexpr call that appears in an argument of another constexpr call is
5+
// evaluated before that other call is entered, so it is not nested in it.
6+
// Previously, the interpreter charged the outer call against the call depth
7+
// limit before evaluating its arguments, so each level of recursion below
8+
// counted twice and only about half the requested depth was available.
9+
10+
constexpr int add(int a, int b) { return a + b; }
11+
constexpr int f(int n) { return n == 0 ? 0 : add(1, f(n - 1)); }
12+
static_assert(f(256) == 256, ""); // Previously not a constant.
13+
static_assert(f(299) == 299, ""); // 300 nested calls of f: at the limit.
14+
15+
struct S {
16+
int v;
17+
constexpr S(int x) : v(x) {}
18+
};
19+
constexpr int g(int n) { return n == 0 ? 0 : S(1 + g(n - 1)).v; }
20+
static_assert(g(299) == 299, ""); // Previously not a constant.

0 commit comments

Comments
 (0)