On 2025-01-20 02:55, Jₑₙₛ Gustedt wrote:

Ah, so this is indeed quite another model, basically seeing a pointer
as a bit pattern that is the same.

Yes. It'd help if the standard made it clear what it considers to be "the same" when it comes to [[reproducible]] and [[unsequenced]], using wording similar to what I sent you earlier. This is unclear right now.


I have difficulties of understanding the usefullness of such an
interface

You're better off than I, as I'm still having difficulties knowing even what unsequenced and reproducible mean, much less what they're useful for. For example, I don't understand why the 'abs' and 'strcmp' functions are not declared to be [[unsequenced]], as they seem to satisfy the intended criteria for unsequenced functions.



By "extension", I assume you mean that [[unsequenced]] is intended to
be looser than __attribute__((const)). That is, every const function
is unsequenced, but the reverse is not true. This is what Bruno said
in the above quote.

Yes that's my idea.

Then I'm more confused than ever, unfortunately.

Here's another illustration of my confusion. 6.7.13.8.3 EXAMPLE 2 says that given this definition:

  typedef struct toto toto;
  toto const *toto_zero(void) [[unsequenced]];

"a single call can be executed during thread startup and the return value p and the value of the object *p of type toto const can be cached." This means a compiler can optimize code like this:

   toto const x = *toto_zero();
   change_state();
   toto y = *toto_zero();

as if it were this:

   toto const x = *toto_zero();
   change_state();
   toto y = x;

That is, EXAMPLE 2 means an unsequenced function not only can examine storage addressed by pointers passed to it: it also guarantees that when it returns a pointer, storage addressed by that returned pointer always has contents derivable from the function's arguments (including the storage addressed by those arguments). In particular tot_zero's [[unsequenced]] guarantees that change_state does not modify the storage addressed by the pointer returned by toto_zero.

But this means that, contrary to stated intent, __attribute__((const)) does not imply [[unsequenced]]. For example:

   toto x;
   toto const *toto1(void) { return &x; }
   void change_state() { memset(x, 1, sizeof x); }

toto1 is a const function, but it's not unsequenced because toto1 returns a pointer to storage that change_state modifies.

In other words, [[unsequenced]] allows some compiler optimizations that __attribute__((const)) does not - contrary to stated intent.


I don't understand why
the output of 'assert' doesn't count as a side effect,

because the properties and applied optimizations at the call-site
should not change if somebody includes `<assert.h>` with a different
value for `NDEBUG` at a completely unrelated point in the code

I don't see why not. Suppose the program looks like this

  [[unsequenced]] int foo(int);
  int main(int argc, char **argv)
  {
    return argc == 1 ? 0 : foo (argc);
  }

  #undef NDEBUG
  #include <assert.h>
  int foo(int n)
  {
    assert (n != 1);
    return n;
  }

(foo could be defined in a separate compilation unit; that doesn't change the analysis.)

If a compiler were allowed to move the call foo(argc) to the start of main (and why not? the function is unsequenced...), that would change the meaning of the program.

infloops are forbidden in const functions to allow those sorts of optimizations. If infloops are also forbidden in unsequenced functions, but 'assert's are allowed, a caller still can't do those optimizations.

This is another reason why it's important to state clearly the *motivation* for [[unsequenced]] and and [[reproducible]]. The standard doesn't do that, and the proposed change doesn't help. Without a clearer explanation of why these attributes are present I fear we'll continue to have misunderstandings and glitches.



I think the idea is that you cannot move
a function that never returns accross side effects because then a side
effect (such as a call to `printf`) could become invisible that would
otherwise be visible. (or the other way arround?)

That's the basic idea, yes; my example above does that.


the idea is that all of the things that are listed there are allowed
to happen (and are side effects in the strict sense of the standard),
but optimizations should not be concerned about them.  For example the
possibility that a signal kicks in, should not have an influence on
code movement for these function calls.

Your phrasing above is clearer than what's in n3424 and I urge that wording continue to be made clearer along these lines. We're not there yet, though, as per my previous comments.


Let's not underestimate the difficulty of making these concepts clear.

Oh, no, indeed, I don't think they are. The text that now is in C23 is
probably one of the most delicate texts I ever wrote in my whole
career.

Oh, this means you weren't around when the sequence-point language got added to the C standard in the 1980s. Although C89 was verrrrry delicately worded nobody knew what the wording actually meant, not even its authors. I would ask simple questions like "can f's and g's instructions be interleaved when executing f(x)+g(y)?" and nobody could justify their answer from the wording of the standard. For all I know some of those problems still lurk in C23 (I gave up worrying about it).

My comments this week are in the hope that things have gotten better in the standardization process.


Reply via email to