With the stability of the Swift ABI (release of Swift4.0), more and more companies are moving to Swift or Swift/OC co-development.

Before moving to Swift, most developers were familiar with Objective-C(OC for short), Object C is literally Object oriented C language, yes OC is essentially C language but with the Runtime it is Object oriented and dynamic, if you want to understand OC and use it well, You must understand how the Runtime works.

Swift is a static language because of its functional programming, and it is also a dynamic language because it is object-oriented and compatible with the OC Runtime mechanism. In fact, both statements are true, it is both static and dynamic.

First we want to look at some interesting code

A protocol ProtocolA {func test1()} extension ProtocolA {func test1() {print("ProtocolA - test1")}  struct StructB { } extension StructB: ProtocolA { func test1() { print("StructB - test1") } } let p1: ProtocolA = StructB() let s1: Structb-test1 () // print: "structb-test1" s1.text1()Copy the code

This code should all be fine, as everyone understands that the actual caller is the method’s ultimate executor, so Swift is indeed a dynamic language.

Then look at the code below

Protocol ProtocolA {} Extension ProtocolA {func test2() {print("ProtocolA - test2")}} struct StructB { } extension StructB: ProtocolA { func test2() { print("StructB - test2") } } let p1: ProtocolA = StructB() let s1: StructB = StructB() p1.test2() s1.test2()Copy the code

Results of the above execution:

ProtocolA – test2

StructB – test2

Feeling is not very magical, in fact, this is the Swift feature (dynamic and static dual characteristics), when is dynamic and when is static, then from three aspects (message distribution type, message distribution principle, and how to verify the message distribution mechanism) to analyze the Swift message distribution mechanism.

I. Message distribution type:

Generally speaking, there are dynamic and static distribution methods. In Swift, there are not only two distribution methods (static and dynamic), but four distribution methods:

  1. Inline inline (fastest)

  2. Static Dispatch

  3. Dynamically Dispatch Virtual Dispatches

  4. Dynamic Dispatch (slowest)

It is up to the compiler to decide which distribution method should be used, using inline first and then choosing on demand.

Ps: Inline distribution can be understood as directly running the code block in the function without function address jump

Static distribution:

It is very fast because the compiler is able to determine the location of instructions at compile time. Therefore, when a function is called, the compiler jumps directly to the function’s memory address to perform the operation. This leads to huge performance gains and certain compiler optimizations, such as inlining.

Dynamic distribution:

  1. Table Dispatch

This is the most common distribution method for compiled languages, which ensures both dynamic and efficient execution. The class in which a function resides maintains a “function table,” also known as a virtual function table, known in Swift as a “Witness table.” The function table accesses Pointers to each function implementation. The Vtable for each class is built at compile time, so there are only two more reads: the vtable for the class and the pointer to the function. Theoretically, function table distribution is also an efficient way. However, the compiler is unable to optimize some functions with side effects compared to direct distribution, which also causes the slower distribution of function tables.

Jump principle:

Take the example of calling ChildClass method2

Source:

class ParentClass {
    func method1() {}
    func method2() {}
}
class ChildClass: ParentClass {
    override func method2() {}
    func method3() {}
}
Copy the code

The jump steps are as follows:

  1. Read 0xB00 vtable

  2. Reads the method2 function pointer 0x222

  3. Jump to address 0x222, read function implementation

  1. Message Dispatch

The messaging used in Swfit is still Objc’s runtime system.

The messaging mechanism is the most dynamic way to call functions and is the cornerstone of Cocoa.

All methods are basically done through the objc_msgSend(or super_msgSend) function, which passes self and selector as default arguments and uses isa Pointers to crawl the class hierarchy to determine which method to call.

The runtime mechanism is actually much more complex than this, with three steps:

  1. Message is sent

  2. Dynamic message parsing

  3. forward

It’s really slow. Fortunately, each class has a cache, and if the same message is later sent, the execution rate is very fast, increasing the performance to the same speed as the function table dispatch.

Ii. Principles of message distribution:

Principle:

Direct call

Function table

Message mechanism

Clear implementation

final, static

The dynamic (NSObject subclass)

Value types

All the methods

agreement

Methods in development

Defined methods

class

Methods in development

Defined methods

Extension with @objc

This means that methods extended in the Swift class cannot be overridden unless OC runtime mechanisms are used;

Those following expand the method here can be rewritten, but to expand the method or direct call, can’t follow implements a method is called the method of agreement, the following is true? Those following distributing see how to define if it is a direct call that is called directly, if it can be inherited the virtual function table is scheduling.

Decorator:

Static: indicates the static distribution

Fianl: Confirm static distribution

Dynamic: Determines the use of the message mechanism, which must ultimately inherit NSObject

Objc: Available for OC, not necessarily using messaging internally

@objC dynamic: Available for OC, sure to use message mechanism

Inlinable: tells the compiler to distribute this function directly, but converts it to SIL code and still distributes it as vtable

Class: class method function table distribution, if the fianl class class method may be optimized to static distribution

Refer to the modifier’s official documentation for details

Supplement:

  1. Swift optimizes the distribution of functions as much as possible. For example, when a private function is used, it is likely to be optimized for direct distribution

  2. Different versions of Swift message distribution mechanism have certain differences. After version 4.0, the overall distribution principle is similar, only some optimization, and there is no research significance if version differences are not discussed in detail.

How to verify message distribution mechanism

Code compilation process:

LLVM architecture:

LLVM is a compilation architecture, which mainly optimizes the source code of multiple languages to be converted into machine code of different architectures. When a new language is added, it only needs to add a front-end compiler (converted into LLVM IR code) to realize the conversion of different machine code. Conversely when adding a back end compiler when adding an LLVM IR code to machine code.

LLVM structure diagram:

LLVM official document

OC compilation

OC front-end code uses the CLANG compiler

OC source -> pre-processing into C++ code -> IR front-end code

Generate instructions:

$: clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOS.platform/Developer/SDKs/MacOS.sdk xxx.m
Copy the code

Xxx. m indicates the OC source file to be converted

If you don’t mind setting macro in ~/.bash_profile, for example:

alias ft_rwobjc='clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOS.platform/Developer/SDKs/MacOS.sdk'
Copy the code

The generation instruction is simplified to:

$: ft_rwobjc xxx.m
Copy the code

Swift front-end compilation

Swift uses the SwifTC compiler

Swift source code -> syntax tree AST -> intermediate code SIL -> IR front-end code

There are two forms of SIL, raw SIL (raw SIL) and Canonical SIL (canonical SIL). The non-optimized SIL that has just come out of SILGen is called a RAW SIL

Swift source to SIL command

Generate raw SIL:

swiftc -emit-silgen Source.swift -o Source.sil

Generate canonical SIL:

swiftc Source.swift -emit-sil > Source-canonical.sil

SIL grammar:

SIL Apple official documentation and source code

Truncated function convention part description

Here’s a quick translation

The convention of the function, represented by the @convention(convention) property:

  • @convention(thin) : indicates a lightweight method that has no special “self” or “context” argument

  • @convention(thick) : Represents a heavyweight method that uses the Swift calling convention and carries a reference-counting context object to indicate the capture or other state required by the function. This property is owned by @callee_owned or implied by @callee_guaranteed

  • @convention(block) : represents an Objective-C compatible block reference. The function value is represented as a reference to a Block object, an ID-compatible Objective-C object, into which its calling function is embedded. Calling functions uses the C calling convention

  • @convention(c) denotes a C function reference. Function values carry no context and use the C calling convention

  • @convention(objc_method) represents an objective-C method implementation. This function uses the C calling convention to map the SIL level self argument (mapped to the final parameter by the SIL convention) to the implemented self and _cmd arguments

  • @convention(method) : Swift instance method implementation. This function uses the Swift calling convention, using the special self argument

  • @convention(witness_method) : indicates the Swift protocol method implementation. The polymorphic convention for a function is emitted in such a way that it is guaranteed to be polymorphic among all possible implementers of the protocol

SIL optimization:

General optimizations capture language-specific type information through SIL, making it possible to perform advanced optimizations that are difficult to perform on LLVM IR.

  • Generic specialization analyzes specialized calls to generic functions and generates new specialized versions of the functions. It then rewrites all the specialized uses of generics as direct calls to the appropriate specialized functions.

  • Witness and VTable for a given type devirtualizes the associated methods from the class’s virtual table or type witness table, and replaces indirect virtual calls with calls to mapping functions.

  • Performance inline

  • Reference count optimization

  • Memory upgrade/optimization

  • SIL Advanced optimization

Based on these optimizations, Swift no longer supports dynamic modifications to classes after 4.0. Because Swift has too many optimizations, using dynamic modifications in Swift is equivalent to writing OC code, which makes no sense. I think this is why OC code cannot inherit from Swift.

Front-end intermediate code analysis:

OC:

OC source

@interface Student : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implementation Student
@end

int main() {
    Student *stu = [[Student alloc] init];
    stu.name = @"Good Student";
    NSLog(@"stu = %@", stu);
    return 0;
}
Copy the code

Transformation of the c + +

Implement part of the code

extern "C" unsigned long OBJC_IVAR_$_Student$_name;
struct Student_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */

// @implementation Student

static NSString * _I_Student_name(Student * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Student_setName_(Student * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Student, _name), (id)name, 0, 1); }
// @end
Copy the code

Message sending part of the code:

int main() {
    Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)stu, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_h__dw86ldt90kb41f_vmvkpss700000gn_T_oc_main_986940_mi_0);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_h__dw86ldt90kb41f_vmvkpss700000gn_T_oc_main_986940_mi_1, stu);
    return 0;
}
Copy the code

You can see that these object methods (including class methods) are implemented based on objc_msgSend (or super_msgSend).

Swift:

Start by defining a Student class and its subclasses

Class Student: NSObject {/// attribute var name: Final final func finalMethod() {} /// The virtual function calls func vtableMethod() {} /// the runtime method @objc dynamic func Class GoodStuden: Student {override func vtableMethod() {}}Copy the code

Function implementation part of the code:

// Student.finalMethod() sil hidden [ossa] @$s10swift_main7StudentC11finalMethodyyF : $@convention(method) (@guaranteed Student) -> () { // %0 "self" // user: %1 bb0(%0 : @guaranteed $Student): debug_value %0 : $Student, let, name "self", argno 1 // id: %1 %2 = tuple () // user: %3 return %2 : $() // id: %3 } // end sil function '$s10swift_main7StudentC11finalMethodyyF' // Student.vtableMethod() sil hidden [ossa] @$s10swift_main7StudentC12vtableMethodyyF : $@convention(method) (@guaranteed Student) -> () { // %0 "self" // user: %1 bb0(%0 : @guaranteed $Student): debug_value %0 : $Student, let, name "self", argno 1 // id: %1 %2 = tuple () // user: %3 return %2 : $() // id: %3 } // end sil function '$s10swift_main7StudentC12vtableMethodyyF' // Student.runTimeMethod() sil hidden [ossa] @$s10swift_main7StudentC13runTimeMethodyyF : $@convention(method) (@guaranteed Student) -> () { // %0 "self" // user: %1 bb0(%0 : @guaranteed $Student): debug_value %0 : $Student, let, name "self", argno 1 // id: %1 %2 = tuple () // user: %3 return %2 : $() // id: %3 } // end sil function '$s10swift_main7StudentC13runTimeMethodyyF' // @objc Student.runTimeMethod() sil hidden [thunk]  [ossa] @$s10swift_main7StudentC13runTimeMethodyyFTo : $@convention(objc_method) (Student) -> () { // %0 // user: %1 bb0(%0 : @unowned $Student): %1 = copy_value %0 : $Student // users: %6, %2 %2 = begin_borrow %1 : $Student // users: %5, %4 // function_ref Student.runTimeMethod() %3 = function_ref @$s10swift_main7StudentC13runTimeMethodyyF : $@convention(method) (@guaranteed Student) -> () // user: %4 %4 = apply %3(%2) : $@convention(method) (@guaranteed Student) -> () // user: %7 end_borrow %2 : $Student // id: %5 destroy_value %1 : $Student // id: %6 return %4 : $() // id: %7 } // end sil function '$s10swift_main7StudentC13runTimeMethodyyFTo'Copy the code

So all of these object methods are using @convention(method) and the Swift call convention internally needs to use ‘self’, fianl or virtual function dispatches and I can’t tell if I’m using the @convention(method) declaration. Need to cooperate with Vtable to see, will be mentioned later.

Student.runtimemethod uses the @convention(objc_method) runTimeMethod. Note that there is a default Swift implementation method of the same name, In objc_method, there is the apply method, which uses the Swift method because copy_value Destroy_value, a counter – dependent management schedule, executes a lot more supplementary code. Swift is optimized for reference counting, which is a big optimization if the function is large, right

@convention(witness_method) The type is witness_method.

Let’s take a look at protocol invocation

Source code:

Protocol StudyProtocol {/// func readBook() /// / write func writeArticle()} /// follow the protocol class Student: NSObject, StudyProtocol {func readBook() {} func writeArticle() {} Student {override func writeArticle() {/// Write articles}} // Protocol witness for studyprotocol.readbook () in conformance Student sil private [transparent] [thunk] [ossa] @$s10swift_main7StudentCAA13StudyProtocolA2aDP8readBookyyFTW : $@convention(witness_method: StudyProtocol) (@in_guaranteed Student) -> () { // %0 // user: %1 bb0(%0 : $*Student): %1 = load_borrow %0 : $*Student // users: %5, %3, %2 %2 = class_method %1 : $Student, #Student.readBook : (Student) -> () -> (), $@convention(method) (@guaranteed Student) -> () // user: %3 %3 = apply %2(%1) : $@convention(method) (@guaranteed Student) -> () %4 = tuple () // user: %6 end_borrow %1 : $Student // id: %5 return %4 : $() // id: %6 } // end sil function '$s10swift_main7StudentCAA13StudyProtocolA2aDP8readBookyyFTW' // protocol witness for StudyProtocol.writeArticle() in conformance Student sil private [transparent] [thunk] [ossa] @$s10swift_main7StudentCAA13StudyProtocolA2aDP12writeArticleyyFTW : $@convention(witness_method: StudyProtocol) (@in_guaranteed Student) -> () { // %0 // user: %1 bb0(%0 : $*Student): %1 = load_borrow %0 : $*Student // users: %5, %3, %2 %2 = class_method %1 : $Student, #Student.writeArticle : (Student) -> () -> (), $@convention(method) (@guaranteed Student) -> () // user: %3 %3 = apply %2(%1) : $@convention(method) (@guaranteed Student) -> () %4 = tuple () // user: %6 end_borrow %1 : $Student // id: %5 return %4 : $() // id: %6 } // end sil function '$s10swift_main7StudentCAA13StudyProtocolA2aDP12writeArticleyyFTW'Copy the code

The witness_method convention is used, followed by @in_guaranteed Student, which means that the Student is assigned a schedule. Protocol in the absence of any compliance, the intermediate code, at the same time can also verify that multiple compliance is not an implementation, the conclusion is that there will be one more

// protocol witness for StudyProtocol.readBook() in conformance Theacher
sil private [transparent] [thunk] [ossa] @$s10swift_main8TheacherCAA13StudyProtocolA2aDP8readBookyyFTW : $@convention(witness_method: StudyProtocol) (@in_guaranteed Theacher) -> () {
// %0                                             // user: %1
bb0(%0 : $*Theacher):
  %1 = load_borrow %0 : $*Theacher                // users: %5, %3, %2
  %2 = class_method %1 : $Theacher, #Theacher.readBook : (Theacher) -> () -> (), $@convention(method) (@guaranteed Theacher) -> () // user: %3
  %3 = apply %2(%1) : $@convention(method) (@guaranteed Theacher) -> ()
  %4 = tuple ()                                   // user: %6
  end_borrow %1 : $Theacher                       // id: %5
  return %4 : $()                                 // id: %6
} // end sil function '$s10swift_main8TheacherCAA13StudyProtocolA2aDP8readBookyyFTW'
Copy the code

The above code can be seen as having an extra (@in_guaranteed Theacher) implementation. Protocol methods are called type-specific even though the protocol is abstract, and different classes of methods are scheduled by type at compile time.

Virtual table code:

Mark these methods as distributed by virtual functions

sil_vtable Student { #Student.getStudentType: (Student.Type) -> () -> Int : @$s10swift_main7StudentC03getC4TypeSiyFZ // static Student.getStudentType() #Student.name! getter: (Student) -> () -> String : @$s10swift_main7StudentC4nameSSvg // Student.name.getter #Student.name! setter: (Student) -> (String) -> () : @$s10swift_main7StudentC4nameSSvs // Student.name.setter #Student.name! modify: (Student) -> () -> () : @$s10swift_main7StudentC4nameSSvM // Student.name.modify #Student.vtableMethod: (Student) -> () -> () : @$s10swift_main7StudentC12vtableMethodyyF // Student.vtableMethod() #Student.readBook: (Student) -> () -> () : @$s10swift_main7StudentC8readBookyyF // Student.readBook() #Student.writeArticle: (Student) -> () -> () : @$s10swift_main7StudentC12writeArticleyyF // Student.writeArticle() #Student.deinit! deallocator: @$s10swift_main7StudentCfD // Student.__deallocating_deinit } sil_vtable Theacher { #Theacher.readBook: (Theacher) -> () -> () : @$s10swift_main8TheacherC8readBookyyF // Theacher.readBook() #Theacher.writeArticle: (Theacher) -> () -> () : @$s10swift_main8TheacherC12writeArticleyyF // Theacher.writeArticle() #Theacher.init! allocator: (Theacher.Type) -> () -> Theacher : @$s10swift_main8TheacherCACycfC // Theacher.__allocating_init() #Theacher.deinit! deallocator: @$s10swift_main8TheacherCfD // Theacher.__deallocating_deinit } sil_vtable GoodStuden { #Student.getStudentType: (Student.Type) -> () -> Int : @$s10swift_main10GoodStudenC14getStudentTypeSiyFZ [override] // static GoodStuden.getStudentType() #Student.name! getter: (Student) -> () -> String : @$s10swift_main7StudentC4nameSSvg [inherited] // Student.name.getter #Student.name! setter: (Student) -> (String) -> () : @$s10swift_main7StudentC4nameSSvs [inherited] // Student.name.setter #Student.name! modify: (Student) -> () -> () : @$s10swift_main7StudentC4nameSSvM [inherited] // Student.name.modify #Student.vtableMethod: (Student) -> () -> () : @$s10swift_main10GoodStudenC12vtableMethodyyF [override] // GoodStuden.vtableMethod() #Student.readBook: (Student) -> () -> () : @$s10swift_main7StudentC8readBookyyF [inherited] // Student.readBook() #Student.writeArticle: (Student) -> () -> () : @$s10swift_main10GoodStudenC12writeArticleyyF [override] // GoodStuden.writeArticle() #GoodStuden.deinit! deallocator: @$s10swift_main10GoodStudenCfD // GoodStuden.__deallocating_deinit } sil_witness_table hidden Student: StudyProtocol module swift_main { method #StudyProtocol.readBook: <Self where Self : StudyProtocol> (Self) -> () -> () : @$s10swift_main7StudentCAA13StudyProtocolA2aDP8readBookyyFTW // protocol witness for StudyProtocol.readBook() in conformance Student method #StudyProtocol.writeArticle: <Self where Self : StudyProtocol> (Self) -> () -> () : @$s10swift_main7StudentCAA13StudyProtocolA2aDP12writeArticleyyFTW // protocol witness for StudyProtocol.writeArticle() in conformance Student } sil_witness_table hidden Theacher: StudyProtocol module swift_main { method #StudyProtocol.readBook: <Self where Self : StudyProtocol> (Self) -> () -> () : @$s10swift_main8TheacherCAA13StudyProtocolA2aDP8readBookyyFTW // protocol witness for StudyProtocol.readBook() in conformance Theacher method #StudyProtocol.writeArticle: <Self where Self : StudyProtocol> (Self) -> () -> () : @$s10swift_main8TheacherCAA13StudyProtocolA2aDP12writeArticleyyFTW // protocol witness for StudyProtocol.writeArticle() in conformance Theacher }Copy the code

You can see that each class maintains its own function table, and the fianl or @objc modifier is not added to the virtual function table. Every class has a deinit method;

Each compliance protocol has a function table (sil_witness_table). The implementation points to the implementation in the compliance sil_vtable.

I’ve put hidden in here and this is actually a function table that’s not going to be called but it’s just an auxiliary code, look at the SIL code that’s actually scheduled

Source:

Let stu: Student = GoodStuden()Copy the code

SIL code:

%0 = metatype $@thick GoodStuden.Type           // user: %2
// function_ref GoodStuden.__allocating_init()
%1 = function_ref @$s10swift_main10GoodStudenCACycfC : $@convention(method) (@thick GoodStuden.Type) -> @owned GoodStuden // user: %2
%2 = apply %1(%0) : $@convention(method) (@thick GoodStuden.Type) -> @owned GoodStuden // user: %3
%3 = upcast %2 : $GoodStuden to $Student        // users: %13, %5, %4
debug_value %3 : $Student, let, name "stu"      // id: %4
%5 = begin_borrow %3 : $Student                 // users: %8, %7, %6
%6 = class_method %5 : $Student, #Student.writeArticle : (Student) -> () -> (), $@convention(method) (@guaranteed Student) -> () // user: %7
%7 = apply %6(%5) : $@convention(method) (@guaranteed Student) -> ()
end_borrow %5 : $Student                        // id: %8
Copy the code

This appears to be Student scheduling, but it’s virtual dispatching, and finally GoodStudent.

Why is it not GoodStudent, because there is a type qualification, and if you set the type qualification, the SIL code becomes GoodStudent

%0 = metatype $@thick GoodStuden.Type           // user: %2
// function_ref GoodStuden.__allocating_init()
%1 = function_ref @$s10swift_main10GoodStudenCACycfC : $@convention(method) (@thick GoodStuden.Type) -> @owned GoodStuden // user: %2
%2 = apply %1(%0) : $@convention(method) (@thick GoodStuden.Type) -> @owned GoodStuden // users: %12, %4, %3
debug_value %2 : $GoodStuden, let, name "stu"   // id: %3
%4 = begin_borrow %2 : $GoodStuden              // users: %7, %6, %5
%5 = class_method %4 : $GoodStuden, #GoodStuden.writeArticle : (GoodStuden) -> () -> (), $@convention(method) (@guaranteed GoodStuden) -> () // user: %6
%6 = apply %5(%4) : $@convention(method) (@guaranteed GoodStuden) -> ()
Copy the code

Iv. Summary:

General rules:

  • Swift writes functions mostly in static methods, which is why Swift is fast

  • Protocol inheritance and class inheritance ensure that object polymorphism is dynamically distributed using virtual function tables

  • The object inherited from NSObject is sent through the message mechanism using the dynamic/ @objc Dynamic keyword

  • Different versions of Swift, DEBUG and release will have different levels of compilation optimization, distribution mechanism will also be different

Development Suggestions:

  1. Value types exist wherever they are available, not only because they are fast to copy, but also because method scheduling is fast

  2. More use of private final keywords, on the one hand to improve code reading, the compiler internal message scheduling optimization

  3. Code classification uses extensions where methods are distributed statically (except as run-time methods)

  4. The protocol to comply with (in this case the Swift protocol) should be written in the extension if it is desired to be overridden by subclasses. It is recommended not to use polymorphisms of classes, but to use protocols for abstraction. Methods requiring attributes and multiple implementations are extracted into protocols to expand and implement some common methods. This not only removes the dependency on the parent class but also enables’ multiple inheritance ‘

  5. When OC is mixed, frameworks that use some OC features (such as KVO) require not only @objC declarations for attributes or methods, but also dynamic modifications for them to behave as expected

V. References:

Swift function distribution mechanism

Trinhngocthuyen. Making. IO/posts/tech /…

Swift/sil.rst at main · Apple/Swift · GitHub