Skip to content

Change __rust_no_alloc_shim_is_unstable to be a function #141061

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

dpaoliello
Copy link
Contributor

@dpaoliello dpaoliello commented May 15, 2025

This fixes a long sequence of issues:

  1. A customer reported that building for Arm64EC was broken: Linking error when compiled to arm64ec-pc-windows-msvc #138541
  2. This was caused by a bug in my original implementation of Arm64EC support, namely that only functions on Arm64EC need to be decorated with # but Rust was decorating statics as well.
  3. Once I corrected Rust to only decorate functions, I started linking failures where the linker couldn't find statics exported by dylib dependencies. This was caused by the compiler not marking exported statics in the generated DEF file with DATA, thus they were being exported as functions not data.
  4. Once I corrected the way that the DEF files were being emitted, the linker started failing saying that it couldn't find __rust_no_alloc_shim_is_unstable. This is because the MSVC linker requires the declarations of statics imported from other dylibs to be marked with dllimport (whereas it will happily link to functions imported from other dylibs whether they are marked dllimport or not).
  5. I then made a change to ensure that __rust_no_alloc_shim_is_unstable was marked as dllimport, but the MSVC linker started emitting warnings that __rust_no_alloc_shim_is_unstable was marked as dllimport but was declared in an obj file. This is a harmless warning which is a performance hint: anything that's marked dllimport must be indirected via an __imp symbol so I added a linker arg in the target to suppress the warning.
  6. A customer then reported a similar warning when using lld-link (Fix linking statics on Arm64EC #140176 (comment)). I don't think it was an implementation difference between the two linkers but rather that, depending on the obj that the declaration versus uses of __rust_no_alloc_shim_is_unstable landed in we would get different warnings, so I suppressed that warning as well: [win] Ignore MSVC linker warning 4217 #140954.
  7. Another customer reported that they weren't using the Rust compiler to invoke the linker, thus these warnings were breaking their build: Fix linking statics on Arm64EC #140176 (comment). At that point, my original change was reverted (Revert "Fix linking statics on Arm64EC #140176" #141024) leaving Arm64EC broken yet again.

Taking a step back, a lot of these linker issues arise from the fact that __rust_no_alloc_shim_is_unstable is marked as extern "Rust" in the standard library and, therefore, assumed to be a foreign item from a different crate BUT the Rust compiler may choose to generate it either in the current crate, some other crate that will be statically linked in OR some other crate that will by dynamically imported.

Worse yet, it is impossible while building a given crate to know if __rust_no_alloc_shim_is_unstable will statically linked or dynamically imported: it might be that one of its dependent crates is the one with an allocator kind set and thus that crate (which is compiled later) will decide depending if it has any dylib dependencies or not to import __rust_no_alloc_shim_is_unstable or generate it. Thus, there is no way to know if the declaration of __rust_no_alloc_shim_is_unstable should be marked with dllimport or not.

There is a simple fix for all this: there is no reason __rust_no_alloc_shim_is_unstable must be a static. It needs to be some symbol that must be linked in; thus, it could easily be a function instead. As a function, there is no need to mark it as dllimport when dynamically imported which avoids the entire mess above.

There may be a perf hit for changing the volatile load to be a tail call, so I'm happy to change that part back (although I question what the codegen of a volatile load would look like, and if the backend is going to try to use load-acquire semantics).

Build with this change applied BEFORE #140176 was reverted to demonstrate that there are no linking issues with either MSVC or MinGW: https://github.com/rust-lang/rust/actions/runs/15078657205

Incidentally, I fixed tests/run-make/no-alloc-shim to work with MSVC as I needed it to be able to test locally (FYI for #128602)

r? @bjorn3
cc @jieyouxu

@rustbot rustbot added A-testsuite Area: The testsuite used to check the correctness of rustc T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-infra Relevant to the infrastructure team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels May 15, 2025
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rustbot rustbot added the A-run-make Area: port run-make Makefiles to rmake.rs label May 16, 2025
@rust-log-analyzer

This comment has been minimized.

@bjorn3
Copy link
Member

bjorn3 commented May 18, 2025

Do you have a link to the problems you are having?

@dpaoliello
Copy link
Contributor Author

Do you have a link to the problems you are having?

Yes, sorry, I was intending to add a full write-up to the description once I had done the PR builds to validate that this works with both MinGW and MSVC. I've updated the description now.

@dpaoliello dpaoliello marked this pull request as ready for review May 19, 2025 17:19
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label May 19, 2025
@rustbot
Copy link
Collaborator

rustbot commented May 19, 2025

Some changes occurred in compiler/rustc_codegen_gcc

cc @antoyo, @GuillaumeGomez

This PR modifies run-make tests.

cc @jieyouxu

Some changes occurred in compiler/rustc_codegen_cranelift

cc @bjorn3

Some changes occurred in compiler/rustc_codegen_ssa

cc @WaffleLapkin

@dpaoliello
Copy link
Contributor Author

YAML changes were for testing.
@rustbot label -T-infra

@rustbot rustbot removed the T-infra Relevant to the infrastructure team, which will review and decide on the PR/issue. label May 19, 2025
Copy link
Member

@bjorn3 bjorn3 left a comment

Choose a reason for hiding this comment

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

How do we handle regular #[no_mangle] statics that doesn't cause those linker warnings/errors?

#[rustc_nounwind]
#[rustc_std_internal_symbol]
#[cfg(not(bootstrap))]
fn __rust_no_alloc_shim_is_unstable();
Copy link
Member

Choose a reason for hiding this comment

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

At the very least this symbol name will need to be changed to prevent UB when someone uses the old definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I'll rename it.

core::ptr::read_volatile(&__rust_no_alloc_shim_is_unstable);
#[cfg(not(bootstrap))]
__rust_no_alloc_shim_is_unstable();
Copy link
Member

Choose a reason for hiding this comment

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

Would read_volatile(__rust_no_alloc_shim_is_unstable.addr().cast::<u8>()) (using the unstable FnPtr::addr method) work? Or maybe one of OpenBSD's exploit mitigations would break with that. Do we even care about that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would work for most platforms.

I'm not sure about OpenBSD, but it would certainly cause issues in Xbox kernel-mode, as it loads binaries as Execute-Only (i.e., they are not readable).

Again, I suspect that the volatile read is more expensive than a tail call to empty function as it may generate load-acquire semantics.

I'm not sure if we could get away with just getting the address of the function and not reading from it? Backends might optimize that away though...

Copy link
Member

Choose a reason for hiding this comment

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

Maybe black_box(__rust_no_alloc_shim_is_unstable as fn())?

Copy link
Member

Choose a reason for hiding this comment

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

Would read_volatile(__rust_no_alloc_shim_is_unstable.addr().cast::()) (using the unstable FnPtr::addr method) work?

Miri would definitely complain about that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe black_box(__rust_no_alloc_shim_is_unstable as fn())?

Doesn't work: we get the correct behavior where it is required for linking to work, but then the codegen tests fail as the compiler is not able to optimize away allocations.

if output.is_some() {
block.end_with_return(None, ret);
} else {
block.end_with_void_return(None);
Copy link
Contributor

Choose a reason for hiding this comment

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

There was a fix for that done in the repo of cg_gcc, but since you change the same code anyway, I guess it would be better to have the fix here as well to make sure I don't forget it when fixing the merge conflict.

Suggested change
block.end_with_void_return(None);
block.add_eval(None, ret);
block.end_with_void_return(None);

@jieyouxu jieyouxu added A-linkage Area: linking into static, shared libraries and binaries A-allocators Area: Custom and system allocators labels May 19, 2025
@dpaoliello
Copy link
Contributor Author

How do we handle regular #[no_mangle] statics that doesn't cause those linker warnings/errors?

If the static is exported by one crate and referenced by another, then the compiler knows whether that static will be statically linked or dynamically imported since it can see the dependency chain between the current crate using the static and the declaring crate.

If the static is referenced via an extern block, then it is up to the user to correctly decorate it with a #[link] attribute. If they get that attribute wrong (mark it as dynamically imported when it is statically linked AND the declaration is in an obj being linked in, not an rlib) then they will see the linker warning.

@rustbot
Copy link
Collaborator

rustbot commented May 19, 2025

The Miri subtree was changed

cc @rust-lang/miri

@rust-log-analyzer

This comment has been minimized.

@bjorn3
Copy link
Member

bjorn3 commented May 19, 2025

If the static is exported by one crate and referenced by another, then the compiler knows whether that static will be statically linked or dynamically imported since it can see the dependency chain between the current crate using the static and the declaring crate.

If the static is defined inside an rlib, and we are currently compiling an rlib, rustc can't know if the static will be statically linked or dynamically imported. Only when we are linking the current rlib into something do we know if the other rlib was statically linked or dynamically linked.

@dpaoliello
Copy link
Contributor Author

If the static is exported by one crate and referenced by another, then the compiler knows whether that static will be statically linked or dynamically imported since it can see the dependency chain between the current crate using the static and the declaring crate.

If the static is defined inside an rlib, and we are currently compiling an rlib, rustc can't know if the static will be statically linked or dynamically imported. Only when we are linking the current rlib into something do we know if the other rlib was statically linked or dynamically linked.

The warning is only emitted if something marked with dllimport is declared either in the current obj file or another obj file that is currently being linked: the MSVC linker doesn't complain if it's declared in a static library (.lib or .rlib).

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@RalfJung
Copy link
Member

The alloc-access-tracking.rs test in Miri is sensitive to the exact allocation counts; ID 21 mentioned in that test will have to be adjusted because there's now fewer extern statics being declared (the new ID is probably 20).

@rust-log-analyzer

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-allocators Area: Custom and system allocators A-linkage Area: linking into static, shared libraries and binaries A-run-make Area: port run-make Makefiles to rmake.rs A-testsuite Area: The testsuite used to check the correctness of rustc 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. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants