Skip to content

Direct access external data#158409

Open
ilovepi wants to merge 2 commits into
rust-lang:mainfrom
ilovepi:direct_access_external_data
Open

Direct access external data#158409
ilovepi wants to merge 2 commits into
rust-lang:mainfrom
ilovepi:direct_access_external_data

Conversation

@ilovepi

@ilovepi ilovepi commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Today, when using -Zdirect-access-external-data we've seen an issue when compiling code with ThinLTO or when codegen-units > 1 (due to the use of ThinLTO), that causes GOT accesses to occur in cases where they shouldn't. This is due to two reasons. First, unlike clang, rustc does not emit the direct-access-external-data module flag in the LLVM IR. When compiling with ThinLTO, the missing module flag will cause the compiler to drop the dso_local from external data, leading to GOT slots being used. This breaks Rust usage in the Fuchsia kernel, and for embedded code that cannot use GOT indirection. The Clang example can be found here: https://github.com/llvm/llvm-project/blob/dbab3f71775f6edd11546385aeddb9d0b053a2b5/clang/lib/CodeGen/CodeGenModule.cpp#L1664

The second issue is that without setting the PIC/PIELevel consistently, several things in the LTO pipelines stop working, including direct-access-external-data, incorrect TLS models being selected, or the wrong relocation type being emitted for them in the backend. In the case of direct-access-external-data, if the relocation model is not set correctly, the indirection will always happen because that is the only thing possible (e.g. you're compiling PIC for a shared lib and not PIE). Ideally, we'd unify and simplify things on the LLVM side to make this very hard for frontends to get wrong, or at least supply more consistent APIs for them to consume. This PR works around that by emitting the module flag, and setting the module's PICLevel and PIELevel consistent with the way these are set in context.rs in lto.rs.

@rustbot

rustbot commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

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

@rustbot rustbot added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. A-run-make Area: port run-make Makefiles to rmake.rs 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 25, 2026
@rustbot

rustbot commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

r? @nnethercote

rustbot has assigned @nnethercote.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 73 candidates
  • Random selection from 18 candidates

@ilovepi

ilovepi commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Note: I've included two commits, since I'm trying to solve a single problem, but its fine if these should be split up somehow.

@ilovepi

ilovepi commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@nikic do you have thoughts here on how we can/should solve this on the llvm side (or if i've done something silly in terms of rustc)?

@rust-log-analyzer

This comment has been minimized.

@ilovepi ilovepi force-pushed the direct_access_external_data branch from b46d076 to 7f4494e Compare June 25, 2026 18:22
Comment on lines 1115 to 1127
extern "C" void LLVMRustSetModulePICLevel(LLVMModuleRef M) {
unwrap(M)->setPICLevel(PICLevel::Level::BigPIC);
Module *Mod = unwrap(M);
if (!Mod->getModuleFlag("PIC Level")) {
Mod->setPICLevel(PICLevel::Level::BigPIC);
}
}

extern "C" void LLVMRustSetModulePIELevel(LLVMModuleRef M) {
unwrap(M)->setPIELevel(PIELevel::Level::Large);
Module *Mod = unwrap(M);
if (!Mod->getModuleFlag("PIE Level")) {
Mod->setPIELevel(PIELevel::Level::Large);
}
}

@ilovepi ilovepi Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not totally convinced this is 100% the right approach. Alternatively, we could set the merge strategy and do it that way, but I figured its probably better to only replace if it doesn't exist. Happy to amend the strategy here.

View changes since the review

let llmod = module.module_llvm.llmod();

let reloc_model = cgcx.relocation_model;
llvm::set_module_pic_and_pie_levels(llmod, reloc_model, &cgcx.crate_types);

@nikic nikic Jun 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm confused by why this is necessary. Aren't we just loading a previously serialized module here? Shouldn't the PIC/PIE flags have been set at the time the module was created?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I was surprised too. I believe the main contributor is that core & std libs end up w/o the PIE levels set at all, since they aren't being used in an executable. I can't recall if we have other examples or not, but its my vague recollection that we triggered this issue w/o use of core at all when codegen-units>1.

For our kernel specifically, this becomes an issue as we'd really like to use code from core. However, that would be a hard sell if we cannot prevent GOT indirection when accessing globals that would normally be safe to directly access (and have been accessed that way from C++ code for a very long time).

Do you think there's a better way to address this? Maybe somewhere in the lto.rs code?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So the problem is that libcore/libstd are compiled as PIC but you want them to be PIE? And you're solving this by adding the PIE metadata during LTO? I don't think that's the right way to fix this.

Wouldn't the correct solution here to use build-std and enable PIE for libstd?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

They're being compiled into an executable, and our kernel builds core on demand as part of its build with all the same flags as the rest of the kernel. Under codegen-units>1 ThinLTO is always used, but the output isn't necessarily a PIE object, and this causes the kernel build to break due to the presence of GOT indirection (we have a rule preventing this in our linker script), even when we're not trying to use LTO if we don't setcodegen-units=1. This is all pretty surprising to us, as we didn't expect to have issues w/ LTO builds since they've been working forever in C++. FWIW, I'm pretty sure we tried setting all the various relocation-model flags on the core build w/o success, but we can try again.

I also think the test I added should just work w/ direct-access-external-data when compiling and linking against an rlib.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well, I'll be damned. That did solve the issue. So this does at least unblock me. Thanks for the suggestion. I had wrongly assumed that setting the platform defaults to position independent in the target spec would have amounted to the same thing, especially coupled with the fact that the Rust parts are only slotting into a minor part of the kernel build that's still mostly driven by clang.

I do still think the example in the test should be something that can work out of the box for platforms that default to PIE, though. That said, I'm guessing there may be a better way to spell that, such that we don't need about half of this patch. If you have thoughts about that I'm all ears.

In the meantime, unless there's an objection I'll pare this PR down to just adding the module flag and the minimal set of tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would expect setting it in the target spec to also work, as long as it's not explicitly overwritten on the command line. sess.relocation_model() looks like a pretty simple fallback to the target: self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model).

In the meantime, unless there's an objection I'll pare this PR down to just adding the module flag and the minimal set of tests.

Sounds good to me.

@nnethercote nnethercote left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't know this stuff at all, I will defer to an expert.

View changes since this review

@nnethercote

Copy link
Copy Markdown
Contributor

r? @nikic

@rustbot rustbot assigned nikic and unassigned nnethercote Jun 29, 2026
@rust-bors

This comment has been minimized.

ilovepi added 2 commits July 6, 2026 09:06
When compiling with ThinLTO, the missing module flag will cause the
compiler to drop the `dso_local` from external data, leading to GOT
slots being used. This breaks Rust usage in the Fuchsia kernel, and for
embedded code that cannot use GOT indirection whenever ThinLTO is at
play due to incorrect setting of the relocation model in the LLVM module
when they are merged. Handling the relocation model will be done
separately, as this module flag is a pre-requisite of for that change to
affect direct-access-external-data.

This patch ensures we emit the right module flag for the option. This
matches clang's behavior. See
https://github.com/llvm/llvm-project/blob/dbab3f71775f6edd11546385aeddb9d0b053a2b5/clang/lib/CodeGen/CodeGenModule.cpp#L1664
Without setting the PIC/PIELevel consistently, several things in the LTO
pipelines stop working, direct-access-external-data specifically, but
this can also lead to incorrect TLS models being used or the wrong
relocation type being emitted for them in the backend. Ideally, we'd
unify and simplify things on the LLVM side to make this very hard for
frontends to get wrong. This PR works around that by setting the
module's PICLevel and PIELevel consistent with the way these are set in
context.rs in lto.rs.
@ilovepi ilovepi force-pushed the direct_access_external_data branch from 7f4494e to 1b5adcf Compare July 6, 2026 16:43
@rustbot

rustbot commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

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. A-run-make Area: port run-make Makefiles to rmake.rs 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants