Skip to content

rustc_const_eval: respect target.min_global_align #142198

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

folkertdev
Copy link
Contributor

@folkertdev folkertdev commented Jun 8, 2025

Currently different backends handle the target.min_global_align option inconsistently: llvm and gcc respect it, but miri/rustc_const_eval does not. I believe that rustc_codegen_cranelift can't yet support per-item alignment.

The only rust target that sets min_global_align today is s390x. In LLVM the only other target that also specifies a min_global_align is larai, which rust does not support. The nvptx targets inherit the min_global_align from their host.

Proposal

The min_global_align target option becomes a language guarantee: If the target specifies min_global_align (i.e. it is not None), all globals will have an alignment of at least the min_global_align specified by the target.

This allows unsafe code authors to write inline assembly and other code that relies on the alignment of globals.

Background

Issue #44411 describes a correctness problem on s390x, where a static is not sufficiently aligned: the larl instruction that LLVM generates assumes an even address, but e.g. a static containing a bool may have an odd address. Clang and gcc make sure that globals are always aligned properly, even if their type does not require it. A language guarantee is required to justify inline assembly using the same instruction.

See also #miri > alignment of globals on s390x

r? @RalfJung
@rustbot label +I-lang-nominated

@rustbot rustbot added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jun 8, 2025
@rustbot
Copy link
Collaborator

rustbot commented Jun 8, 2025

The Miri subtree was changed

cc @rust-lang/miri

These commits modify compiler targets.
(See the Target Tier Policy.)

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

Some changes occurred in compiler/rustc_codegen_gcc

cc @antoyo, @GuillaumeGomez

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

@rust-log-analyzer

This comment has been minimized.

@folkertdev folkertdev force-pushed the miri-s390x-align-statics branch from c87f1ba to 21aae4d Compare June 8, 2025 17:17
@@ -0,0 +1,50 @@
// Test that miri respects the `target.min_global_align` value for the target.
// E.g. on s390x, statics have, in practice, a minimum alignment of 2.
Copy link
Member

Choose a reason for hiding this comment

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

"in practice" seems like unnecessarily wobbly language here. Can we say something more concrete, or if the answer is complicated point to where the full answer is?

Copy link
Member

Choose a reason for hiding this comment

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

the actual answer is "LLVM and some other compilers generate incorrect code if globals have an alignment less than 2, as they generate accesses to globals using LALR, which requires alignment to even addresses in order to work".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, the situation here is a confusing

  • technically, miri does not need to enforce this alignment of 2, because it does not actually emit the larl instruction that would behave incorrectly
  • but in practice, both gcc, clang and rustc do respect this alignment
  • intuitively miri should respect target.min_global_align
  • the only way to observe its effect right now is to test the alignment of statics on s390x

Anyway I've updated the comment with why this test was written.

@folkertdev folkertdev force-pushed the miri-s390x-align-statics branch 2 times, most recently from 7b8bcdd to 553ae22 Compare June 8, 2025 17:44
@RalfJung
Copy link
Member

RalfJung commented Jun 9, 2025

The way I'd frame it is that LLVM made the choice to access globals with an instruction that requires the address to be even (?!?), and we have to set some target option to ensure this is indeed the case. It is somewhat odd that LLVM doesn't ensure this by itself -- nobody asked it to use that instruction after all, that was its own choice, why do frontends even have to know about this? But that's a separate discussion from the one in this PR.

For this PR, the question is whether we promise to unsafe code authors that all globals on this target are going to be 2-aligned. If the answer is "no", then I don't think Miri should do anything here; the use of larl and the 2-aligning of globals is an implementation choice that we could take back any time. If we land this PR, we pretty much make a stable promise that all globals will be 2-aligned, and that should be documented somewhere.

Cc @rust-lang/opsem

Copy link
Member

Choose a reason for hiding this comment

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

Any chance this could be done with a macro rather than having to repeat the same code many times?

@folkertdev folkertdev force-pushed the miri-s390x-align-statics branch from 553ae22 to 69a7b9c Compare June 9, 2025 10:11
@folkertdev folkertdev force-pushed the miri-s390x-align-statics branch from 69a7b9c to adfdea0 Compare June 9, 2025 10:16
@folkertdev
Copy link
Contributor Author

The target maintainer may have some background too, cc @uweigand.

@Darksonn
Copy link
Contributor

Darksonn commented Jun 9, 2025

For this PR, the question is whether we promise to unsafe code authors that all globals on this target are going to be 2-aligned.

There might be unsafe code authors that wish to use the larl instruction in their own inline assembly. That doesn't seem like an unreasonable expectation.

@RalfJung
Copy link
Member

RalfJung commented Jun 9, 2025

Yes, that would be the kind of argument requiring us to make this a language guarantee. Miri can't run inline asm, but arguably unsafe code authors could also cast a pointer to such memory to &u16 and justify it with the platform alignment.

But that requires making this alignment an actual language guarantee, so we should involve the lang team before landing this.

@folkertdev can you rephrase the PR description to make it clear that this is about elevating this target option to a language guarantee? Then we can nominate for t-lang discussion.

@rustbot rustbot added the I-lang-nominated Nominated for discussion during a lang team meeting. label Jun 9, 2025
@folkertdev
Copy link
Contributor Author

I updated the comment, hopefully that is sufficiently clear.

If this does become a language guarantee, I should write a test with a custom target that sets a much higher min_global_align, hence:

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 9, 2025
@rustbot
Copy link
Collaborator

rustbot commented Jun 9, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@RalfJung
Copy link
Member

RalfJung commented Jun 9, 2025

Custom targets are unstable, and adding such a test at least in Miri will be somewhat tricky.

@RalfJung
Copy link
Member

RalfJung commented Jun 9, 2025

The nvptx targets inherit the min_global_align from their host.

That's a statement about LLVM or rustc? It seems like very undesirable behavior, so I hope it's not ours.^^ The generated code shouldn't depend on the host used for compilation...

@folkertdev
Copy link
Contributor Author

The nvptx targets inherit the min_global_align from their host.

That's a statement about LLVM or rustc? It seems like entirely wild behavior, so I hope it's not ours.^^

That's about llvm, specifically the value is inherited here. spir is similar to nvptx, but it has no rust targets.

But now that I look a bit more carefully, also e.g. aarch64 windows has some custom alignment rules for globals here. That logic is based on

https://learn.microsoft.com/en-us/cpp/build/arm64-windows-abi-conventions?view=msvc-170#alignment

That alignment logic is in the clang part of the code base, so presumably rustc would have to replicate it if it wants to be compatible.

The final case (similar to aarch64) is csky here.

@RalfJung
Copy link
Member

RalfJung commented Jun 9, 2025

That's about llvm, specifically the value is inherited here. spir is similar to nvptx, but it has no rust targets.

If Opts.HostTriple there is actually the host this runs on, then that is truly cursed. Cc @RDambrosio016 @kjetilkjeka

@uweigand
Copy link
Contributor

The target maintainer may have some background too, cc @uweigand.

On our platform the ABI specifies that symbol values are guaranteed to be 2-byte aligned. All compilers for the platform will (by default) enforce that property for symbols they generate, and assume that property for external symbols they reference.

(As an extension, at least in GCC you can in fact override this and force generation of a symbol that is not 2-byte aligned; this requires an explicit attribute ((aligned (1))) and strictly speaking causes the program to no longer comply with the ABI - any user of that symbol will also have to use the aligned attribute to ensure correct code generation.)

@traviscross traviscross added the P-lang-drag-2 Lang team prioritization drag level 2.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. label Jun 11, 2025
@scottmcm
Copy link
Member

Hmm, it's unclear to me that this should be a rust problem.

If we tell LLVM it's 1-aligned and they generate incorrect code, why isn't that an LLVM bug?

If someone on a deprecated-by-its-manufacturer platform wants more alignment for a static, why not have them make the type be align 2, or do the #[align(2)] static rust-lang/rfcs#3806, just like one would do on other platforms too?

(I tried to find larai and couldn't -- is it maybe https://q3k.org/lanai.html?)

@folkertdev
Copy link
Contributor Author

I think the argument is that it is in fact incorrect to tell LLVM to say that a global is 1-aligned: it violates the ABI, so LLVM can do whatever it wants.

If someone on a deprecated-by-its-manufacturer platform wants more alignment for a static, why not have them make the type be align 2,

s390x still gets a decent amount of use I think? (it's a weird target, so I believe MIRI uses it a bunch to find edge cases. I personally use it a bunch on CI just to find endianness issues).

(I tried to find larai and couldn't -- is it maybe https://q3k.org/lanai.html?)

Yes, my bad. Clearly it is very obscure. The relevant line is here:

https://github.com/llvm/llvm-project/blob/82acd8c377e9ed267195afdbde16eedebabc648c/clang/lib/Basic/Targets/Lanai.h#L59

though it notes that it's a bit of a hack:

    // Temporary approach to make everything at least word-aligned and allow for
    // safely casting between pointers with different alignment requirements.
    // TODO: Remove this when there are no more cast align warnings on the
    // firmware.
@saethlin
Copy link
Member

so I believe MIRI uses it a bunch to find edge cases

You are trying to put the cart before the horse. The s390x target is used in testing Miri because of the support that IBM provides when the target misbehaves. Unlike MIPS, which is hardly supported at all by anyone.

@RalfJung
Copy link
Member

RalfJung commented Jun 18, 2025 via email

@uweigand
Copy link
Contributor

I think the argument is that it is in fact incorrect to tell LLVM to say that a global is 1-aligned
That's not what we are doing. We are telling LLVM that we don't require any particular alignment for this global. If LLVM wants to align it anyway, it is free to do so. If LLVM chooses not to align it, LLVM must generate the code to make that work. It is quite odd that LLVM would leave ABI matters to the frontend.

If you indeed do not specify any alignment, the back-end will chose a two-byte alignment due to the DataLayout settings, I think. However, at least in the issue #44411 refered to above, the situation was that the front-end did explicitly specify an alignment of 1:

@_ZN3foo3FOO17h5b2ce89c23349598E = constant i8 1, align 1

Now, this issue is 8 years old so I'm not sure if this is actually still a problem in current sources ...

@folkertdev
Copy link
Contributor Author

Rust will always set the alignment of a static, so we can't rely on the default LLVM behavior. Instead the alignment is corrected here to consider target.min_global_align:

fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) {
// The target may require greater alignment for globals than the type does.
// Note: GCC and Clang also allow `__attribute__((aligned))` on variables,
// which can force it to be smaller. Rust doesn't support this yet.
if let Some(min_global) = cx.sess().target.min_global_align {
align = Ord::max(align, min_global);
}
llvm::set_alignment(gv, align);
}

So the proposal here is to formalize what is already the de-facto behavior: statics are guaranteed to have an alignment of at least target.min_global_align .

@RalfJung
Copy link
Member

RalfJung commented Jun 25, 2025

the situation was that the front-end did explicitly specify an alignment of 1:

What you are showing is the frontend specifying an alignment of at least 1. align 1 does not mean "this MUST NOT be 2-aligned", all it says is "this must be at least 1-aligned".

So the proposal here is to formalize what is already the de-facto behavior

The proposal is to make this a user-visible guarantee rather than an implementation detail we can change any time.

@uweigand
Copy link
Contributor

the situation was that the front-end did explicitly specify an alignment of 1:

What you are showing is the frontend specifying an alignment of at least 1. align 1 does not mean "this MUST NOT be 2-aligned", all it says is "this must be at least 1-aligned".

This doesn't match my understanding of the LLVM IR semantics. For the alignment of global variables the LLVM documentation (https://llvm.org/docs/LangRef.html#globalvars) states:

An explicit alignment may be specified for a global, which must be a power of 2. If not present, or if the alignment is set to zero, the alignment of the global is set by the target to whatever it feels convenient. If an explicit alignment is specified, the global is forced to have exactly that alignment. Targets and optimizers are not allowed to over-align the global if the global has an assigned section. In this case, the extra alignment could be observable: for example, code could assume that the globals are densely packed in their section and try to iterate over them as an array, alignment padding would break this iteration.

(Of course, a global marked with "align 1" may happen to end up at an address that is a multiple of two. But as far as observable effects of alignment setting are concerned, e.g. section alignment values as mentioned above, LLVM must respect the alignment value exactly. In my understanding this also applies to cases like here, where an alignment of 1 requires a variant ABI and use of different instructions.)

@RalfJung
Copy link
Member

RalfJung commented Jun 25, 2025

Of course, a global marked with "align 1" may happen to end up at an address that is a multiple of two.

Yes, that's what I am saying. LLVM shoots itself into the foot by putting the global at an odd address and then using an instruction that requires an even address. That's entirely self-inflicted pain on the side of LLVM, as far as I can tell. LLVM could just put all globals at even addresses by itself since it is also the party making use of that assumption when generating the code that accesses the global.

But anyway, this discussion is kind of moot -- LLVM decided to burden their frontends with this problem, so we deal with it, as we deal with a lot list of other decisions LLVM made that we cannot all comprehend. This PR, however, is not about how we deal with LLVM, it's about whether we guarantee to our users that all globals will live at an even address.

@traviscross
Copy link
Contributor

We talked about this today in triage. We were a bit unclear on how to view the exact nature of the ask. It would be helpful to us if someone could write up an analysis of to what degree the behavior that's being requested is compelled by what a correctly implemented compiler for this target must do versus to what degree something that could be said to be a language guarantee is needed.

Analysis of how to distinguish these two things, in general, would also be helpful to us, as we were looking for ways to write down, for ourselves, certain guidelines about this that we could apply broadly.

@traviscross traviscross added T-lang Relevant to the language team I-lang-radar Items that are on lang's radar and will need eventual work or consideration. and removed I-lang-nominated Nominated for discussion during a lang team meeting. P-lang-drag-2 Lang team prioritization drag level 2.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. labels Jul 2, 2025
@folkertdev
Copy link
Contributor Author

Specifically the meeting notes ask for

Was there a "here's the link to the target ABI that says this"

This link was not specifically provided, but you can download the spec here https://github.com/IBM/s390x-abi/releases. On page 51 it says:

2.3.1. Symbol Values
A symbol table entry’s st_value field is the symbol value. If that value represents a section
offset or a virtual address, it must be halfword aligned. This enables use of CIA-relative
addressing instructions such as LARL.

So, this is not just LLVM being LLVM (would be weird, some of the authors of the spec work on the LLVM implementation).


Given that this behavior is defined in the official specification, it seems reasonable to me that inline assembly could make use of this information, like so:

https://godbolt.org/z/13rxosr9e

static MY_GLOBAL: u8 = 12;

#[unsafe(naked)]
pub extern "C" fn naked_asm() -> u8 {
    // SAFETY: larl is safe because global symbols are guaranteed 
    // to be 2-byte aligned on s390x.
    std::arch::naked_asm!(
        "larl    %r1, {}",
        "llgc    %r2, 0(%r1)",
        "br      %r14",
        sym MY_GLOBAL,
    );
}

My understanding is that currently the above is technically unsound (though it works in practice). I think it should be sound, given that the platform specification says global symbols are sufficiently aligned for the larl instruction.

@RalfJung
Copy link
Member

RalfJung commented Jul 3, 2025

So, this is not just LLVM being LLVM (would be weird, some of the authors of the spec work on the LLVM implementation).

I would say LLVM expecting frontends to handle this rather than doing it once and for all in the shared library is still LLVM being LLVM. ;)

But I agree with the rest of your post.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team
10 participants