Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Add realign_stack attribute to rustc #3594

Open
wants to merge 33 commits into
base: master
Choose a base branch
from

Conversation

benisxdxd
Copy link

@benisxdxd benisxdxd commented Mar 26, 2024

@Lokathor
Copy link
Contributor

This is useful on ARM as well, where an interrupt handler could be called when the stack isn't 8-byte aligned, which is what the extern "C" ABI expects.

Ideally, if an align_stack fn is called but it doesn't actually use the stack at all then the function could just skip aligning the stack.

@ehuss ehuss added the T-lang Relevant to the language team, which will review and decide on the RFC. label Mar 26, 2024
@matthieu-m
Copy link

There's a #[realign_stack] attribute mentioned early on, but the rest of the RFC only refers to #[stack_realign], so I think the former is a left-over from a previous iteration and should be renamed.

@eggyal
Copy link

eggyal commented Mar 29, 2024

Cc rust-lang/rust#112480

@RalfJung
Copy link
Member

RalfJung commented Mar 30, 2024

I don't see how the stated motivation for this attribute makes sense. When I have a stack frame that contains SSE variables, it is the compiler's responsibility to ensure these variables are sufficiently aligned. Otherwise, that's a soundness bug in the compiler. In Rust we don't ask the user to change their code to fix a compiler bug.

The RFC should show some concrete code examples for code that needs this attribute.

rust-lang/rust#112480 is a bug in MSVC. My understanding is that Rust code for that target already guarantees that local variables are sufficiently aligned. It is only when you mix that with C code emitted by the broken Microsoft compiler that you get unaligned pointers. No amount of attributes on the Rust side can fix that. Did I misunderstand something?

@benisxdxd
Copy link
Author

I don't see how the stated motivation for this attribute makes sense. When I have a stack frame that contains SSE variables, it is the compiler's responsibility to ensure these variables are sufficiently aligned. Otherwise, that's a soundness bug in the compiler. In Rust we don't ask the user to change their code to fix a compiler bug.

The RFC should show some concrete code examples for code that needs this attribute.

I had a non deterministic bug which was caused by a bugged stack alignment. The rust code assumed a stack alignment of 16, while the system's stack alignment was 4. No matter how much I changed the "data-layout", the problem remained.
A team member of mine forked and updated LLVM and added a "hacky" implementation of the realign_stack.

@Lokathor
Copy link
Contributor

Lokathor commented Mar 30, 2024

To perhaps clarify the goal: the requirements for data types aren't changed by this attribute. What's changed is the assumptions of what's true on function entry.

  • If llvm needs the stack at 16 and can assume it's at 16 by default then it obviously doesn't need to do anything. This is our default in rust (on x86_64 at least).
  • If llvm needs the stack at 16 but it can only assume the stack is at 4, then llvm needs to insert the correct adjustments before using the stack, and when returning.

Think of it as an ABI modification attribute.

@Lokathor
Copy link
Contributor

Lokathor commented Mar 30, 2024

@benisxdxd you could add the additional motivation that interrupt handler functions can't always assume the stack is aligned, and currently they need to do manual stack alignment tricks which are annoying to implement every time. Also, they're costly to do if the stack doesn't actually get used at all.

@RalfJung
Copy link
Member

RalfJung commented Mar 30, 2024

I had a non deterministic bug which was caused by a bugged stack alignment. The rust code assumed a stack alignment of 16, while the system's stack alignment was 4. No matter how much I changed the "data-layout", the problem remained.
A team member of mine forked and updated LLVM and added a "hacky" implementation of the realign_stack.

Again, that sounds like a compiler bug. Why does Rust assume more alignment than the system stack provides? One of them is wrong and should be fixed.

If this is some interrupt handler stuff, then it sounds like interrupt handlers can't use the C ABI. Is it possible to add an interrupt ABI that only assumes the alignment guaranteed by interrupts on the given target, and have the compiler automatically do the necessary adjustments when a function with that ABI calls a C ABI function?

@RalfJung
Copy link
Member

RalfJung commented Mar 30, 2024

What's changed is the assumptions of what's true on function entry.

That sounds like a boolean property: do we assume that the regular alignment is guaranteed, or do we not? One could consider having a C-unaligned ABI that makes no assumptions about stack alignment, so the function itself is responsible for doing whatever needs doing for its locals. I guess this could also be an attribute like #[no_stack_alignment_assumption], modifying the C ABI rather than a new ABI. 🤷

But saying "please align the stack to N" for a user-defined N makes no sense with that motivation. The RFC is conflating policy and mechanism. What you want is a function that can be called with any stack alignment; the way this is internally implemented is likely by having the function re-align the stack to some target-specific value, but that's an implementation detail. There's nothing in Rust that lets you argue "I aligned the stack to 128 and hence I can do X", so #[realign_stack(128)] is entirely meaningless.

@benisxdxd
Copy link
Author

benisxdxd commented Mar 30, 2024

#[no_stack_alignment_assumption]

I think that would also solve my problem.

@Lokathor
Copy link
Contributor

Lokathor commented Mar 30, 2024

I don't think it should be an ABI. The ABI of a function affects what callers need to know about the function to make the call. Having a lower aligned stack isn't a caller concern at all. It's something only the internals of the function needs to potentially care about.

Also, a separate ABI combines poorly with other ABI stuff. We have two C ABI variants now for unwind yes/no, then we add this and now there's 4 C ABI variants. Then we get more and more variants down the road.

In fact, there's absolutely no reason to restrict this to the C ABI. It's not fundamentally unreasonable for a Rust ABI function to sometimes only assume a stack alignment of 4 or 1 or whatever, for example. (edit: since this is only needed for ffi we probably still should restrict it to C ABI, but we don't absolutely need to)

Since min-stack-alignment is a function property that calling code doesn't need to care about, we shouldn't make it an actual fn type difference. I'd say it's similar to the instruction_set attribute, which also doesn't affect the ABI because it's similarly a private property of the function you use the attribute on, and also doesn't change the type of a function you use the attribute on.

@RalfJung
Copy link
Member

RalfJung commented Mar 30, 2024

I don't think it should be an ABI. The ABI of a function affects what callers need to know about the function to make the call. Having a lower aligned stack isn't a caller concern at all. It's something only the internals of the function needs to potentially care about.

The caller needs to know which alignment the callee expects. For C functions that's apparently 16 on some targets?

The bugs you describe above are caused by caller violating this requirement. Clearly the caller is implementing a different ABI than the callee (caller aligns stack to 4, callee expects 16).

Also, a separate ABI combines poorly with other ABI stuff. We have two C ABI variants now for unwind yes/no, then we add this and now there's 4 C ABI variants. Then we get more and more variants down the road.

Yeah, the combinatorial explosion is annoying. It's not too surprising though, ABIs have a lot of knobs that one can tune independently.

Since min-stack-alignment is a function property that calling code doesn't need to care about,

I don't follow. The bugs you are describing are caused by the calling code (that calls the interrupt handler -- I guess it's calling silicon, but that doesn't make a difference) not caring about the callee's requirements (min-stack-alignment = 16)!

@Lokathor
Copy link
Contributor

Ah, I think I understand your confusion. The thing is, calling code can't take advantage of a function having a lower minimum stack alignment. If you call a function, you (the caller) must align the stack to the target specified minimum. The only time this attribute is needed is when a function is expected to be called from weird situations that won't perform the correct alignment on their end.

The reason for the bug is because LLVM assumed the current fn had a stack align of 16 on entry, and so didn't adjust the stack before making the call or using align-16 data. If LLVM had known that the fn didn't have align-16 for the stack on entry, it would fix up the stack within the fn prologue whenever the stack is used (including if any other fn is called).

This is similar to the asm! macro nostack option. If it's left off of an asm! block then LLVM will ensure that the stack is aligned to the C ABI specified minimum stack value before entering the assembly block. If nostack is given, then LLVM might leave the stack in a mis-aligned position (since you promised to not use the stack anyway).

@RalfJung
Copy link
Member

RalfJung commented Mar 30, 2024 via email

Co-authored-by: Jacob Lifshay <programmerjake@gmail.com>
@RalfJung
Copy link
Member

For performance reasons I'd prefer it to be specified as fixing the stack "when necessary", and allow the LLVM to make a determination, but "always" doing it also works.

I think it shouldn't talk about fixing the stack at all. It should just talk about what's actually relevant: the precondition that the stack must be aligned is removed. That's all that matters, the rest is implementation details.

benisxdxd and others added 2 commits April 8, 2024 23:13
Co-authored-by: Jacob Lifshay <programmerjake@gmail.com>
Co-authored-by: Jacob Lifshay <programmerjake@gmail.com>
Copy link
Member

@programmerjake programmerjake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks fine to me. requesting a review from me doesn't do a whole lot though since that's not how RFCs get approved, the appropriate team (probably lang) will review and start a proposed FCP once they think it's ready. I'm not a member of those teams.

text/3594-expose-stackrealign-attribute.md Outdated Show resolved Hide resolved
text/3594-expose-stackrealign-attribute.md Outdated Show resolved Hide resolved
- Limited use cases: Stack realignment may not be necessary in most Rust codebases, potentially making this feature less relevant for many developers.

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another alternative would be to add a new ABI that captures "function which can be called with any stack alignment". This section should discuss why that alternative was not proposed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a paragraph about why this was chosen

[rationale-and-alternatives]: #rationale-and-alternatives
An alternative could be a macro workaround instead of adding the attribute.
However it would be more like band-aid than an actual solution.
Another alternative could be adding the any extern "C" function the `stackrealign` attribute implicitly which would solve the main use-case.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems you know have a single paragraph that discusses at least 3 different points, without any structure. Please split this into bullet points or separate paragraph; currently it is very hard to read.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added some spacing

Another alternative could be adding the any extern "C" function the `stackrealign` attribute implicitly which would solve the main use-case.
An extra option could be not verifying data-layout for custom targets provided via `--target=`, which would allow users to patch the "natural stack alignment" in their custom target which should relax LLVM stack alignment assumptions that are present in the system.
Another alternative could be adding a new ABI that captures "function which can be called with any stack alignment".
I chose to propose this RFC and not any of the alternatives because it seems to me that this proposition provides the simplest solution, a solution that is very close to `force_align_arg_pointer` function attribute in GCC and a solution that is easy to implement for rustc.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"easy to implement" and "it's like what GCC does" are not very strong arguments, IMO. The question is what is best for Rust programmers.

@Lokathor raised some points for why they did not think a separate ABI was a good idea; those should be reflected here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added more explaining there

@benisxdxd benisxdxd requested a review from RalfJung April 24, 2024 15:12
Copy link

@eggyal eggyal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some passing thoughts from me.


When the `realign_stack` attribute is applied to a function, the compiler no longer assumes the stack is properly aligned when the function is called, and so will insert code to forcefully realign the stack as needed for calling other functions, variables requiring alignment, etc.
This alignment is achieved by adjusting the stack pointer accordingly to the stack alignment specified in the target ABI's data layout.
Adding this attribute unnecessarily might "waste" space on the stack which could be crucial in real-time systems.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In such cases I can see that instructions/time might be wasted, but wouldn't the net result be no change to the stack?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct, will remove.


# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives
An alternative could be a macro workaround instead of adding the attribute.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Macros don't extend what can be expressed in Rust: anything that can be done by a macro can also be done without a macro (just replace the macro invocation with its expansion)—they just make it less verbose/more ergonomic. It therefore isn't clear to me what is meant by this alternative? A more ergonomic way to define a naked function in assembly that realigns its stack, perhaps?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah.
I meant that maybe this RFC could already be implemented purely in rust already with some naked function and inline assembly shenanigans

[rationale-and-alternatives]: #rationale-and-alternatives
An alternative could be a macro workaround instead of adding the attribute.
However it would be more like band-aid than an actual solution.
Another alternative could be adding the any extern "C" function the `stackrealign` attribute implicitly which would solve the main use-case.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...but at the cost of added overhead where callers abide by the target's stack alignment, as in the majority of cases.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noted

An extra option could be not verifying data-layout for custom targets provided via `--target=`, which would allow users to patch the "natural stack alignment" in their custom target which should relax LLVM stack alignment assumptions that are present in the system.
Another alternative could be adding a new ABI that captures "function which can be called with any stack alignment".
I chose to propose this RFC and not any of the alternatives because it seems to me that this proposition provides the simplest solution, a solution that is very close to `force_align_arg_pointer` function attribute in GCC and a solution that is easy to implement for rustc.
While creating a different ABIs to handle stack realignment could be a viable alternative, introducing a new function attribute like realign_stack in Rust offers several advantages. Firstly, leveraging function attributes aligns with Rust's philosophy of providing clear and concise language features, ensuring that developers can easily understand and apply stack realignment where necessary. Also, if the realign_stack was a part of the ABI we would need to practically duplicate every ABI and create a copy where one has that attribute and the other does not. This would lead to a higher level of complexity and would require higher maintenance over time.
Copy link

@eggyal eggyal Apr 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key point here is that having the ability to be called with an unaligned stack is orthogonal to Rust's existing ABI definitions; perhaps another approach could be for the language to support ABIs as a composable set of features along different axes, e.g. extern "C" + "realign_stack" fn ... or somesuch? That way, it could still form part of the function's type signature/ABI while avoiding exponential growth in the number of explicitly-defined ABIs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks good enough to me but im not sure it is much different from something like the no_mangle attribute which some people also think should be unsafe.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have duplicate ABIs for supporting unwinding extern "C-unwind", so I quite like the ABIs as a composable set of features idea! extern "C" + "unwind" + "align_stack" fn()

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know that actually and this does look nice.
However I think a different RFC might be better for that.
Also, should be noted, there is still #[unwind(allowed)] and #[unwind(abort)] which might suggest that maybe we should be doing both the ABI and attribute solutions

@@ -66,7 +66,8 @@ Another alternative could be adding the any extern "C" function the `stackrealig
An extra option could be not verifying data-layout for custom targets provided via `--target=`, which would allow users to patch the "natural stack alignment" in their custom target which should relax LLVM stack alignment assumptions that are present in the system.
Another alternative could be adding a new ABI that captures "function which can be called with any stack alignment".
I chose to propose this RFC and not any of the alternatives because it seems to me that this proposition provides the simplest solution, a solution that is very close to `force_align_arg_pointer` function attribute in GCC and a solution that is easy to implement for rustc.
Adding a new ABIs adds a higher level of complexity to the language(in my opinion) which is avoidable with this attribute.
While creating a different ABIs to handle stack realignment could be a viable alternative, introducing a new function attribute like realign_stack in Rust offers several advantages. Firstly, leveraging function attributes aligns with Rust's philosophy of providing clear and concise language features, ensuring that developers can easily understand and apply stack realignment where necessary. Also, if the realign_stack was a part of the ABI we would need to practically duplicate every ABI and create a copy where one has that attribute and the other does not. This would lead to a higher level of complexity and would require higher maintenance over time.
Copy link
Member

@RalfJung RalfJung Apr 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firstly, leveraging function attributes aligns with Rust's philosophy of providing clear and concise language features, ensuring that developers can easily understand and apply stack realignment where necessary.

I don't know what you mean by this. Rust's general philosophy is to have a strong type system that captures everything needed to ensure soundness. The "Rust approach" to this would IMO clearly be to reflect this in the function type.

I don't think we have a single existing attribute that works like yours. This is not a "concise language feature", it is an attribute that leaks low-level implementation concerns without properly reflecting them in the type system.

If we follow your arguments we should remove the borrow checker since it is a high level of complexity and requires high maintenance over time. But obviously that would be a bad idea, since we put a high priority on having the compiler catch your mistakes. IMO you should at the very least acknowledge that there is a trade-off here and that there are good arguments for both versions. Currently the RFC doesn't read like you are seriously considering the alternative of doing proper ABI treatment of this issue, instead you are dismissing the alternative on vague grounds without giving it a proper chance. In an RFC you are expected to give good arguments for things you do not agree with -- that's how good discussions look like.

But anyway this is getting into the editorial part of how to write a text that weighs the arguments for two sides of a trade-off in a fair manner and justifies the conclusion taken -- I'm afraid I don't currently have the capacity to help with that.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an extra paragraph about the whole new ABI option.
I would be glad to more pros for that option if I can think of more (or if you tell me them :) ).
I think we are overthinking this whole thing though. If we see in the future that this causes problems could remove it and do the ABI option instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we see in the future that this causes problems could remove it and do the ABI option instead.

removing it may not be backward compatible, so we'd have to do something like deprecate it in all editions < some cutoff and just not have it in editions after that, and have the alternate ABI stuff instead.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah true you are correct I was not thinking about that.
I hope we won't need to remove it, just like it is still present in GCC and clang c still but yeah.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I was looking into the ABI option and came across this, a file @RalfJung actually edited not too away from now.
Seems to me that if every time we want to add something like this we need to change the ABI, it would become very difficult to scale and maintain. @eggyal comment on that seems nice though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants