Re: C aggregate passing (Rust kernel policy)

Martin Uecker
2025-02-27T06:57:04Z

Am Mittwoch, dem 26.02.2025 um 21:52 -0800 schrieb Linus Torvalds:
> On Wed, 26 Feb 2025 at 20:18, Martin Uecker <uecker@tugraz.at> wrote:
> >
> > This is not really related to "observable" but to visibility
> > of stores to other threads.
>
> Yes? What's the difference?

Observable is I/O and volatile accesses. These are things considered
observable from the outside of a process and the only things an
optimizer has to preserve.

Visibility is related to when stores are visible to other threads of
the same process. But this is just an internal concept to give
evaluation of expressions semantics in a multi-threaded
program when objects are accessed from different threads. But
the compiler is free to change any aspect of it, as  long as the
observable behavior stays the same.

In practice the difference is not so big for a traditional
optimizer that only has a limited local view and where
"another thread" is basically part of the "outside world".

I personally would have tried to unify this more, but this
was a long time before I got involved in this.

>
> Threading is a fundamental thing. It didn't *use* to be fundamental,
> and yes, languages and CPU architectures were designed without taking
> it into account.
>
> But a language that was designed this century, wouldn't you agree that
> threading is not something unusual or odd or should be an
> after-thought, and something as basic as "observable" should take it
> into account?
>
> Also note that "visibility of stores to other threads" also does mean
> that the loads in those other threads matter.

I agree that this could have been done better. This was bolted
on retrospectively and in a non-optimal way.
>
> That's why rematerializing loads is wrong - the value in memory may
> simply not be the same value any more, so a load that is
> rematerialized is a bug.

I assume that compromises were made very deliberately
to require only limited changes to compilers designed for
optimizing single-threaded code.   This could certainly be
reconsidered.

>
> > It sounds you want to see the semantics strengthened in case
> > of a data race from there being UB to having either the old
> > or new value being visible to another thread, where at some
> > point this could change but needs to be consistent for a
> > single access as expressed in the source code.
>
> Absolutely.
>
> And notice that in the non-UB case - ie when you can rely on locking
> or other uniqueness guarantees - you can generate better code.

A compiler would need to understand that certain objects are
only accessed when protected somehow. Currently this is
assumed for everything. If you want to strengthen semantics
for all regular memory accesses, but still allow more optimization
for certain objects, one would need to express this somehow,
e.g. that certain memory is protected by specific locks.

>
> So "safe rust" should generally not be impacted, and you can make the
> very true argument that safe rust can be optimized more aggressively
> and migth be faster than unsafe rust.
>
> And I think that should be seen as a feature, and as a basic tenet of
> safe vs unsafe. A compiler *should* be able to do better when it
> understands the code fully.
>
> > There would certainly be opposition if this fundamentally
> > diverges from C++ because no compiler framework will seriously
> > consider implementing a completely different memory model
> > for C (or for Rust) than for C++.
>
> Well, if the C++ peoiple end up working on some "safe C" model, I bet
> they'll face the same issues.

I assume they will enforce the use of safe high-level
interfaces and this will not affect the memory model.

>
> > I could also imagine that the problem here is that it is
> > actually very difficult for compilers to give the guarantess
> > you want, because they evolved from compilers
> > doing optimization for single threads and and one would
> > have to fix a lot of issues in the optimizers. So the
> > actually problem here might be that nobody wants to pay
> > for fixing the compilers.
>
> I actually suspect that most of the work has already been done in practice.
>
> As mentioned, some time ago I checked the whole issue of
> rematerializing loads, and at least gcc doesn't rematerialize loads
> (and I just double-checked: bad_for_rematerialization_p() returns true
> for mem-ops)
>
> I have this memory that people told me that clang similarly
>
> And the C standards committee already made widening stores invalid due
> to threading issues.

That widening stores are not allowed is a consequence
of the memory model when only using local optimization.
There are not explicitely forbidden, and an optimizer that
could see that it does not affect global observable behavior
could theoretically then widen a store where this is safe,
but in practice no compiler can do such things.

>
> Are there other issues? Sure. But remat of memory loads is at least
> one issue, and it's one that has been painful for the kernel - not
> because compilers do it, but because we *fear* compilers doing it so
> much.

I will talk to some compiler people.

Martin