Open Subsentient opened 3 years ago
There is some relevant prior discussion in this rust internals thread: https://internals.rust-lang.org/t/add-rustc-flag-to-disable-mutable-no-aliasing-optimizations/14404
I think calling this -fno-strict-aliasing
is just confusing. According to gcc docs:
In particular, an object of one type is assumed never to reside at the same address as an object of a different type, unless the types are almost the same.
Rust has no type-based strict aliasing like this, as far as I know in the rust abstract machine memory is untyped and can freely alias regardless of the types.
I think what you want is something that turns explicitly unique references (i.e &mut
) into non unique references, which would be something like -fcompletely-disable-restrict
for C. I am not familiar with any flag like that for C/C++ compilers
Why would you want something like -fno-strict-aliasing
in Rust? Proper (i.e. not utterly broken) Rust code should not depend on it. If GCC adds an option to disable certain optimizations, that won't make code that does illegal things proper Rust.
Hi @Subsentient thanks for your question and support this means a lot to me personally. One of the key things about this project is that we wish to provide an experience for compilation of rust-code to be just like any other gcc front-end. This means flags like -fno-strict-aliasing which is a common gcc flag (not front-end specific) you get for free. All of those options you see used will be available, something we noticed recently with @CohenArthur cargo-gccrs wrapper is that rustc automatically produces PIE [1] executable (not sure why) but this does not impact the front-end because it is up to the cargo wrapper or how ever you invoke gccrs to get the result you desire. Just like with C/C++,Fortran/....
TLDR: I never want to put any barrier on how people invoke their compiler, its up to the programmer how you wish to compile your code in my opinion. All common gcc options will be exposed so it is up to you how you invoke the compiler. This also means you should be sure to know why you are enabling an option or disabling an option just like with C/C++, we are all consenting adults in the room lol.
I wonder does @tschwinge or @dkm have any opinion here?
This means flags like -fno-strict-aliasing which is a common gcc flag (not front-end specific) you get for free.
Strict aliasing is incompatible with rust's memory model, so -fno-strict-aliasing
must be used by default with maybe an error for explicit -fstrict-aliasing
. Rust's memory model is untyped, so pointers of different types can point to the same memory just fine, which is exactly what strict aliasing disallows in the general case. Rust instead has roughly the rule that mutable references alias with nothing and immutable references never alias with references or pointers that are written through. This allows for more optimizations than strict aliasing, while also being easier to explain and less likely to accidentally violate. AFAIK gcc already has support for this because of the fortran frontend and has much less buggy support for it than LLVM.
Agreed with a lot of what is said before. Rust doesn't have TBAA, and so doesn't have strict aliasing, and so that -fstrict-aliasing
is effectively not valid.
If you read the attached thread, what this user actually wants is a way to alias &mut T
s. This is a language semantics issue, as said in that thread by @joshtriplett (language team co-lead) @ralfjung (whose PhD work was formalizing Rust's semantics) and many others who do work on the language itself. @Subsentient has stated elsewhere (https://news.ycombinator.com/item?id=28366365 is the most recent) that they are interested in non-rustc implementations of Rust specifically to try and push alternative language semantics, which is one of the community's greatest fears about additional implementations. You all have stated in the past that you're committed to keeping compatibility with rustc, and that's great. I would beg you to please keep that in mind at all times. "my code compiles on gcc but not on rustc" is harmful to both projects and Rust at large.
I am still a big fan of this project overall, keep up all the great work. I did want to make sure that you all have the proper context here though.
If you read the attached thread, what this user actually wants is a way to alias &mut Ts.
Honestly I think mutable references should be renamed to unique references and immutable references to shared references. That will hopefully bias people towards wanting mutation through shared references and then find the solution to their problems through the means of interior mutability with Cell
or RefCell
rather than wanting shared mutable references and making proposals to allow them on i.r-l.o or elsewhere.
I have complicated feelings on that topic, but it is very offtopic for this bug. :)
I think the solution to problems with Stacked Borrows is to improve Stacked Borrows, and not to fork the language. IMO, a compiler that allows programmers to do any of the things that are UB in Rust should not call itself a Rust compiler -- it's effectively a different language at that point, with subtle interop issues.
Keep in mind that Stacked Borrows is the first detailed proposal for a Rust aliasing model, not the last. It has several known problems, and at least for some of those there are ideas for how to improve the situation. More feedback (ideally as concrete, precise, and technical as possible) is very welcome!
I really appreciate the feedback @steveklabnik @bjorn3 @RalfJung. It is not my intention to deviate from rust behaviour but we can add default options into our cargo wrapper by @CohenArthur which will help guide how the gcc compiler should be invoked by default. Gccrs still has alot of work ahead of us but to me the important thing for this compiler is the tight integration we get with gcc such as thse common compiler options.
Overall we still have a long way to go before thse options have much impact right now and we will be making sure we are as rigorous as possible in our testing efforts for compatibility. We even have plans to investigate a common rust compiler testing framework to help automate finding any possible gaps in compatibility.
I hope this comment makes sense at least.
It is not my intention to deviate from rust behaviour but we can add default options into our cargo wrapper by @CohenArthur which will help guide how the gcc compiler should be invoked by default.
How does the fortran frontend handle disabling strict aliasing?
To emphasize on what Philbert has said:
You all have stated in the past that you're committed to keeping compatibility with rustc, and that's great. I would beg you to please keep that in mind at all times.
The topic of adding "new" features to gccrs that are not supported by rustc has been brought up multiple times. The overwhelming feeling of the people involved in the project was that this was far from the goal. There are no plans to push any new GNU features, add some sort of GNU attributes, etc etc etc. Philip keeps telling us that the goal would be to stick to the rustc's core-team processes and to try and work together as much as possible. As he said, cargo-gccrs is about bringing gccrs closer to rustc, and allow gccrs to evolve towards what rustc is doing.
"my code compiles on gcc but not on rustc" is harmful to both projects and Rust at large.
I completely agree with this. I think this is also why I wanted to work on cargo-gccrs and why Philip was so enthusiastic about it. Even though it's a simple wrapper, it can help in "bridging the gap" between both gccrs and rustc. If you look at the project, it is mainly about understanding rustc arguments and trying to adapt them to gccrs. Likewise, with this issue, the goal is once again to make gccrs as compatible with regular cargo operations as possible. We adapt gccrs so that it plays nice with the existing rust ecosystem, not the other way around.
We were strongly against adding any sort of gccrs specific feature to the cargo file, or adding some sort of gccflags
similar to
[build]
rustflags = ["-D", "warnings"]
# gccflags = ["-Werror"] is a no-no
The only thing available to users of cargo gccrs
is to pass specific gcc options via an environment variable, should they chose to do so (This is also necessary since we have to invoke ar
manually and people might like to configure this as well). This might be useful if you decide to have a different workflow for gccrs and rustc, I can't think of any cases but I'm sure they might exist. And again, this is not about adding anything else to "our" version of rust.
I strongly belive that gccrs NEEDS to be able to understand the code that rustc understands.
To give another example: rustc generates Position Independant executables. gccrs allows the generation of non-PIE executables. However, we noticed this, worked through the issue, and added specific default options to gccrs in order to reproduce the rustc behavior.
I think that if the memory model of rust does not allow -fstrict-aliasing
, then it should be disabled (at least for now, as @RalfJung says, Stacked Borrows will be improved/need to be improved).
I did want to make sure that you all have the proper context here though.
I feel that my argument here and line of thinking is being misrepresented a bit. What I want most but accept I probably won't get, is a way to disable aliasing optimizations so that unsafe code can alias &mut references, if the code that utilizes those references is smart enough not to assume they are exclusive.
I want this because of things like recursive mutexes, self-referential structures with mutable parent references, etc. Things that are currently not possible with Rust's aliasing model. What I do not want and would actively fight against, is breaking the borrow checker to allow multiple &mut references. What I'd prefer most of all is for an optional switch which enables the borrow checker to be solely responsible for enforcing exclusivity, not aliasing optimizations. If you think about it for a minute, we already have that to a point, if you're willing to risk undefined behavior.
What I've decided would be acceptable is a compiler flag that allows me to get this behavior, but which is still considered UB, which upon further thought is probably the best way to go about this. For one, there is lots of code that relies on the exclusivity of &mut, so it would always be dangerous to pass those references to different crates.
I would be satisfied with pointers, but unfortunately there are many traits and structures in Rust which require &mut, and do not accept shared references, for example, the Drop trait.
Thanks!
@Subsentient As was mentioned, what you want is separate from -fno-strict-aliasing
(Which is required by rust code). You have just described the problem with this hypothetical do-not-optimize-aliasing-unique-references
flag:
I want this because of things like recursive mutexes, self-referential structures with mutable parent references, etc. Things that are currently not possible with Rust's aliasing model.
This would allow something that is currently UB, and change rust's model. This means that for a rust compiler, your code would be UB and invalid, and for a compiler with this flag, it would not be. That kind of incompatibility is what people want to avoid. I think this kind of discussion in gccrs is problematic because it results in the
"my code compiles on gcc but not on rustc" is harmful to both projects and Rust at large.
situation described earlier. I think you might be able to have a more constructive discussion in the forums or the unsafe code guidelines with concrete examples of the problems you run into trying to use raw pointers. For example, with Drop as you mentioned (I'm not sure exactly what problems you had with it, but #[may_dangle]
might be useful, there is some discussion around that here and here)
Well done @CohenArthur for the detailed answer. The way I see the cargo wrapper is a method to provide the "correct rust experience" but if a user chooses to invoke the compiler directly they will have access to all of these options which includes disabling or enabling strict aliasing https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
I need to get to my laptop to reply to your question properly @bjorn3
@philberty @CohenArthur Thank you for your considered viewpoints on this. With my language, library, and cargo team hats on, if gccrs is interested in remaining fully Rust-compatible rather than diverging, that's great news, and I think we'd be much more enthusiastic about having conversations about Rust features and other work that would help with that. It'd be nice to have that gccrs policy documented somewhere for reference. I very much appreciate that the folks working on gccrs don't want to create the situation where code compiles on one Rust compiler and not on another.
To echo what others have said: Having more than one &mut
reference to the same location, whether in safe or unsafe code, would be undefined behavior that breaks fundamental semantics of Rust. Such code would break with rustc, and with gccrs without this flag. This would thus lead to crates containing non-standard Rust code which only compiles with gccrs, and only when passing this specific flag, and which breaks with subtle undefined behavior (that the compiler may not be able to detect).
"multiple &mut
references to the same location", even in unsafe code, is not something that -fno-strict-aliasing
should allow. I think that GCC's semantic of "strict aliasing" is something that, if it applies at all, would only apply to raw (C-style) pointers like *const
and *mut
, not references. C with and without strict aliasing seems effectively like two slightly different languages, one with more optimization possibilities and one allowing more code; there's C code that is only valid in "C without strict aliasing". We don't want Rust to be two slightly different languages. I personally would suggest that we should establish which of the two behaviors matches the expected semantics of Rust (which I think is -fno-strict-aliasing
), make that the behavior for Rust code regardless of command-line options, and then gccrs should treat the other compiler option as a no-op for Rust code (while potentially affecting C code that's part of the same compilation).
As an aside, the fact that Drop
takes a &mut self
has come up as a problem before. That's something we can and should look into improving. We should also look at any other case that wants a &mut
where it perhaps shouldn't.
There are already multiple mechanisms to support multiple pointers to the same locations without UB, including safe mechanisms like Arc
, and unsafe mechanisms like *mut
or UnsafeCell
.
FWIW, in general Rust folks do not like using "UB as a weapon"; we try to avoid cases where UB is hurting code that people want to write and which logically ought to work. Even folks who work on language semantics try to handle UB-related issues with care and understanding towards systems developers who often need to push those boundaries. I personally tend to push for defining more things that people want to do. Something like multiple &mut
references to the same location, however, breaks fundamental assumptions that Rust code is allowed to make; if anything, that shouldn't be undefined, it should be defined as invalid.
@joshtriplett
It'd be nice to have that gccrs policy documented somewhere for reference
It has been in their FAQ for a very long time: https://github.com/Rust-GCC/gccrs/wiki/Frequently-Asked-Questions#what-is-the-plan-for-inconsistencies-in-behaviour
@steveklabnik I've seen that, but the "(at this stage of the project)" disclaimer suggests a different policy. It'd be nice to have confirmation that compatibility is the long-term policy plan, as well.
In any case, further depth on that would be off-topic for this issue. For this issue, it's sufficient to note that allowing multiple &mut
references to the same location would introduce such an incompatibility, making code compile on one compiler and not on another.
I want [...] Things that are currently not possible with Rust [...].
^intentionally only partially quoting @Subsentient to make my point:
I think it is important to note, that the rust-gcc project is far from the right place to request something like this. If someone wants new features in Rust, they will have to go through the proper channels (i.e. IRLO, MCP, RFC, ...). gccrs
is not and will not be a shortcut to get features into the Rust language. Maybe @joshtriplett is right and this is not clear enough yet.
I'm not a language lawyer, but it seems that this question is mixing two or three different issues.
First, it's not clear that -fno-strict-aliasing
really is the appropriate option for the behavior that Subsentient is requesting. I'm not going to address that.
Second, GCC has multiple options that explicitly cause the compiler to not conform to the relevant language standard (C, C++, Fortran, etc.). When the user invokes those options, he or she should know and understand that the program may build and run but no longer conforms to the language standard. I am not suggesting that GCC Rust introduce -std=gnu22
variants to deviate from the Rust language, but there are options that will change behavior.
Third, I hope that the Rust community will scale back its instincts to control all Rust developers. These types of variations occurs when a language grows and matures and achieves multiple implementations. There are C and C++ and Fortran and Go applications that utilize special features in GCC or Clang or Golang or HP C or IBM XL C or Digital C or other compilers. One can make it clear that those applications and features are not portable and do not conform to the language, but innovations occurs where people are allowed the freedom to experiment. One can provide implementation-specific features without the intention to strip users away or split the user community or lock users into an implementation. GCC has some command line options and features and behaviors that should not be the default for GCC Rust and do not conform to the language standard, one should not preclude users from being able to access them.
but innovations occurs where people are allowed the freedom to experiment
Experimentation in rustc happens behind feature flags that can only be enabled on nightly. This has been a deliberate choice to prevent a situation whereby it is practically infeasible to change or remove an experimental feature that had deficiencies due to people depending on the current behavior. See the web for an example of the exact problem this is meant to avoid. I don't know for sure, but I suspect that the same problem also happens with GCC and Clang experimental features, albeit maybe in lesser quantity. I think it is very important that if (and I don't say it should) gccrs were to introduce experimental rust features, they would not be accessible on stable builds.
Third, I hope that the Rust community will scale back its instincts to control all Rust developers. These types of variations occurs when a language grows and matures and achieves multiple implementations.
I think it is actively harmful to have language variations. Especially when they don't cause code to stop compiling when using a different variantion, like changing the aliasing model. Experimentation on non-stable compiler versions is fine, but when actually stabilizing said language features, this will cause a split in the ecosystem where some crates only work on one compiler and others only work on another compiler, for example making it impossible to combine those crates. Or what about if you need one compiler because of architecture support, but would like to use a crate that only supports the other compiler?
On the other hand I think it would somewhat be less of a problem for features that are not part of the language like say profilers, sanitizers or new optimization methods (think of lto or pgo when they were not yet commonly supported across compilers) that don't change the language semantics and are not rust specific. Crates can't depend on them, only user programs, so it is much less likely to result in an ecosystem split.
@bjorn3 That's exactly the distinction I would make, as well. I think it's perfectly reasonable to support any and all GCC options to gccrs, even those that might have user-visible behavior changes. (If someone really wants to use -ffast-math
or similar, more power to them.) The only thing I'd be concerned about is changes to the the language semantics in such a way that some code compiles on one stable compiler and not on another stable compiler. Allowing multiple &mut
references to the same location would fall in that category.
The definitions of "language variations" are ambiguous; for example, does this mean allowing users to use compiler flags? Or do you mean language features outside of RFC?
Changing the aliasing model is not our intention, but if we actively go out of our way to disable common GCC flags and stop programmers from having access to enable/disable strict-aliasing looks pretty drastic to me. So let us encourage people with documentation to choose the appropriate flags to be as compatible as possible, and if they choose to ignore this, it is up to them. Of course, as we have pointed out several times, the other part is that our cargo-gccrs wrapper will default to the appropriate compatibility options from the get-go.
We have been trying to clarify that we do not intend to allow language changes outside of Rustc processes. But let us try and define what that means. To me, there are language features like const-generics or GAT's, for example, features like incremental compilation, CFI, and there are attributes like allow-dead-code or feature gates.
Language features like const-generics are not something we will ever change. This is just core Rust it is not possible to change how path-resolution behaves, for example. I do not have any interest in designing new language features, and the implications are too great. However, I would like to hear how the implementation of new features like GAT's is going. Note I pointed out in my latest status report about QualifiedPathInTypes report an ambiguous type error which makes implementing them confusing. [1]
Gccrs does not support compiler features like incremental compilation. This does not make us incompatible with rust, but the legacy name mangling scheme in rustc uses a 128bit hash of some meta-data at the end of every symbol, which makes it likely not possible for us to replicate this hash. The new V0 scheme will likely solve this issue, though.
Also, one of the key reasons for implementing gccrs for me was always the tight integration with the rest of GCC. This means providing the same compilation experience as the other front-ends in the toolchain; this is critical and includes allowing people to use GCC plugins that can manipulate the GCC IR's at any level.
Attributes are an interesting one because attributes are used as feature gates or as flags to turn lints on or off and are not necessarily a language change in my opinion, since the grammar has not changed. But these set flags within the compiler for specific checks, which change regularly when things are stabilised or removed. So it seems fair to me that within reason allowing some attributes could be considered such as a new lint which does not exist within rustc. I don't believe it would be in anyone's interest for gccrs to be unable to innovate unless we get the ok from Rustc.
Naturally when two things exist that are different, some will choose one and some the other, you could say having GCC plugins present in the kernel was unfair to Clang which didn't have that functionality at the time. But then the flip side is true now of Clang CFI getting merged upstream and there being no upstream GCC equivalent.
I have always considered gccrs as in addition to Rustc, never against. I do take this question of compatibility seriously from reading pages of comments over the last 6 months about this project. This is the first time people are discussing it with us, and I appreciate that it has happened. We can do better than past mistakes in compatibility; let us work together, not against one another.
[1] https://github.com/Rust-GCC/Reporting/blob/main/2021-08-monthly-report.org
Gccrs does not support compiler features like incremental compilation.
Naturally when two things exist that are different, some will choose one and some the other, you could say having GCC plugins present in the kernel was unfair to Clang which didn't have that functionality at the time. But then the flip side is true now of Clang CFI getting merged upstream and there being no upstream GCC equivalent.
These are examples of things that I don't consider to be language features. Libraries are completely oblivious to them being used or not unless they already contain UB.
Attributes are an interesting one because attributes are used as feature gates or as flags to turn lints on or off and are not necessarily a language change in my opinion, since the grammar has not changed.
I would say that the language is equally about semantics as it is about syntax. Attributes don't change the syntax, but they can change semantics. For example #[repr]
makes a specific layout guaranteed in a way that you can depend on the type to always have this layout (assuming that the layout of all subfields is guaranteed). Other attributes like #[deny]
to make a lint an error don't change semantics. Libraries may fail to compile when too much lints are enabled, but if it compiles the behavior of the library is unchanged. Disabling lints also doesn't change behavior either. In fact cargo disables all lints by default for dependencies.
I think a decent test for if a feature could be considered a language feature would be to take UB free library code not using implementation details that works with a compiler supporting this feature and then compile it with a compiler not supporting this feature using the default compiler flags (or maybe with disabling all lints). If it still compiles and runs fine, it is likely not a language feature. If it doesn't compile or run it likely is a language feature. This probably classifies some things as language feature that are arguably not, but I think it is a decent test to start discussion.
Changing the aliasing model is not our intention, but if we actively go out of our way to disable common GCC flags and stop programmers from having access to enable/disable strict-aliasing looks pretty drastic to me. So let us encourage people with documentation to choose the appropriate flags to be as compatible as possible, and if they choose to ignore this, it is up to them. Of course, as we have pointed out several times, the other part is that our cargo-gccrs wrapper will default to the appropriate compatibility options from the get-go.
I like your attitude very much, @philberty . It mirrors my own position. I am very strongly of the conviction that Rust's job should be to help you avoid mistakes, not force your hand in a specific direction. To me, Rust's biggest value comes from safety by default with little to no performance cost. I doubt I've mentioned this before, but it is a serious sore spot for me personally when any organization decides what can and cannot be done with a piece of software or hardware. It enrages me so much that I refused to own a smartphone for years just on principle, and now I use a PinePhone.
I'm genuinely saddened to see the way that the Rust community has reacted to my request. I truly don't understand how it can be viewed as a language breaking change when it is still officially UB and not enabled by default. Someone on Hacker News even said it was unethical and probably illegal to have a compiler that offered flags rustc did not, something I found quite offensive. What I consider unethical is attempting to force programmers down a specific path by not providing features provided for other GCC frontends because someone at the Rust Foundation decided it was bad. In truth, I don't think anyone has the moral right, even if they have the legal right, to try and control the behavior of other programmers.
At the end of the day, there is no good rationalization for not including such a ubiquitous flag other than that the developers fear that someone might actually use it. I submit that they do not have the right to stop anyone from using it.
People here keep saying that the code won't compile on rustc, which technically isn't true. Code that breaks Rust's strict stacked borrows model does compile, and in most cases it even works as intended. But, because it's UB, you can't trust it to do what it says it will do, so I don't use it. -fno-strict-aliasing always did violate the C and C++ standards, but as it was an opt-in compiler option, nobody really cared. In my decade of C and C++ experience, I've never once had an issue with strict aliasing turned off, even interfacing with third party libraries. I have, however, had serious headaches with strict aliasing enabled.
See, what I want isn't for Rust to declare aliasing &muts as defined behavior. I don't need Rust to condone it. Hell, Rust can even condemn it. All I want, is that I can pass a flag to a Rust compiler to forcibly disable all bothersome aliasing optimizations, and then use my fucked up, demented pointer magic with impunity. If Rust calls it UB, fine. But if the observed behavior is correct, that's enough for me. And best of all, if GCC supports Rust (GCC strongly being my preferred toolchain) and allows this feature, I'd probably switch to gccrs as soon as it's usable.
There are definitely programmers who should never mess with settings like these. There are programmers who should never be trusted with unsafe. But, I don't believe I'm one of those, because I have a very good track record of getting away with black magic pointer fuckery with zero consequences. Should I be using these types of things? Hey, maybe not, but that's my choice, and I genuinely believe that I could write more elegant, even if non-conforming, Rust code with such an option. Recursive mutexes, easy self-referential data structures in safe code, the possibilities are endless. I've definitely written better, more performant C and C++ code as a result of being able to disable aliasing optimizations where necessary.
Again, let me reiterate. I'm not trying to get Rust to declare &mut defined behavior, even with a compiler flag. I only want the flag to give me the behavior I expect, nothing more. I think having the aliasing optimizations on by default is reasonable. I only want a method to opt-out of the optimization passes that break my weird code.
I'm sure I've made some enemies in the Rust community with this whole thing, and for that I'm truly sorry. I just ask that you try and understand my perspective.
At the end of the day, there is no good rationalization for not including such a ubiquitous flag other than that the developers fear that someone might actually use it.
@Subsentient What do you mean "ubiquitous"? As has been mentioned multiple times, strict aliasing is a separate issue from the uniqueness of &mut. I don't believe there exists an analogous flag that is "ubiquitous"
Third, I hope that the Rust community will scale back its instincts to control all Rust developers. These types of variations occurs when a language grows and matures and achieves multiple implementations. There are C and C++ and Fortran and Go applications that utilize special features in GCC or Clang or Golang or HP C or IBM XL C or Digital C or other compilers. One can make it clear that those applications and features are not portable and do not conform to the language, but innovations occurs where people are allowed the freedom to experiment. One can provide implementation-specific features without the intention to strip users away or split the user community or lock users into an implementation. GCC has some command line options and features and behaviors that should not be the default for GCC Rust and do not conform to the language standard, one should not preclude users from being able to access them.
I just wanted to add that it has really brightened my day to see that I'm not totally insane (not totally lol) and that there are others who perceive this the same way that I do.
Is LuaJIT not Lua? Is PyPy not Python? Differing implementations, even with slightly different language features, can still be comfortably grouped in most people's minds as the same language, with little details differing between them. I don't believe what I'm asking for even qualifies as a language feature, more of a codegen feature.
If you're afraid of it breaking cargo, then that's why you put it in RUSTFLAGS. If you're afraid of logic assuming &mut is exclusive breaking, well, that's a bug in the user of this compiler flag. It's sound when used correctly, but this is a good example of not using it correctly. You shouldn't write to such a reference when sending it to unknown code. There is no safe way to write and read at the same time to the same location, in any language, but whether or not unused but still extant &mut s to the same location can produce subtle aliasing bugs depends entirely on a codegen option. To me that's a race condition like any other, equivalent to filling a struct with atomics and no locks that make sure there is a correct happens-before relationship, even in current vanilla safe Rust. And yes, with async it gets harder to prove correctness, but I don't use async much anyways, so that's not an issue for me, and I strongly suspect that's the case for many others.
At the end of the day, there is no good rationalization for not including such a ubiquitous flag other than that the developers fear that someone might actually use it.
@Subsentient What do you mean "ubiquitous"? As has been mentioned multiple times, strict aliasing is a separate issue from the uniqueness of &mut. I don't believe there exists an analogous flag that is "ubiquitous"
Both are codegen options dealing with aliasing optimizations to make what would otherwise be broken code work as expected, even if technically UB, and even though the exact optimizations performed differ.
@philberty I should add that if I'm permitted to help on gccrs and given assurances that quality work will not be rejected, I'll add the compiler flag myself, along with the help comment saying it's still technically UB. I know C++ quite well so there should be no issues there.
What I've decided would be acceptable is a compiler flag that allows me to get this behavior, but which is still considered UB, which upon further thought is probably the best way to go about this. For one, there is lots of code that relies on the exclusivity of &mut, so it would always be dangerous to pass those references to different crates.
Either it is UB, and then you can't make any assumptions about what happens, or it's not UB and then that flag would actually switch the compiler to a different, Rust-adjacent programming language. I know you had this discussion in the past with other people, but I can just repeat what they said: what you are asking for is a fork of Rust. I am well aware that C/C++ compilers offer flags like this; the C/C++ ecosystem is much less worried about language forks and much less principled about undefined behavior, so the fact that they do it does not mean Rust should do it. (Note that in C/C++, these flags do change what is UB, at least for any useful definition of UB.)
I feel very strongly that Rust should never ever do things like "this is UB but we still guarantee X about it". This totally subverts what UB is all about, and it teaches people a wrong mental model of what UB is for. (We currently have some things where we say "this is UB but the compiler doesn't exploit that yet"; that is a very different situation: this arises because Rust is not fully specified yet, and moreover it does not constitute a guarantee -- the compiler could start exploiting this any time and that would not be considered a breaking change.)
I doubt I've mentioned this before, but it is a serious sore spot for me personally when any organization decides what can and cannot be done with a piece of software or hardware. It enrages me so much that I refused to own a smartphone for years just on principle, and now I use a PinePhone.
I run my own server and an Android phone without the Google services. So, I feel you. But your comparison is totally off: if you want to fork Rust, go ahead! It's all open-source and available for you to experiment. Just please do everyone the favor of clearly marking your fork as such, e.g. by giving it a different name.
Of course we'd much prefer if you instead worked on improving Rust, for which there are many avenues. (Good luck doing that with Android or even less open ecosystems...) But at the same time, for Rust to thrive, it is important that we maintain a coherent, mutually compatible ecosystem, and this requires a single common language standard shared by everyone. Flags to change what is or is not UB break this coherence and therefore harm the language. Your freedom (to do with Rust whatever you want) ends where other people are beginning to be affected (e.g. those that want to build a coherent and compatible Rust ecosystem).
If there are things Rust currently cannot do due to its UB rules, this feedback is extremely valuable. Unfortunately, so far you have not been willing to provide such feedback; instead you keep insisting on needing those flags. This has gone around in circles for quite some time now. What do you hope to achieve by repeating the same points over and over? You got basically the same response from many different people. Maybe it is time you actually take that response seriously and consider (and properly respond to) the points we have been making. [But this thread is likely not the right place for that. IRLO would probably be good, or the UCG if it's very UB-specific.]
At the end of the day, there is no good rationalization [...]
Many good arguments have been given. You chose to ignore then. Many alternative avenues to achieve your goals have been suggested. You chose to not even comment on them. You insist on having things exactly your way without even entering a constructive discussion with others. Don't be surprised you won't get a lot of positive responses.
@RalfJung Alright, I've had enough of this, I'm going to close this issue, but first I feel compelled to address some of your points. Let me start by saying that I restrained myself from replying in kind to what I perceived to be rude and hostile replies multiple times in this thread.
if you want to fork Rust, go ahead!
I've heard that for years from many projects, and every time I see it actually happen, the upstream freaks out, results to hostility, and does everything in their power to sabotage or control the downstream. People use "go fork it yourself" as a mic drop, but if you actually do fork it, well then you're the enemy (tm). From my observations, the Rust community is a particularly egregious example of this, especially such outlandish notions that a vendor-specific compiler attribute or flag makes it an entirely different language, and that such attributes are unethical or even illegal. Frankly it absolutely appalls me that such arguments were made at all, and the lack of proper denunciation from other Rust members makes me strongly suspect it's a hidden sentiment, indicative of a deeply unhealthy and actively destructive, authoritarian mindset among these members.
Many good arguments have been given.
I have heard over and over again that it constitutes a fork of the language, (obviously not true to any outside observer) and to use facilities that are not adequate unless you want to use raw pointers for everything and abandon all notions of RAII and any other forms of safety. I've suggested far milder alternatives and been greeted with "huh? how's that useful?". Important features haven't made it in years, how am I supposed to feel confident that a "small" one like fixes for the overzealous UB will ever be made? C is still C, with or without strict aliasing. Rust is a systems language. It should be expected to have certain vendor-specific extensions in various compiler implementations. It should be expected to have slight variation in behavior across implementations. That is not chaos, that is the sign of a healthy language.
You chose to not even comment on them.
I have replied to them repeatedly, and they have been suggested over and over again, here, on Hacker News, on IRC, and other places. I didn't reply much to them here because I'm tired of repeating myself. And now you say I haven't addressed them? Pointers prevent using RAII and most built-in traits, or pretty much anything that needs a &mut. Unlike C and C++, Rust is very dependent on its standard library, at very least libcore, so the parameters it accepts for common tasks are very relevant to what you can and cannot write. It's not an issue for C++, because destructors, copy constructors, move constructors, etc are a core part of the language and not dependent on libstdc++ or libc++.
Working with raw pointers when you want to in Rust is already torture compared to other systems languages, and with these limitations, it is essentially making unsafe code more dangerous. Making unsafe code less dangerous was half the point of all of this whining on my part. Even if you don't deliberately use aliasing &mut, one might want the compile flag as a safety mechanism against human fallibility causing UB. Reference counting? Yeah, I can use that in many places, but my code tends to be very multithreaded, which means Arc and not Rc, which means paying for atomics as well as the size and complexity of Arc.
Sadly it appears Rust is hell-bent on maintaining an iron grip on the language, so much so that people have come here from Rust's community to badger the authors of gccrs to guarantee that they'll never ever add anything useful that rustc doesn't already have and willfully misrepresent my arguments. If anything, this behavior proves that the decentralization of Rust must be *accelerated", precisely to remove influence from such individuals upon the wider language before they choke it to death. Sooner or later, Rust Foundation will lose control of its language, and I'm hoping that it'll be sooner rather than later, most especially after this experience. It has been, unfortunately, illuminating.
Of course we'd much prefer if you instead worked on improving Rust, for which there are many avenues
What do you think I've been trying to do? Did you think I was trying to make it worse? I tried the proper channels and was insulted repeatedly for suggesting it, and then when I responded accordingly I was labelled the villain. Rust is not interested in discussing features or negotiating. It has a set will and a set direction and is actively hostile to anything that opposes any element of that direction, and I am completely certain that it would be futile to try and convince a bunch of people who have already made up their minds. Rust has RFCs that drag on for years for core functionality (asm!() feature-gated for years on nightly, no f128 or f80, no placement new, to name a few) without significant progress, while arguably useless by comparison stuff like async gets pushed through, and badly. Let me be clear as glass: I have zero faith in Rust's management organizations.
I'll make a prediction. Stacked Borrows will be stabilized with little to no changes, and forever more, unsafe Rust code will be dropped to the dangerously low expressiveness of ANSI C.
Rust has pulled far more anger out of me in a few months than a dozen other languages have in my entire life. That's quite an achievement. I suppose it only bothers me so much because there's a great deal of Rust that I do like and appreciate.
I give up. I'm done. I quit. Don't worry about this issue any more. Nothing good can come from defibrillating this dead horse any further.
Good day sir. Harumph.
Let's keep this civil and respectful here. The problem in this thread I see is that we are talking over one another without much consideration. Let's keep this thread purely focused on compiler flags.
@Subsentient no need to work on exposing these flags they are already exposed you can go experiment with them on compiler explorer too.
But at the same time, for Rust to thrive, it is important that we maintain a coherent, mutually compatible ecosystem, and this requires a single common language standard shared by everyone. Flags to change what is or is not UB break this coherence and therefore harm the language. Your freedom (to do with Rust whatever you want) ends where other people are beginning to be affected (e.g. those that want to build a coherent and compatible Rust ecosystem).
@RalfJung Does this mean that GCCRS which currently is exposing all of these flags to the user https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html should not be allowed, or is gccrs considered a fork of rust because it currently allows someone to use -ffast-math?
I understand that using a particular option might change the "semantics" but this is also true in Rust, simply turning optimizations off or on will add/remove the overflow traps, is this not also changing the behaviour or semantics? https://compiler-explorer.com/z/bKqae83fG
Let me reiterate that I wish to work together here through this, let us not push each other away because no one is willing to compromise. Is clear documentation over the usage of the gcc rust compiler not enough to avoid this? What about warnings when using flags outside of what is expected rust behaviour?
(
I have heard over and over again that it constitutes a fork of the language, (obviously not true to any outside observer)
You're wrong here, and repeating this false claim will not make it any more true. I'm just pointing this out for the people reading along that have not yet made up their mind beyond reason. "The language" does not consist just of its syntax but also its semantics including the exact set of UB; a compiler flag that guarantees to not exploit certain UB is equivalent to making it not UB and therefore changes the language. )
@philberty thanks for trying to keep this discussion civil and constructive!
I understand that using a particular option might change the "semantics" but this is also true in Rust, simply turning optimizations off or on will add/remove the overflow traps, is this not also changing the behaviour or semantics? https://compiler-explorer.com/z/bKqae83fG
That's a good question, thanks!
Overflow checks are explicitly documented as being affected by certain compiler flags: the language spec (to the extend that it exists) explicitly says that +
will perform overflow checks if and only if -C overflow-checks
is set -- this is very similar to how debug_assert!
will actually run the assertion if and only if -C debug-assertions
is set. The fact that the default value for this flag is tied to the optimization level does not fundamentally change this.
So, the reason we can have this change in semantics is that the language, down to its very core, explicitly accounts for it. The language does not do anything like this for aliasing of &mut
, so the situation cannot really be compared.
Does this mean that GCCRS which currently is exposing all of these flags to the user https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html should not be allowed, or is gccrs considered a fork of rust because it currently allows someone to use -ffast-math?
Well I guess this means gccrs can be used to do things that cannot be done in rustc. For example, building and running a Rust program with -ffast-math
(which I assume will mean that all floating point arithmetic is subject to fast-math rules, not just the fast-math intrinsics that Rust [unstably] provides) will likely produce a result that violates the Rust spec -- since Rust does not actually have a fast-math mode. This could probably in theory even be used to make well-defined programs cause UB (some unsafe code could rely on getting a particular bitwise result from a floating point operation, and fast-math might break that assumption).
So... yes I think I would say this should be considered an "unsupported"/"unstable" configuration, akin to things that are only possible on nightly Rust. This is just my personal opinion; I do not know the lang team's stanza on this.
Flags that disable some optimizations are probably even more subtle, as this thread shows: in C, -fno-delete-null-pointer-checks
effectively makes dereferencing a null pointer not UB (at least the way it is implemented in LLVM); in Rust, no (officially sanctioned) language dialect exists that has this property. Of course it is always okay for a compiler to perform fewer optimizations, but things become dicey when the compiler guarantees the absence of certain optimizations and therefore lets programmers write code that would be considered UB in "real Rust". That is a situation that I think should be avoided, and that also seems to be @joshtriplett's concern.
@RalfJung I'm done arguing, please do not elicit my replies on this issue or expect me to reply further. Thank you.
@philberty wrote:
@RalfJung Does this mean that GCCRS which currently is exposing all of these flags to the user https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html should not be allowed, or is gccrs considered a fork of rust because it currently allows someone to use -ffast-math?
FWIW, I don't consider -ffast-math
problematic in this way, for one specific reason: it shouldn't ever make gccrs accept an incorrect program, or even change what programs compile; it just changes the results of computations in ways that can make them faster and less accurate. While in theory it's possible that someone could write a program with -ffast-math
and write algorithms that work but suddenly stop working with more accurate computations, that seems exceedingly unlikely. In practice, the two likely scenarios are "make a program that doesn't care as much about accuracy run faster" or "make a program that cares about accuracy run incorrectly". Neither of those would cause a problem in the ecosystem, or constitute a fork of the language.
(And personally, there are many people, myself included, who would like to see a subset of -ffast-math
included in the Rust language, notably better support for FMA.)
In general, most optimization options aren't likely to be a problem. Options that change the semantics of the language, in ways that allow violating guarantees that programmers rely on, would be an issue. (As an example, we've had many, many requests to have a "make borrow checker failures just a warning rather than an error" option.)
Another example of something that would be an issue: Allowing reads from not-known-to-be-initialized stack variables, either as uninitialized data or by guaranteeing zero initialization. If you write let x;
, it's important that the compiler forces x
to be known-written before it can be read. (Sometimes, people want to only sometimes initialize a variable, and then have the compiler just trust that every path reading the variable has already initialized it, even if that can't be determined statically at compile time.)
Those are the kinds of things that we'd want to coordinate on and consider the language semantics of, rather than having compilers diverge on. That's a small, small subset of code generation options. I just read through the entire list of GCC optimization options again, and didn't see any other obvious ones that would fall in that category.
FWIW, I don't consider -ffast-math problematic in this way, for one specific reason: it shouldn't ever make gccrs accept an incorrect program, or even change what programs compile; it just changes the results of computations in ways that can make them faster and less accurate.
It changes the result of well-defined program executions to differ from their well-defined behavior. So, in a sense it's like a compiler that compiles 1+1
to 3
-- no incorrect programs accepted, and no change to the set of accepted programs, but programs producing a different result. Now, I understand that fast-math is not intended to produce "wrong" results, but if you use IEEE 754 to define what the "right" result is, then it does.
But anyway, precisely specifying fast-math is a complicated topic in its own right, no need to also solve that problem in this thread. :) But I do think it is fair to say that fast-math changes the semantics of the language -- if changing the output of well-defined deterministic operations isn't changing the semantics of the language, I do not know what is.
@RalfJung That's the distinction I'm trying to make. Yes, -ffast-math
changes the semantics, but not in a way that causes changes to the set of accepted programs, or similar compatibility issues; that combined with the fact that it isn't the default makes me not concerned about it. In general, I'd prefer if all Rust compilers stuck very precisely to the language semantics across the board. But in the interests of trying to make sure people and projects have room to experiment, I'd settle for all Rust compilers not changing language semantics in a way that causes some programs to work on some compilers and fail on others. That holds especially true when the change is a non-standard option, rather than the default behavior of that compiler. Another compiler choosing to expose a --give-slightly-wrong-answers
flag won't cause problems in the Rust ecosystem, the way an --accept-incorrect-programs
flag would.
In any case, this thread is indeed not the right place to settle that.
We have updated our FAQ to reflect some of the discussion here: https://github.com/Rust-GCC/gccrs/wiki/Frequently-Asked-Questions
## What is the plan for inconsistencies in behaviour?
**If gccrs interprets a program differently from rustc, this is considered a bug.**
Once Rust-GCC can compile and verify all Rust programs, this can also help figure out any inconsistencies in the specification of features in the language. This should help to get features right in _both_ compilers before they are stabilized.
The GCC Rust project is not and will not provide a shortcut for getting features into the Rust language. It will follow the well established processes, i.e. RFCs.
_NOTE: There are situations where it is not clear if something is a language feature. Those will be carefully considered on a case by case basis._
Hi. I'm very pleased with what you guys are doing here, and deeply appreciative to see a fully independent alternative implementation, in GCC no less.
I'm wondering, are you going to provide an -fno-strict-aliasing option? I strongly hope so. rustc, as you probably know, does not provide such an option, and I keep running into situations where I need either that, or a third party trait to accept pointers instead of references, almost daily. I find stacked borrows to be extremely limiting on the kinds of safe abstractions I can design, and if I could at least use them in my own code compiled with -fno-strict-aliasing, I'd be fairly content.
Thanks!