Skip to content

Devirtualizer: fix a crash due to a not supported bitcast of ABI compatible types #74574

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

Merged
merged 2 commits into from
Jun 22, 2024

Conversation

eeckstein
Copy link
Contributor

@eeckstein eeckstein commented Jun 20, 2024

When devirtualizing witness method calls, it can happen that we need a cast between ABI compatible return types.
We were missing supporting type casts between nominal types which are ABI compatible.

This comes from whole-module reasoning of protocol conformances.
If a protocol only has a single conformance where the associated type (ID) is some concrete type (e.g. Int), then the devirtualizer knows that p.get() can only return an Int:

public struct X2<ID> {
  let p: any P2<ID>
  public func testit(i: ID, x: ID) -> S2<ID> {
    return p.get(x: x)
  }
}

and after devirtualizing the get function, its result must be cast from Int to ID.

The layoutIsTypeDependent utility is basically only used here to assert that this cast can only happen between layout compatible types.

rdar://129004015

@eeckstein
Copy link
Contributor Author

@swift-ci test

@eeckstein eeckstein requested a review from atrick June 20, 2024 17:19

llvm::errs() << "Source type: " << srcTy << "\n";
llvm::errs() << "Destination type: " << destTy << "\n";
llvm_unreachable("Unknown combination of types for casting");
}

namespace {
class TypeDependentVisitor : public CanTypeVisitor<TypeDependentVisitor, bool> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you put this in a shared utility instead of copy and pasting it from IRGen?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This utility is for a different purpose and also behaves substantially different. It's not a copy-paste from IRGen.
What could be done, though is the extract the visit logic for stored properties and enum cases into a base class visitor, like ValuePropertyTypeVisitor from which both utilities can derive.
But I'd like to do that in a follow-up PR to minimize the risk for cherry-picking this PR to 6.0.

@slavapestov
Copy link
Contributor

Actually I don't understand how this can ever happen, and the test case is quite large so it's not clear to me what's going on. Maybe there's a bug somewhere else?

@eeckstein
Copy link
Contributor Author

eeckstein commented Jun 21, 2024

Actually I don't understand how this can ever happen

This comes from whole-module reasoning of protocol conformances.
If a protocol only has a single conformance where the associated type (ID) is some concrete type (e.g. Int), then the devirtualizer knows that p.get() can only return an Int:

public struct X2<ID> {
  let p: any P2<ID>
  public func testit(i: ID, x: ID) -> S2<ID> {
    return p.get(x: x)
  }
}

and after devirtualizing the get function, its result must be cast from Int to ID.

The layoutIsTypeDependent utility is basically only used here to assert that this cast can only happen between layout compatible types.

@slavapestov
Copy link
Contributor

slavapestov commented Jun 21, 2024

This comes from whole-module reasoning of protocol conformances.

Oh, is this the getSoleConformingType() thing in ProtocolConformanceAnalysis.h?

and after devirtualizing the get function, its result must be cast from Int to ID.

what if I call X2<String>.testit(...)?

@eeckstein
Copy link
Contributor Author

eeckstein commented Jun 21, 2024

what if I call X2.testit(...)?

You can't, because you cannot construct an X2<String>. You don't have a conforming type with which you can initialize p.

@slavapestov
Copy link
Contributor

You can't, because you cannot construct an X2. You don't have a conforming type with which you can initialize p.

I see! In that case, do you actually need to check that the types are layout-compatible? Is it just an assertion at that point or can it actually be the case that they are not compatible?

@eeckstein
Copy link
Contributor Author

Is it just an assertion at that point

Exactly (the else of the if directly runs into an unreachable). But I think it's an important assert. Therefore I added the utility

Copy link
Contributor

@slavapestov slavapestov left a comment

Choose a reason for hiding this comment

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

Perhaps then it should go in the SIL verifier?

@eeckstein
Copy link
Contributor Author

eeckstein commented Jun 21, 2024

Perhaps then it should go in the SIL verifier?

Maybe. But I'm not sure if those unsafe bitcast instructions can also stem from unsafe user code, where there is no (provable) layout compatibility on SIL level between the involved types.

@eeckstein
Copy link
Contributor Author

@swift-ci test

…ut is dependent on its generic parameters

This is usually the case. Some examples, where they layout is _not_ dependent:
```
   struct S<T> {
     var x: Int // no members which depend on T
   }

   struct S<T> {
     var c: SomeClass<T> // a class reference does not depend on the layout of the class
   }
```
…atible types

When devirtualizing witness method calls, it can happen that we need a cast between ABI compatible return types.
We were missing supporting type casts between nominal types which are ABI compatible.

This comes from whole-module reasoning of protocol conformances.
If a protocol only has a single conformance where the associated type (`ID`) is some concrete type (e.g. `Int`), then the devirtualizer knows that `p.get()` can only return an `Int`:
```
public struct X2<ID> {
  let p: any P2<ID>
  public func testit(i: ID, x: ID) -> S2<ID> {
    return p.get(x: x)
  }
}
```
and after devirtualizing the `get` function, its result must be cast from `Int` to `ID`.

The `layoutIsTypeDependent` utility is basically only used here to assert that this cast can only happen between layout compatible types.

rdar://129004015
@eeckstein eeckstein force-pushed the fix-devirtualizer branch from 3fa686b to 864c143 Compare June 21, 2024 15:29
@eeckstein
Copy link
Contributor Author

@swift-ci test

@shahmishal shahmishal merged commit 0fc5035 into swiftlang:main Jun 22, 2024
3 checks passed
@eeckstein eeckstein deleted the fix-devirtualizer branch June 23, 2024 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants