Menu ▾ ▴

#1043 Optimiser treats escaping address of const object pessimistically

None
open
nobody
None
5
11 hours ago
2026-11-03
No

Consider the following example:

void foo(const char *i);

int bar()
{
  const char i = 9;
  foo(&i);
  return i;
}

https://godbolt.org/z/ve6jx84W9

Currently, SDCC does not make use of the fact that modification of i would be undefined behaviour in order to optimise the return value of bar to a constant; it only does so if i is constexpr instead of const. Is that distinction accidental or intentional?

My understanding is that i is an immutable object regardless of whether it is also an integer constant expression. I suspect that the behaviour of SDCC will become more optimal if the new rules for integer constant expressions that were accepted into C2Y are implemented, because const char i will be treated as like constexpr char i. But from the point of view that foo could modify i (and thereby invoke undefined behaviour) in either case, that does not seem logical.

The unwanted diagnostic message produced by the following example is probably related:

void baz(_Optional int *const *ppoi);
void qux(_Optional int *poi)
{
  // constrains poi to non-null on the fallthrough path
  if (!poi) return;
  _Optional int *const cpoi = poi; // must always point to poi

  *cpoi = 1;  // no warning is produced
  baz(&cpoi); // non-null constraint on poi is unaffected
  *cpoi = 2;  // unwanted warning is produced
}

https://godbolt.org/z/4K6Kj87G3
Unlike the version in (rejected) [bugs:#3958], the alternative version shown above does not rely on trusting baz not to cast away const-ness; instead, it relies on the fact that modifying cpoi has undefined behaviour.

Related

Bugs: #3958
Wiki: SDCC 4.7.0 Release

Discussion

  • Christopher Bazley

    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -11,7 +11,7 @@
     ```
     https://godbolt.org/z/ve6jx84W9
    
    -Currently, SDCC does not make use of the fact that modification of i would be undefined behaviour in order to optimise the return value of bar to a constant; it only does so if i is `constexpr` instead of `const`. Is that distinction accidental or intentional?
    +Currently, SDCC does not make use of the fact that modification of `i` would be undefined behaviour in order to optimise the return value of `bar` to a constant; it only does so if `i` is `constexpr` instead of `const`. Is that distinction accidental or intentional?
    
     My understanding is that `i` is an immutable object regardless of whether it is also an integer constant expression. I suspect that the behaviour of SDCC will become more optimal if the new rules for integer constant expressions that were accepted into C2Y are implemented, because `const char i` will be treated as like `constexpr char i`. But from the point of view that `foo` could modify `i` (and thereby invoke undefined behaviour) in either case, that does not seem logical.
    
    @@ -30,4 +30,4 @@
     }
     ```
     https://godbolt.org/z/4K6Kj87G3
    -Unlike the version in (rejected) [bugs:#3958], the alternative version shown above does not rely on trusting `baz` not to cast away const-ness; instead, it relies on the fact that modifying `cpoi` has undefined behaviour. 
    +Unlike the version in (rejected) [bugs:#3958], the alternative version shown above does not rely on trusting `baz` not to cast away `const`-ness; instead, it relies on the fact that modifying `cpoi` has undefined behaviour.
    
    • Group: -->
     
  • Christopher Bazley

    What I find more surprising is that neither Clang nor GCC optimise the return value of a function even if illicit modification of the object with the escaped address would have undefined behaviour:

    void foo(int *const *ppi);
    /* Modifying *ppi would have UB (because pi is const,
       not because *ppi is const) */
    
    int main(void)
    {
        int i = 10;
        int *const pi = &i; // pi can only point to i
        foo(&pi); // address of pi escapes
        return pi == &i; // guaranteed to be true
    }
    

    https://godbolt.org/z/EGqes58bh
    But that seemingly-excessive abundance of caution does not extend to non-pointer types, which seems inconsistent:

    void foo(const int *pi);
    /* Modifying *pi would have UB (because i is const,
       not because *pi is const) */
    
    int main(void)
    {
        const int i = 10;
        foo(&i); // address of i escapes
        return i == 10; // guaranteed to be true
    }
    

    https://godbolt.org/z/hMx3Wbeo4
    (I can only assume that it's something to do with i being treated as an integer constant expression, unlike pi and &i.)

     
  • Christopher Bazley

    • Description has changed:

    Diff:

    --- old
    +++ new
    @@ -24,9 +24,9 @@
       if (!poi) return;
       _Optional int *const cpoi = poi; // must always point to poi
    
    
    -  *cpoi = 1;    // no recommended diagnostic
    +  *cpoi = 1;  // no warning is produced
       baz(&cpoi); // non-null constraint on poi is unaffected
    -  *cpoi = 2;    // no recommended diagnostic
    +  *cpoi = 2;  // unwanted warning is produced
     }
     ```
     https://godbolt.org/z/4K6Kj87G3
    
     
  • Christopher Bazley

    Please consider the attached patch developed in discussion with Codex. The actual compiler change is small but there are many tests to ensure that the change is safe.

     
  • Christopher Bazley

    Replacing the candidate patch with one that uses __reentrant to avoid the usual test issues when targeting PDK13 and similar machines.

     
  • Christopher Bazley

    Further changes to the candidate patch to avoid assuming that redundant loads are not discarded by the optimiser in tests that warnings are not produced.

     
    • Philipp Klaus Krause

      Looks good to me.

       

Log in to post a comment.