Skip to content

Add a !type annotation to a vtable for every supertrait that is a prefix of the vtable - #165475

Open
folkertdev wants to merge 5 commits into
rust-lang:mainfrom
folkertdev:vfe-upcast
Open

folkertdev wants to merge 5 commits into
rust-lang:mainfrom
folkertdev:vfe-upcast

Conversation

@folkertdev

@folkertdev folkertdev commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

tracking issue: #68262

This fixes a miscompilation where a vtable was incorrectly eliminated because it was only used by a supertrait.

In https://godbolt.org/z/8TE1WMvac, call_super could be applied to both A and B, but it incorrectly just always calls the B method. The vtable for A has been eliminated.

The fix here only works for the first supertrait in the list, or later ones if they have no methods. So in trait Foo: A + B, there is still a bug when B actually has methods (and its vtable is not a prefix of Foos vtable).

In that case the vtable for B does not get any !type annotations, which provides incomplete information to VFE and it may therefore miscompile. That needs some further refactoring, which is blocked on #164808.

Best reviewed commit-by-commit.

@rustbot rustbot added A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. 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. labels Nov 4, 2026
@folkertdev
folkertdev force-pushed the vfe-upcast branch 2 times, most recently from 25055da to 7663f53 Compare November 5, 2026 12:37
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@folkertdev
folkertdev force-pushed the vfe-upcast branch 2 times, most recently from 3811141 to c638b49 Compare November 5, 2026 18:35

@folkertdev folkertdev Nov 5, 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.

On current nightly the drop does not run

https://godbolt.org/z/qs4v4Wbqf

View changes since the review

@folkertdev

Copy link
Copy Markdown
Contributor Author

r? WaffleLapkin

@rustbot

rustbot commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

WaffleLapkin is not on the review rotation at the moment.
They may take a while to respond.

@folkertdev
folkertdev marked this pull request as ready for review November 5, 2026 23:34
@rustbot

rustbot commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred in compiler/rustc_codegen_llvm/src/debuginfo

cc @Walnut356

@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 Nov 5, 2026
@rust-bors

This comment has been minimized.

In `with_self_ty` the def_id that is used is just `self.def_id`, so this is always the same DefId
@rustbot

rustbot commented Nov 12, 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.

@WaffleLapkin WaffleLapkin left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Some minor comments;

More importantly, the type metadata / vfe seems very confusing. I wish we had some better docs for it... But I think I understood the problem? Here is how I understand it:

We want llvm to be able to de-virtualize calls through trait objects. The tool that LLVM provides for this is "type metadata" (& typed loads & vcall visibility). Type metadata is just a pair of (offset, ident) which we attach to the vtable constants, which says "'object' ident starts at offset in this constant". Then, when we load function pointers from vtables, we use llvm.type.checked.load with the ident of the vtable we are loading from. From that LLVM assumes that this load only takes pointers to constants maked with the same ident (+ the offset, but we always make it 0), and can figure out what are all the options for the loaded value.

The problem then comes when we use vtable of subtraits as the super trait's one, because it can hide an option for what the virtual function is, in cases where we don't ever generate the super trait's vtable separately. And so this can only happen with upcasting (and only the case where it uses a prefix of the vtable), because that's the only case where we load pointer from a vtable X but it's backed by a constant of vtable Y.

@folkertdev does that all sound correct?

View changes since this review

}

impl VCallVisibility {
fn for_def_id<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, def_id: DefId) -> Self {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO this should take a trait ref, not just the def id (as otherwise it's unclear def id of what should be passed).

Comment on lines +1795 to +1801
let Some(vcall_visibility) = trait_hierarchy
.iter()
.map(|trait_ref| VCallVisibility::for_def_id(cx, trait_ref.def_id))
.min()
else {
return;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you add a comment describing what a "minimal" visibility here means?

let mut entries = vec![];

let vtable_segment_callback = |segment| -> ControlFlow<()> {
if let VtblSegment::TraitOwnEntries { trait_ref, emit_vptr: false } = segment {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could you add a comment explaining why we are checking for emit_vptr: false?

@WaffleLapkin WaffleLapkin left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

r=me with the comments from my previous review addressed.

Follow up work would be to make sure that we always go through the codepath that adds the type metadata, rather than having something different for const eval / vptrs.

View changes since this review

@folkertdev

Copy link
Copy Markdown
Contributor Author

We discussed this in person, but just for posterity:

Type metadata is just a pair of (offset, ident) which we attach to the vtable constants, which says "'object' ident starts at offset in this constant".

Yes. For Rust vtables the offset is always 0, C++ vtables actually use this offset parameter.

Then, when we load function pointers from vtables, we use llvm.type.checked.load with the ident of the vtable we are loading from.

Yes, and it is important that we only ever use a checked load to load a function pointer from the vtable. A normal load is not allowed. We do use normal loads for the size and align fields of the vtable header. That is fine.

From that LLVM assumes that this load only takes pointers to constants marked with the same ident (+ the offset, but we always make it 0), and can figure out what are all the options for the loaded value.

Yes, again this only works if we give LLVM accurate information. When we don't, it may miscompile.

The problem then comes when we use vtable of subtraits as the super trait's one,

Yes, specifically when we only perform checked loads for methods from the supertrait, and never with the subtrait.

because it can hide an option for what the virtual function is, in cases where we don't ever generate the super trait's vtable separately.

Nit: generating the supertraits vtable separately by itself would not be sufficient. We'd also need to perform a type load for the specific functions that are used.

And so this can only happen with upcasting (and only the case where it uses a prefix of the vtable), because that's the only case where we load pointer from a vtable X but it's backed by a constant of vtable Y.

As discussed the case where the supertrait's vtable is not a prefix is also broken. But yeah the idea is just that before this PR the vtable for Trait could be removed (correctly, based on the information that we provided to LLVM) even though we might actually be using methods from the supertrait(s).


@bors r=WaffleLapkin

@rust-bors

rust-bors Bot commented Nov 13, 2026

Copy link
Copy Markdown
Contributor

📌 Commit 4573464 has been approved by WaffleLapkin

It is now in the queue for this repository.

@rust-bors rust-bors Bot 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 Nov 13, 2026
Zalathar added a commit to Zalathar/rust that referenced this pull request Nov 13, 2026
Add a `!type` annotation to a vtable for every supertrait that is a prefix of the vtable

tracking issue: rust-lang#68262

This fixes a miscompilation where a vtable was incorrectly eliminated because it was only used by a supertrait.

In https://godbolt.org/z/8TE1WMvac, `call_super` could be applied to both `A` and `B`, but it incorrectly just always calls the `B` method. The vtable for A has been eliminated.

The fix here only works for the first supertrait in the list, or later ones if they have no methods. So in `trait Foo: A + B`, there is still a bug when B actually has methods (and its vtable is not a prefix of `Foo`s vtable).

In that case the vtable for `B` does not get any `!type` annotations, which provides incomplete information to VFE and it may therefore miscompile. That needs some further refactoring, which is blocked on rust-lang#164808.

Best reviewed commit-by-commit.
rust-bors Bot pushed a commit that referenced this pull request Nov 13, 2026
Rollup of 17 pull requests

Successful merges:

 - #160618 (require a consistent codegen backend when -Zllvm-target-feature is present)
 - #164043 (alloc: Unstably add `Vec::repeat` and `Vec::repeat_in`)
 - #164808 (asm const ptr: move name generation to asm-specific logic)
 - #164823 (Use `DerefMut` and `IndexMut` before `&raw mut` autoborrow)
 - #165475 (Add a `!type` annotation to a vtable for every supertrait that is a prefix of the vtable)
 - #165568 (rustdoc: Suggest corrections for reference label typos)
 - #165924 (rustc_codegen_gcc subtree update 2026-11-13)
 - #165927 (Remove `const_struct` from `rustc_codegen_ssa`)
 - #164362 (Emit note for `non_exhaustive` within defining crate for `unreachable_patterns`)
 - #164997 (Fix as str suggestion in subpattern)
 - #165825 (make `Complex<x87_f80>` ABI-compatible with C)
 - #165868 (Small changes to dyn compatibility)
 - #165879 (Add `#[rustc_test_entrypoint_marker]` back !)
 - #165903 (Fix borrowck ICE when a borrow outlives a local in a const block)
 - #165914 (Use `trim_prefix` in bootstrap)
 - #165919 (tests: accept better behavior on underaligned accesses)
 - #165936 (Enhance documentation for try_clone File implementation)
Zalathar added a commit to Zalathar/rust that referenced this pull request Nov 14, 2026
Add a `!type` annotation to a vtable for every supertrait that is a prefix of the vtable

tracking issue: rust-lang#68262

This fixes a miscompilation where a vtable was incorrectly eliminated because it was only used by a supertrait.

In https://godbolt.org/z/8TE1WMvac, `call_super` could be applied to both `A` and `B`, but it incorrectly just always calls the `B` method. The vtable for A has been eliminated.

The fix here only works for the first supertrait in the list, or later ones if they have no methods. So in `trait Foo: A + B`, there is still a bug when B actually has methods (and its vtable is not a prefix of `Foo`s vtable).

In that case the vtable for `B` does not get any `!type` annotations, which provides incomplete information to VFE and it may therefore miscompile. That needs some further refactoring, which is blocked on rust-lang#164808.

Best reviewed commit-by-commit.
rust-bors Bot pushed a commit that referenced this pull request Nov 14, 2026
Rollup of 16 pull requests

Successful merges:

 - #160618 (require a consistent codegen backend when -Zllvm-target-feature is present)
 - #164043 (alloc: Unstably add `Vec::repeat` and `Vec::repeat_in`)
 - #164823 (Use `DerefMut` and `IndexMut` before `&raw mut` autoborrow)
 - #165475 (Add a `!type` annotation to a vtable for every supertrait that is a prefix of the vtable)
 - #165568 (rustdoc: Suggest corrections for reference label typos)
 - #165924 (rustc_codegen_gcc subtree update 2026-11-13)
 - #165927 (Remove `const_struct` from `rustc_codegen_ssa`)
 - #164362 (Emit note for `non_exhaustive` within defining crate for `unreachable_patterns`)
 - #164997 (Fix as str suggestion in subpattern)
 - #165825 (make `Complex<x87_f80>` ABI-compatible with C)
 - #165868 (Small changes to dyn compatibility)
 - #165879 (Add `#[rustc_test_entrypoint_marker]` back !)
 - #165903 (Fix borrowck ICE when a borrow outlives a local in a const block)
 - #165914 (Use `trim_prefix` in bootstrap)
 - #165919 (tests: accept better behavior on underaligned accesses)
 - #165936 (Enhance documentation for try_clone File implementation)

This branch has not been deployed

No deployments
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. 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.

4 participants