“This is the 14th day of my participation in the First Challenge 2022.

As we all know, method calls in OC send messages via objc_msgSend; So how is method invocation implemented in Swift?

Method lookup

Let’s look at the following code:

In the ViewController class, we create a new Teacher class in which we define a teach method that initializes Teacher in the viewDidLoad method and calls teach. (To avoid code generated by other calls, We delete the super.viewDidLoad call), we create a breakpoint when we call teach, at which point we look at the assembly page:

In the assembly instruction, we call the analysis method mainly through bl and BLR two instructions; Note that there is also a BLR instruction between the __allocating_init and swift_release BL directives. Is this BLR execution the same as the teach method called? We execute the assembly instruction down to BLR X8:

By instruction printing, we confirm that we are calling the teach method. How is the teach method found? Let’s analyze assembly code line by line:

  • bl 0x104012a1cThis line is called by the assembler instruction__allocating_initMethod, which returns oneTeacherThe return value is placed in thex0Register;
  • mov x20, x0: Registerx0Is copied tox20Register, at this timex20The register holdsTeacherInstance object of;
  • str x20, [sp, #0x8]andstr x20, [sp, #0x10]Is to convert the registerx20Write the value of to memory, we can ignore;
  • ldr x8, [x20]Will:x20The value in the register is readx8Register, because theta is64Bit schema, so what is read here is8Bytes,x20Is stored inTeacherInstance object, the first instance object8A byte ismetadataFor the time of,x8The register holds the instance object’smetadata;
  • ldr x8, [x8, #0x50]: Registerx8Address offset in0x50And then store the offset address inx8;
  • blr x8: jump tox8The address of the register, which we can see by printingx8isteachMethods;

The call process of teach method: first find the metadata of the instance object, and then find the method of the instance object by offsetting the metadata address to a certain size.

Function table

What if there are multiple methods? Let’s add two more methods teach1 and teach2, and then call these two methods as well, to look at the assembly code:

We can see that three method calls correspond to three BLR x8, and the addresses of the three methods differ by 8 bytes, i.e. the size of a function pointer. In memory, the memory addresses of the three functions are contiguous; Then the scheduling of functions in Swift is based on the function table.

Next, let’s verify with the SIL file:

Through the generated SIL, it can be found that Teacher’s three methods are stored in the sil_vtable, which is the function table of the class.

TargetClassDescriptor

We have analyzed the structure of Metadata as follows:

struct Metadata{
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int.Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer
    var iVarDestroyer: UnsafeRawPointer
}
Copy the code

So in this structure we need to notice that the property of a typeDescriptor, whether it’s Class, Struct, or Enum has its own Descriptor, which is a detailed description of the Class; The type descriptor is TargetClassDescriptor:

By analyzing the TargetClassDescriptor and its inheritance relationship, the data structure of TargetClassDescriptor can be roughly as follows:

struct TargetClassDescriptor {
    ContextDescriptorFlags Flags;
    TargetRelativeContextPointer<Runtime> Parent;
    TargetRelativeDirectPointer<Runtime, const char./*nullable*/ false> Name;
    TargetRelativeDirectPointer<Runtime, MetadataResponse(...). ./*Nullable*/ true> AccessFunctionPtr;
    TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
                              /*nullable*/ true> Fields;
    TargetRelativeDirectPointer<Runtime, const char> SuperclassType;
    uint32_t MetadataNegativeSizeInWords;
    uint32_t MetadataPositiveSizeInWords;
    uint32_t NumImmediateMembers;
    uint32_t NumFields;
    uint32_t FieldOffsetVectorOffset;
}
Copy the code

There are no vtable-related attributes in it; We searched the whole text for TargetClassDescriptor and found that the class had an alias:

So in the source code we search for ClassDescriptor, we search the full text in the source code and we find too much, so how do we find that? GenMeta. CPP file name = GenMeta. CPP file name = GenMeta.

We can locate the class ClassContextDescriptorBuilder this is the founder of the description of the current class; In the layout method of this class we can see the following implementation:

First let’s look at the super::layout implementation:

As you can see, here we’re creating the structure of the TargetClassDescriptor that we analyzed before, and assigning the property; Next, let’s look at the implementation of the addVTable method:

We’re going to hazard a guess that B here is our structure TargetClassDescriptor, and that structure doesn’t have to complete:

struct TargetClassDescriptor {
    ContextDescriptorFlags Flags;
    TargetRelativeContextPointer<Runtime> Parent;
    TargetRelativeDirectPointer<Runtime, const char./*nullable*/ false> Name;
    TargetRelativeDirectPointer<Runtime, MetadataResponse(...). ./*Nullable*/ true> AccessFunctionPtr;
    TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
                              /*nullable*/ true> Fields;
    TargetRelativeDirectPointer<Runtime, const char> SuperclassType;
    uint32_t MetadataNegativeSizeInWords;
    uint32_t MetadataPositiveSizeInWords;
    uint32_t NumImmediateMembers;
    uint32_t NumFields;
    uint32_t FieldOffsetVectorOffset;
    uint32_t offset;
    uint32_t size;
    // Next is vtable
}
Copy the code

So what if we verify that the above conclusion is true? In the next article we verify this conclusion with the MachO file;