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

Miri function identity hack: account for possible inlining #123781

Merged
merged 1 commit into from
Jul 5, 2024

Conversation

RalfJung
Copy link
Member

@RalfJung RalfJung commented Apr 11, 2024

Having a non-lifetime generic is not the only reason a function can be duplicated. Another possibility is that the function may be eligible for cross-crate inlining. So also take into account the inlining attribute in this Miri hack for function pointer identity.

That said, cross_crate_inlinable will still sometimes return true even for inline(never) functions:

  • when they are DefKind::Ctor(..) | DefKind::Closure -- I assume those cannot be InlineAttr::Never anyway?
  • when cross_crate_inline_threshold == InliningThreshold::Always

so maybe this is still not quite the right criterion to use for function pointer identity.

@rustbot
Copy link
Collaborator

rustbot commented Apr 11, 2024

r? @davidtwco

rustbot has assigned @davidtwco.
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

@rustbot rustbot added 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 Apr 11, 2024
@rustbot
Copy link
Collaborator

rustbot commented Apr 11, 2024

Some changes occurred to the CTFE / Miri engine

cc @rust-lang/miri

@RalfJung
Copy link
Member Author

r? @oli-obk

@rustbot rustbot assigned oli-obk and unassigned davidtwco Apr 11, 2024
@RalfJung
Copy link
Member Author

This is likely blocked on #123780.

Actually that may not be true any more -- Miri should evaluate statics only once and gives at most one address to each AllocId, so the entire thing may be unnecessary now. I don't remember if USIZE_MARKER was already a static back when we added this hack in the interpreter.

@rust-log-analyzer

This comment has been minimized.

Comment on lines +569 to +496
let can_be_inlined = match self.codegen_fn_attrs(instance.def_id()).inline {
InlineAttr::Never => false,
_ => true,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

codegen_fn_attrs(...).inline encodes the inline attribute, which in general is orthogonal to the question how many times item can be code generated:

  • Generic functions / closures can be marked with #[inline(never)].
  • #[no_mangle] functions can be marked #[inline(always)].
  • Cross crate inlining is separate from the attribute.

Copy link
Member Author

Choose a reason for hiding this comment

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

Generic functions are already handled above.

The direction we need is that if the item can be cross-crate inlined then we should give it a fresh address each time here, i.e. is_generic || can_be_inlined should be true. It's okay if we give it a fresh address too often. I am not sure if that property holds with my current definition, it's hard to say as everything is spread across so many parts of rustc.

Cc @saethlin

Copy link
Member

Choose a reason for hiding this comment

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

It is and was entirely unclear to me if any parts of rustc rely on certain Instances getting LocalCopy or GloballyShared codegen. I've been meaning to try to clean this logic up at some point.

Cross-crate inlining should not be orthogonal from the attribute. It should return true for anything that is #[inline] or #[inline(always)], and use heuristics for all other cases.

I do not understand why we permit #[no_mangle] with #[inline(always)]. That seems like an incoherent request to me. #[no_mangle] is widely understood as requesting that the function be lowered as a single external symbol that can be linked against, and lowering it as LocalCopy directly contradicts that. We should probably produce a hard error for that attribute combination.

Copy link
Member

Choose a reason for hiding this comment

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

#[no_mangle] + #[inline(always)] to me seems like a request to export a single copy for consumption by C code, while still codegening a separate local copy for each use from Rust with the request to inline it where possible.

Copy link
Member Author

@RalfJung RalfJung Jul 2, 2024

Choose a reason for hiding this comment

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

FWIW this is all off-topic for this PR. The key thing this PR relies on is that if a function is non-generic (only lifetimes) and inline(never) then it will never have more than one copy across the entire crate graph.

Copy link
Member Author

Choose a reason for hiding this comment

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

And even that is a rather soft requirement -- the LLVM attribute we set makes it so that functions can still be (de)duplicated. It's just that making all functions duplicated in Miri makes backtraces look worse so it's kind of not worth it...

Ideally we'd have an attribute that makes a function guaranteed to have a unique address, and then we suppress the unnamed_addr attribute. Then we can decorate the backtrace functions with that.

But until then this PR at least surfaces more possible address mismatches than before. I am just a bit worried about generating an endless amount of AllocId if a function is cast to a pointer a lot...

Copy link
Member

Choose a reason for hiding this comment

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

I am just a bit worried about generating an endless amount of AllocId if a function is cast to a pointer a lot...

Shouldn't the GC prevent any issues with this?

Copy link
Member Author

Choose a reason for hiding this comment

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

The GC doesn't remove allocations, I don't think?

Copy link
Member

Choose a reason for hiding this comment

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

Right, it doesn't. I'll file a Miri issue as a possible extension, but for now this program:

fn main() {
    loop {
        oof as usize;
    }
}

fn oof() {}

seems to take a minute for its memory usage to grow to 1 GB. That is definitely not desirable but in general I think programs that do that number of casts will have other runtime or memory usage issues.

@rustbot
Copy link
Collaborator

rustbot commented Apr 11, 2024

The Miri subtree was changed

cc @rust-lang/miri

@rust-log-analyzer

This comment has been minimized.

@RalfJung
Copy link
Member Author

🤔 the incremental test suite is failing...?

@RalfJung
Copy link
Member Author

I have no idea how this can affect incremental compilation, but it seems quite concerning. Except for some backtraces printed by Miri, it should be completely fine to do the "new AllocId each time an item-to-fn-ptr coercion happens" for all functions. Bot now it seems like somehow something else started to rely on this not happening.

@oli-obk do you understand how this could be happening?

@michaelwoerister
Copy link
Member

The error looks like a query is being executed during the deserialization of an incr. comp. on-disk cache entry, which is not allowed (see #91919).

@RalfJung
Copy link
Member Author

Thanks! Maybe codegen_fn_attrs is a query, and reserve_and_set_fn_alloc gets called during deserialization?

(That incremental error message is really not giving any hint to the underlying cause, then.)

@michaelwoerister
Copy link
Member

Yes, reserve_and_set_fn_alloc is called during decoding here:

let alloc_id = decoder.interner().reserve_and_set_fn_alloc(instance);

@michaelwoerister
Copy link
Member

Is there any way you can refactor this to not call the codegen_fn_attrs query?

@RalfJung
Copy link
Member Author

I did not realize this logic is also called during decoding. I'll have to rethink this... cross-crate handling is largely a mystery to me and so far magically things worked despite me largely ignoring encoding/decoding concerns. ;)

@michaelwoerister
Copy link
Member

I tried to make the error message more useful in #124252.

GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Apr 22, 2024
…read-ice, r=oli-obk

Improve ICE message for forbidden dep-graph reads.

The new message mentions the main context that the ICE might occur in and it mentions the query/dep-node that is being read.

cc rust-lang#123781, where this would have been helpful.
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Apr 22, 2024
…read-ice, r=oli-obk

Improve ICE message for forbidden dep-graph reads.

The new message mentions the main context that the ICE might occur in and it mentions the query/dep-node that is being read.

cc rust-lang#123781, where this would have been helpful.
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Apr 23, 2024
Rollup merge of rust-lang#124252 - michaelwoerister:better-forbidden-read-ice, r=oli-obk

Improve ICE message for forbidden dep-graph reads.

The new message mentions the main context that the ICE might occur in and it mentions the query/dep-node that is being read.

cc rust-lang#123781, where this would have been helpful.
github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Apr 23, 2024
… r=oli-obk

Improve ICE message for forbidden dep-graph reads.

The new message mentions the main context that the ICE might occur in and it mentions the query/dep-node that is being read.

cc rust-lang/rust#123781, where this would have been helpful.
@rust-log-analyzer

This comment has been minimized.

@RalfJung
Copy link
Member Author

Yes that's a much nicer error. It even tells me which query was being incorrectly invoked. :)

@RalfJung RalfJung 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 May 3, 2024
@RalfJung RalfJung force-pushed the miri-fn-identity branch from 02a1532 to 41b98da Compare July 2, 2024 19:05
@rustbot
Copy link
Collaborator

rustbot commented Jul 2, 2024

Some changes occurred in compiler/rustc_codegen_gcc

cc @antoyo, @GuillaumeGomez

Some changes occurred in compiler/rustc_codegen_cranelift

cc @bjorn3

Some changes occurred to the CTFE / Miri engine

cc @rust-lang/miri

@RalfJung
Copy link
Member Author

RalfJung commented Jul 2, 2024

@oli-obk the latest version should hopefully fix the incremental issues. :)
@rustbot ready

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

oli-obk commented Jul 4, 2024

Yea, that looks reliable to me

@bors r+

@bors
Copy link
Contributor

bors commented Jul 4, 2024

📌 Commit 41b98da has been approved by oli-obk

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jul 4, 2024
@bors
Copy link
Contributor

bors commented Jul 4, 2024

⌛ Testing commit 41b98da with merge 4892331...

@bors
Copy link
Contributor

bors commented Jul 5, 2024

☀️ Test successful - checks-actions
Approved by: oli-obk
Pushing 4892331 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Jul 5, 2024
@bors bors merged commit 4892331 into rust-lang:master Jul 5, 2024
7 checks passed
@rustbot rustbot added this to the 1.81.0 milestone Jul 5, 2024
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (4892331): comparison URL.

Overall result: no relevant changes - no action needed

@rustbot label: -perf-regression

Instruction count

This benchmark run did not return any relevant results for this metric.

Max RSS (memory usage)

Results (secondary 1.6%)

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
1.6% [0.7%, 2.5%] 7
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) - - 0

Cycles

Results (primary -0.9%, secondary 0.7%)

This is a less reliable metric that may be of interest but was not used to determine the overall result at the top of this comment.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
1.3% [0.6%, 2.1%] 2
Improvements ✅
(primary)
-0.9% [-0.9%, -0.9%] 1
Improvements ✅
(secondary)
-0.5% [-0.5%, -0.5%] 1
All ❌✅ (primary) -0.9% [-0.9%, -0.9%] 1

Binary size

This benchmark run did not return any relevant results for this metric.

Bootstrap: 698.879s -> 699.885s (0.14%)
Artifact size: 328.27 MiB -> 328.23 MiB (-0.01%)

@RalfJung RalfJung deleted the miri-fn-identity branch July 5, 2024 20:24
bjorn3 pushed a commit to bjorn3/rust that referenced this pull request Jul 13, 2024
Miri function identity hack: account for possible inlining

Having a non-lifetime generic is not the only reason a function can be duplicated. Another possibility is that the function may be eligible for cross-crate inlining. So also take into account the inlining attribute in this Miri hack for function pointer identity.

That said, `cross_crate_inlinable` will still sometimes return true even for `inline(never)` functions:
- when they are `DefKind::Ctor(..) | DefKind::Closure` -- I assume those cannot be `InlineAttr::Never` anyway?
- when `cross_crate_inline_threshold == InliningThreshold::Always`

so maybe this is still not quite the right criterion to use for function pointer identity.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. 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.