From 6ee67be1414808515a3ab5744580935987174105 Mon Sep 17 00:00:00 2001 From: Mohit Mishra Date: Sat, 14 Nov 2026 22:33:07 +0530 Subject: [PATCH] Keep [[assume]] attribute when its argument call is inlined. [GH #393] --- src/lower_il.c | 6 +++-- .../gh-393-assume-inlined-call.txt | 15 +++++++++++ .../tests/changes/.GH393.rto/default.1.1.txt | 1 + tests/tests/changes/GH393.sft.cpp | 26 +++++++++++++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 src/pending_changes/gh-393-assume-inlined-call.txt create mode 100644 tests/tests/changes/.GH393.rto/default.1.1.txt create mode 100644 tests/tests/changes/GH393.sft.cpp diff --git a/src/lower_il.c b/src/lower_il.c index 832fee1d98..f2edf47203 100644 --- a/src/lower_il.c +++ b/src/lower_il.c @@ -20305,14 +20305,16 @@ Do IL lowering of the indicated statement and everything under it. switch (statement->kind) { case stmk_empty: /* If the statement includes an [[assume(...)]] attribute, lower - its operand. */ + its operand. The operand is not the statement's own expression, + so do not pass the statement (inlining a call would then turn + the statement into a block and drop the attribute). */ for (an_attribute *ap = statement->attributes; ap != NULL; ap = ap->next) { if (ap->kind == ak_assume) { an_expr_node *expr = expr_node_from_attribute_arg(ap->arguments); if (expr != NULL) { - lower_full_expr(expr, statement); + lower_full_expr(expr, (a_statement_ptr)NULL); } /* if */ } /* if */ } /* for */ diff --git a/src/pending_changes/gh-393-assume-inlined-call.txt b/src/pending_changes/gh-393-assume-inlined-call.txt new file mode 100644 index 0000000000..7cceebf8ff --- /dev/null +++ b/src/pending_changes/gh-393-assume-inlined-call.txt @@ -0,0 +1,15 @@ +[GH #393] +IL write/read check failure for [[assume]] with an inlinable call + +When lowering the argument of an [[assume]] attribute, the empty statement +that carries the attribute was passed to the lowering of the expression as +if the argument were the statement's own expression. If the argument was a +call that could be inlined (for example, a call to a constexpr function), +the inlining turned the statement into a block and the attribute was lost. +In a debug build, the IL write/read check then failed with "not all +expected entries were read". For example: + + constexpr bool g(bool b) { return b; } + constexpr int f(bool b) { [[assume(g(b))]]; return 1; } + +The argument is now lowered without a statement, so the attribute is kept. diff --git a/tests/tests/changes/.GH393.rto/default.1.1.txt b/tests/tests/changes/.GH393.rto/default.1.1.txt new file mode 100644 index 0000000000..4163d8bb9a --- /dev/null +++ b/tests/tests/changes/.GH393.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 --c++23 Test_name.c diff --git a/tests/tests/changes/GH393.sft.cpp b/tests/tests/changes/GH393.sft.cpp new file mode 100644 index 0000000000..f1c7d28d38 --- /dev/null +++ b/tests/tests/changes/GH393.sft.cpp @@ -0,0 +1,26 @@ +//type:fp +//options:--c++23 +//remark:[GH #393] [[assume]] with an inlinable call in its argument +// +// Lowering inlined a call in an [[assume]] argument by turning the empty +// statement that carries the attribute into a block, which lost the +// attribute. The IL write/read check then failed with "not all expected +// entries were read" for the scoped-expression entry of the attribute +// argument. + +int nc(); +constexpr bool g(bool b) { return b; } +constexpr bool g1() { return nc() > 0; } +constexpr bool g2(bool b) { if (!b) throw 1; return b; } +inline bool g3(bool b) { return b; } + +constexpr int f(bool b) { [[assume(g(b))]]; return 1; } +constexpr int f1() { [[assume(g1())]]; return 1; } +constexpr int f2(bool b) { [[assume(g2(b))]]; return 1; } +int f3(bool b) { [[assume(g3(b))]]; return 1; } +consteval int f4(bool b) { [[assume(g(b))]]; return 1; } +constexpr int f5(bool b) { [[assume(g(b))]]; [[assume(g(b))]]; return 1; } + +int use(bool b) { + return f(b) + f1() + f2(b) + f3(b) + f4(true) + f5(b); +}