Just some random thoughts without given this the appropriate depth of consideration that it deserves, so this probably will break down horrendously.
I believe that the following cases are the ones that need to considered:
static_cast: the types are known and can be statically converted. Can we achieve this as an "optimization" by statically doing this check as a SIL level pass? Doing that lets us fold this into the next cast type. If we actually track the clang Decl, we can easily verify the inheritance as needed.
dynamic_cast: this is the only cast that I think that matters to Swift for C++ interop. This requires the checking of the type (if RTTI is enabled - RTTI being disabled prevents dynamic_cast from being used) and returning the cast or nullptr. This is modelled in the language as the as cast that is traditionally done. This actually does require changing the underlying runtime calls here.
reinterpret_cast: this can be left as an unsafe memory operation as it is. The reinterpretation of the memory is then effectively made explicit in the language.
C style casts: select the correct cast or go with the syntactic reinterpret_cast equivalent which is what the reinterpret_cast equivalent really is.
Basically, what this boils down to, keep the CXXRecordDecl associated with the imported type. Perform a SIL pass over the casts to see if they can be identified statically or not. If so, great, replace it with the inline casting. Failure to do so, treat it as a dynamic_cast and use the C++ runtime's casting to cast the pointer at runtime and return the value as the optional which as returns, which makes this fit naturally into the language and provides a reasonable point for the casting to be handled.
When perform a method dispatch, if the method is known to be implemented directly by the type, a regular method call be done, otherwise, make it a regular C++ virtual dispatch. That would allow the regular behaviour of C++ method dispatch to be maintained as well.
Upcasts are generally unsafe and go through an explicit pointer cast - which is possibly a solution here - allow the user to take the address of the type, and assign to the explicit type that they are upcasting to. However, does the upcasting really take into account the this pointer adjustment thunk invocation?