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

[SR-3928] Resilient super.foo() calls #46513

Closed
ematejska mannequin opened this issue Feb 11, 2017 · 11 comments
Closed

[SR-3928] Resilient super.foo() calls #46513

ematejska mannequin opened this issue Feb 11, 2017 · 11 comments
Assignees
Labels
affects ABI Flag: Affects ABI bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@ematejska
Copy link
Mannequin

ematejska mannequin commented Feb 11, 2017

Previous ID SR-3928
Radar rdar://problem/31411193
Original Reporter @ematejska
Type Bug
Status Closed
Resolution Done

Attachment: Download

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, AffectsABI
Assignee @slavapestov
Priority Medium

md5: 347473c6e30dd29ed2a52565f931a4d5

Issue Description:

Ahead of ABI Stability, decide if vtables are ABI and lock it down if so.

Invoking a non-final instance method involves calling a function that is not known at compile time: it must be resolved at run time. This is solved through the use of a vtable, or virtual method table (so called because overridable methods are also known as "virtual" methods). A vtable is a table of function pointers to a class or subclass's implementation of overridable methods. If the vtable is determined to be part of ABI, it needs a layout algorithm that also provides flexibility for library evolution.

Alternatively, we may decide to perform inter-module calls through opaque thunks, or compiler-created intermediary functions, which then perform either direct or vtable dispatch as needed. This enables greater library evolution without breaking binary compatibility by allowing internal class hierarchies to change. This would also unify non-final method dispatch between open and non-open classes while still allowing for aggressive compiler optimizations like de-virtualization for non-open classes. This approach would make vtables not be ABI, as that part of the type metadata would effectively be opaque to another module.

@belkadan
Copy link
Contributor

To be clear, some part of "vtables" is ABI, in that subclasses provide a list of methods they want to override and a list of new override points somehow. But that information can be versioned. The more important thing to decide is if calling a dynamically-dispatched method on a class directly accesses the vtable from client code.

@bob-wilson
Copy link

@swift-ci create

@atrick
Copy link
Member

atrick commented Sep 1, 2017

TL;DR: vtables are not ABI!

Here is a plan for the initial method dispatch ABI and implementation that meets
these goals:

  • Maximize future ABI flexibility while deferring ABI complexity.

  • Minimize public symbols, relocations, and library size.

  • Avoid expanding code size, allowing for further improvements in the future.

  • Runtime performance of resilient dispatch comparable to vtable dispatch.

Each method declaration can be identified by a "method index". Method
indices are static constants per-class, availability ordered.

Each nominal class exports provides a "member offset" global variable [as a non-ABI implementation detail]:

  • this is requred for property access as well as dispatch.

  • this is the same for all instantiations of generic classes (their metadata layout is identical).

  • this cannot be in the NTD because it is adjusted for resilient superclass.

Each nominal class exports a method dispatch entry point:

resolve_method_for_<public_class_name>(isa, method_index)

Invoking a method declared in a base class would be done like this:

resolve_method_for_Base(isa_for_Derived, index_of_Base_foo)

At the caller side, dispatch requires this code pattern:

isa = load self
 f = resolve_method_for_<public_class_name>(isa, method_index)
 f(self, ...)

Dispatch will initially be implemented as these steps:

  • Load the isa pointer.
  • Call into the class dispatch with the constant method index.
  • Load the global member offset.
    (This can be optimized away for non-resilient superclass, otherwise it is in parallel with the isa load).
  • Load the function at *(isa + *member_offset + adjustment + 8*method_index)
  • Return the function pointer.
  • Call the function pointer.

Notes:

Slava wants a separate sybol for each member so that SIL can be
generated without full type checking. But that is not an ABI issue. It
can be done within a module at -Onone.

Why this is good:

  • Not locked into exporting symbols for all methods for binary compatibility.
  • Not locked into allocating vtables for all methods for binary compatibility.
  • Handles `super` calls and all conceivable future dispatch mechanisms.
  • More sophisticated dispatch can be easily added.
  • Good code size and efficient implementation (does not increase the
    memory access critical path vs. vtable dispatch).
  • "Somewhat" hoistable method resolution.

@belkadan
Copy link
Contributor

belkadan commented Sep 2, 2017

What's the form of the metadata that gets emitted for a subclass that overrides some of its superclass's methods?

@atrick
Copy link
Member

atrick commented Sep 2, 2017

I only explained dispatch above, not metadata initialization. All we
need for dispatch are the statically ordered per-class "method
indices" and the per-class exported method lookup helper
function. Initialization has 2 more ABI requirements:

(a) An ABI-exposed metadata field for the size reserved in the
metadata for members of this type. This can be loaded directly
from the nominal type metadata without going through `isa`.

(b) An exported per-class metadata member initializer helper.

I'll explain metadata initialization now and that should answer Jordan's question.

To initialize class `Sub` derived from `Base`:

(1) Initialize metadata for `Base`.

(2) Load the size of `Base`'s member metadata from an ABI-exposed field in its
nominal type metadata:

base_meta_member_size = *(base_nominal_metadata + const_member_size_offset) 

(3) Repeat 1-2 for all base classes of `Sub`.

(4) Allocate `Sub` metadata (with size += base_meta_member_size)

(5) Initialize members of each class in the hierarchy with overrides
declared in that class by calling the exported per-class helper to
initialize that class's section of the metadata:

overrides = {{methodID, funcPtr}, ...}
global_init_meta_members_for_Base(overrides) 

(6) Repeat 5 for all base classes of `Sub` and `Sub` itself.


@slavapestov I don't see a need to export a global per-class member offset variable for either method dispatch or metadata initialization. It seems like an implementation detail. We probably still want to export it for property access though.

@slavapestov
Copy link
Member

Property access is done with accessors so we don't want to export field offset globals either.

@atrick
Copy link
Member

atrick commented Sep 5, 2017

Correction to the proposal discussed above. The following is not true:

"Each nominal class exports a "member offset" global variable."

A global "member offset" variable will be associated with each nominal class, but it will not be exported as ABI.

@slavapestov
Copy link
Member

We still need to expose a base offset variable or some other mechanism to resiliently access generic argument metadata, so that you can define extensions of generic classes in other modules. But yeah, I don't think we need this for virtual methods or field offsets. Thanks for taking the time to do the writeup!

@belkadan
Copy link
Contributor

belkadan commented Sep 5, 2017

Indeed. Thanks, Andy!

@slavapestov
Copy link
Member

#19177

@AnnaZaks
Copy link
Mannequin

AnnaZaks mannequin commented Sep 26, 2018

Resolved -> Closed.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
affects ABI Flag: Affects ABI bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

4 participants