https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127289
Bug ID: 127289
Summary: [c++26][contracts] -fcontract-checks-outlined loses a
predicate's mutations of by-value parameters and of
the postcondition result
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: berne at notadragon dot com
Target Milestone: ---
Created attachment 65535
--> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65535&action=edit
Exits 0 as compiled, 19 under -fcontract-checks-outlined; the status is a
per-entity bitmask
Under -fcontract-checks-outlined a contract predicate that mutates a
by-value parameter writes to a copy, so neither the function body nor the
caller sees the mutation. The same happens to a postcondition's result
binding. Without the flag the mutation is observed. So the observable
behaviour of a conforming program depends on a codegen flag.
Exactly the entities passed by value to the outlined check are lost:
entity mutated in the predicate inlined outlined
a global observed observed
a by-value parameter observed lost
a reference parameter observed observed
the result binding in a post observed lost
```
int g = 0;
int by_value_parm(int n) pre(const_cast<int&>(n)++)
{
return n; // required: 3. Outlined gives 2.
}
int result_binding() post(r : const_cast<int&>(r)++)
{
return 1; // caller sees 2 inlined, 1 outlined.
}
int ref_parm(int& n) pre(const_cast<int&>(n)++)
{
return n;
}
void global_mut() pre(const_cast<int&>(g)++)
{
}
// The crux: one predicate that mutates both shared state and the by-value
// parameter. Elision would drop both; this drops only one.
int g_log = 0;
bool bump(int& n)
{
++n;
g_log = n; // shared state: propagates even when outlined
return true;
}
int partial_application(int n) pre(bump(const_cast<int&>(n)))
{
return n * 100 + g_log; // faithful 303, fully elided 200, outlined 203
}
int main()
{
int bad = 0;
int r = partial_application(2);
if (r != 303 && r != 200)
bad |= 16; // 203: neither faithful nor elided
if (by_value_parm(2) != 3)
bad |= 1; // fires only when outlined
if (result_binding() != 2)
bad |= 2; // fires only when outlined
int v = 2;
ref_parm(v);
if (v != 3)
bad |= 4; // never fires
g = 3;
global_mut();
if (g != 4)
bad |= 8; // never fires
return bad;
}
```
```
$ ./gcc-16.2.0/bin/g++ -std=c++26 \
outlined-checks-lose-by-value-mutations.cpp -lstdc++exp -o t
$ ./t; echo $?
0
$ ./gcc-16.2.0/bin/g++ -std=c++26 -fcontract-checks-outlined \
outlined-checks-lose-by-value-mutations.cpp -lstdc++exp -o t
$ ./t; echo $?
19
```
This is not a behavior allowed by predicate elision, as not all
side effects of the predicate are being elided. In the above code,
a result of 200 (no evaluations) or 303 (both side effects) would be
valid, but with -fcontract-checks-outlined the result is 203.
DISCOVERY
Found by working through the interaction of the evaluation-semantic rules
with GCC's own -fcontract-checks-outlined codegen mode, asking for each kind
of entity a predicate can mutate whether the two lowerings agree. Four of
the five agree; the two passed by value do not.
ANALYSIS
build_contract_condition_function (gcc/cp/contracts.cc) builds the outlined
checking function by copy_decl-ing each of the original's PARM_DECLs with
their types unchanged, so a by-value int n is still by-value in the checking
function. For postconditions the result is appended as one more by-value
parameter of the original return type.
VERSIONS -- all on x86_64-linux-gnu
source version inlined outlined
compiler-explorer 16.1.0 0 19
compiler-explorer 16.2.0 0 19
compiler-explorer 17.0.0 20260909, 919c0d16c91 0 19
local build -g 17.0.0 20260909, 7dab38c9d71 0 19
```
$ ./gcc-16.2.0/bin/g++ -v
Using built-in specs.
COLLECT_GCC=./gcc-16.2.0/bin/g++
COLLECT_LTO_WRAPPER=/home/jberne4/repos/compilers/gcc-16.2.0/bin/../libexec/gcc/x86_64-linux-gnu/16.2.0/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../gcc-16.2.0/configure
--prefix=/opt/compiler-explorer/gcc-build/staging --build=x86_64-linux-gnu
--host=x86_64-linux-gnu --target=x86_64-linux-gnu --disable-bootstrap
--enable-multiarch --with-abi=m64 --with-multilib-list=m32,m64,mx32
--enable-multilib --enable-clocale=gnu
--enable-languages=c,c++,fortran,ada,objc,obj-c++,go,d,m2,rust,cobol,algol68
--enable-ld=yes --enable-gold=yes --enable-libstdcxx-time=yes
--enable-linker-build-id --enable-lto --enable-plugins --enable-threads=posix
--with-pkgversion=Compiler-Explorer-Build-gcc--binutils-2.44
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 16.2.0 (Compiler-Explorer-Build-gcc--binutils-2.44)
```