「オブジェクト」の根底にある原理に関するSwiftの詳細な分析

Swiftコンパイルの概要
  • Swiftコンパイル環境の構成とコンパイルプロセスについては、以前のブログを参照してください。Swiftソースコードのコンパイル環境のセットアップとコンパイルプロセス
  • 次のように、新しいSwiftプロジェクトを作成し、main.swiftにYDWTeacherクラスを作成し、デフォルトの初期化子を使用してインスタンスオブジェクトを作成し、それをtに割り当てます。
	class YDWTeacher {
    
     
		var age: Int = 18 
		var name: String = "YDW"  
	} 
	let t = YDWTeacher()
  • 次に、次のように、ターミナルで抽象構文ツリーを表示します:swiftc -dump-astmain.swift。

ここに画像の説明を挿入

  • 次に検討するのは、この初期化子がどのような操作を行うかです。そこで、SIL(Swift中間言語)を導入します。

  • iOS開発言語は、OCであろうとSwiftであろうと、以下に示すように、最下層がLLVMによってコンパイルされて.o実行可能ファイルが生成されます。
    ここに画像の説明を挿入

  • 見やすいです:

    • OCはclangコンパイラを介してIRにコンパイルされ、実行可能ファイル.o(つまりマシンコード)が生成されます。
    • swiftでは、swiftcコンパイラを使用してIRにコンパイルしてから、実行可能ファイルを生成します。
  • 別の見方をしましょう:Swiftファイルのコンパイルプロセスを通過するステップは次のとおりです。
    ここに画像の説明を挿入

  • 以下はSwiftでのコンパイルプロセスです。SIL(Swift Intermediate Language)は、Swiftコンパイルプロセスの中間コードであり、主にSwiftコードのさらなる分析と最適化に使用されます。次の図に示すように、SILはASTとLLVMIRの間にあります。

ここに画像の説明を挿入

  • SwiftとOCの違いは、Swiftが高レベルのSILを生成することです。コンパイル中にSwiftが使用するフロントエンドコンパイラはSwiftcであり、以前のOCで使用されていたclangとは異なります。
  • swiftc -h terminalコマンドを使用して、swiftcで何ができるかを確認します。

ここに画像の説明を挿入

  • 分析の説明:
    • -dump-ast構文と型チェック、AST構文ツリーの出力
    • -ダンプ-構文解析チェック、AST構文ツリーの出力
    • -dump-pcmプリコンパイルされたClangモジュールに関するデバッグ情報をダンプします
    • -dump-scope-mapsexpanded-or-list-of-line:column
      入力ファイルを解析してタイプチェックし、スコープマップをダンプします。
    • -dump-type-in​​foインポートされたすべてのモジュールから固定サイズタイプのYAMLダンプを出力します
    • -dump-type-refinement-contexts
      タイプチェック入力ファイルとダンプタイプリファインメントコンテキスト
    • -emit-assembly Emitアセンブリファイル(-S)
    • -emit-bcはLLVMBCファイルを出力します
    • -emit-executableは実行可能ファイルを出力します
    • -emit-imported-modulesは、インポートされたモジュールのリストを表示します
    • -emit-ir showIR中間コード
    • -emit-libraryはdylibダイナミックライブラリを出力します
    • -emit-objectは.oマシンファイルを出力します
    • -emit-pcmモジュールマップからプリコンパイルされたClangモジュールを出力します
    • -emit-sibgenは.sibの元のSILファイルを出力します
    • -emit-sibは.sib標準SILファイルを出力します
    • -emit-silgenは元のSILファイルを表示します
    • -emit-silは標準のSILファイルを表示します
    • -index-fileソースファイルのインデックスデータを生成します
    • -解析ファイルの解析
    • -print-astはファイルを解析し、(きれい/簡潔な)構文木を印刷します
    • -resolve-importsインポートによってインポートされたファイルを解決します
    • -typecheckチェックファイルタイプ
SIL
1. SIL分析とは何ですか?
  • SILはswiftの型システムと宣言に依存しているため、SIL構文はswiftの拡張です。silファイルは、SIL定義が追加された迅速なソースファイルです。
  • SILファイルには暗黙的なインポートはありません。swiftまたはBuildinの標準コンポーネントを使用する場合は、明示的にインポートする必要があります。
  • SIL関数は、1つ以上のブロックで構成されます。ブロックは、命令の線形シーケンスです。各ブロックの最後の命令は、制御を別のブロックに移すか、関数から戻ります。
  • SILの内容を詳細に調べたい場合は、2015LLVM開発者会議を参照してください。
2.SIL分析ミアン関数
  • 抽象構文ツリーを表示した後、引き続きターミナルでswiftc -emit-sil main.swift >> ./main.sil && code main.silコマンドを呼び出して、main.silファイルを生成します。
  • VSCodeでSILファイルを開きます。
// main
//`@main`:标识当前main.swift的`入口函数`,SIL中的标识符名称以`@`作为前缀
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
    
    
//`%0、%1` 在SIL中叫做寄存器,可以理解为开发中的常量,一旦赋值就不可修改,如果还想继续使用,就需要不断的累加数字(注意:这里的寄存器,与`register read`中的寄存器是有所区别的,这里是指`虚拟寄存器`,而`register read`中是`真寄存器`)
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
//`alloc_global`:创建一个`全局变量`,即代码中的`t`
  alloc_global @$s4main1tAA10YDWTeacherCvp        // id: %2
//`global_addr`:获取全局变量地址,并赋值给寄存器%3
  %3 = global_addr @$s4main1tAA10YDWTeacherCvp : $*YDWTeacher // user: %7
//`metatype`获取`YDWTeacher`的`MetaData`赋值给%4
  %4 = metatype $@thick YDWTeacher.Type           // user: %6
//将`__allocating_init`的函数地址赋值给 %5
  // function_ref YDWTeacher.__allocating_init()
  %5 = function_ref @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher // user: %6
//`apply`调用 `__allocating_init` 初始化一个变量,赋值给%6
  %6 = apply %5(%4) : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher // user: %7
//将%6的值存储到%3,即全局变量的地址(这里与前面的%3形成一个闭环)
  store %6 to %3 : $*YDWTeacher                   // id: %7
//构建`Int`,并`return`
  %8 = integer_literal $Builtin.Int32, 0          // user: %9
  %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
  return %9 : $Int32                              // id: %10
} // end sil function 'main'
  • 分析:
    • @mainこれは、現在のmain.swift関数を識別し、SILの識別子名の前に@が付きます。
    • %0、%1 ...はSILではレジスタとも呼ばれ、日常の開発では定数として理解でき、割り当てられた後は変更できません。SILが引き続き使用される場合、値は継続的に使用されます。蓄積されました。同時に、ここで説明するレジスタは仮想であり、最終的には実際のレジスタを使用してマシン上で実行されます。
    • alloc_gobal:グローバル変数を作成します。
    • global_addr:グローバル変数のアドレスを取得して%3に割り当てます。
    • MetatypeはYDWTeacherのメタデータを取得し、それを%4に割り当て、__ allocating_initの関数アドレスを%5に割り当てます。
    • __applyは__allocating_initを呼び出し、値を%6に返します。
    • %6の値を%3(つまり、作成したばかりのグローバル変数のアドレス)に格納します。
    • Intを作成し、戻ります。
  • 注:codeコマンドは.zshrcで次のように構成されています。端末でソフトウェアを指定して、対応するファイルを開くことができます。
$ open .zshrc
// ****** 添加以下别名
alias subl='/Applications/SublimeText.app/Contents/SharedSupport/bin/subl'
alias code='/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code'

// ****** 使用
$ code main.sil

// 如果想SIL文件高亮,需要安装插件:VSCode SIL
  • SILファイルから、コードが難読化されており、次のコマンドで復元できることがわかります。例としてs4main1tAA10YDWTeacherCvpを取り上げます。xcrunswift-demangles4main1tAA10YDWTeacherCvp、結果は次のとおりです。
	xcrun swift-demangle s4main1tAA10YDWTeacherCvp
	$s4main1tAA10YDWTeacherCvp ---> main.t : main.YDWTeacher
  • SILファイルでs4main10YDWTeacherCACycfCを検索します。その内部実装は、主にメモリの割り当てと変数の初期化です。
    • allocing_ref:YDWTeacherのインスタンスを作成します。現在のインスタンスの参照カウントは1です。
    • initメソッドを呼び出します。
	// ********* main入口函数中代码 *********
	%5 = function_ref @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher 
	
	// s4main10YDWTeacherCACycfC 实际就是__allocating_init()
	// YDWTeacher.__allocating_init()
	sil hidden [exact_self_class] @$s4main10YDWTeacherCACycfC : $@convention(method) (@thick YDWTeacher.Type) -> @owned YDWTeacher {
    
    
	// %0 "$metatype"
	bb0(%0 : $@thick YDWTeacher.Type):
	// 堆上分配内存空间
	%1 = alloc_ref $YDWTeacher                      // user: %3
	// function_ref YDWTeacher.init() 初始化当前变量
	%2 = function_ref @$s4main10YDWTeacherCACycfc : $@convention(method) (@owned YDWTeacher) -> @owned YDWTeacher // user: %3
	// 返回
	%3 = apply %2(%1) : $@convention(method) (@owned YDWTeacher) -> @owned YDWTeacher // user: %4
	return %3 : $YDWTeacher                         // id: %4
	} // end sil function '$s4main10YDWTeacherCACycfC'
  • SIL言語は、Swiftソースコードの分析にとって非常に重要です。文法情報の詳細については、Swift Intermediate Language(SIL)を参照してください
シンボリックブレークポイントのデバッグ
  • TestSwiftプロジェクトで「__allocating_init」シンボルブレークポイントを設定します。

ここに画像の説明を挿入

  • 次に実行すると、次のことがわかります。内部呼び出しはswift_allocObjectです。

ここに画像の説明を挿入

ソースコード分析
  • 以下に示すように、VSCodeのREPL(Pythonと同様のコマンドインタラクションライン、ここにコードを記述できます)に次のコード(コピーも可能)を記述し、* _ swift_allocObject関数を検索して、ブレークポイントを追加します。

ここに画像の説明を挿入

  • 次に、インスタンスオブジェクトtを初期化し、Enterキーを押します。

ここに画像の説明を挿入

  • ここのローカルから見ることができます:requiredSizeはメモリサイズ、requiredAlignmentMaskはメモリアライメント、requiredAlignmentMaskはswiftのバイトアライメント、これはOCと同じで、8の倍数である必要があり、不十分なものは自動的になります目的は、メモリ操作の効率を向上させるためにスペースを時間と交換することです。
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
    
    
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}
  • swift_allocObjectのソースコードは次のとおりで、主に次のように分類されます。
    • swift_slowAllocを介してメモリを割り当て、メモリバイトアラインメントを実行します。
    • new + HeapObject +メタデータを介してインスタンスオブジェクトを初期化します。
    • 関数の戻り値はHeapObject型であるため、現在のオブジェクトのメモリ構造はHeapObjectのメモリ構造です。
  • swift_slowAlloc関数を入力します。その内部は、主にmallocを介してヒープ内のメモリスペースのサイズを割り当て、主にインスタンス変数を格納するために使用されるメモリアドレスを返します。
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
    
    
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  if (alignMask <= MALLOC_ALIGN_MASK) {
    
    
#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
	// 堆中创建size大小的内存空间,用于存储实例变量
    p = malloc(size);
#endif
  } else {
    
    
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}
  • HeapObject初期化メソッドを入力するには、メタデータ、refCountsの2つのパラメーターが必要です。
struct HeapObject {
    
    
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

#ifndef __swift__
  HeapObject() = default;

  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  {
    
     }
  
  // Initialize a HeapObject header for an immortal object
  constexpr HeapObject(HeapMetadata const *newMetadata,
                       InlineRefCounts::Immortal_t immortal)
  : metadata(newMetadata)
  , refCounts(InlineRefCounts::Immortal)
  {
    
     }
  • 分析:
    • メタデータ型はHeapMetadataで、これはポインター型であり、8バイトを占有します。
    • refCounts(参照カウント、タイプはInlineRefCounts、InlineRefCountsはクラスRefCountsのエイリアスで、8バイトを占有)、swiftはアーク参照カウントを使用します。
総括する
  • インスタンスオブジェクトtの場合、その本質は、デフォルトのメモリサイズが16バイト(メタデータ8バイト+ refCounts 8バイト)のHeapObject構造です。OCとの比較は次のとおりです。
    • OCのインスタンスオブジェクトの本質は、objc_objectからテンプレートとして継承される構造体です。この構造体には、8バイトを占めるisaポインタがあります。
    • Swiftには、デフォルトでOCよりも1つ多いインスタンスオブジェクトがあります。refCounted参照カウントサイズ。デフォルトの属性は16バイトを占めます。
  • Swiftのオブジェクトのメモリ割り当てプロセスは次のとおりです。_allocating_init- > swift_allocObject-> _swift_allocObject-> swift_slowAlloc-> malloc;
  • initの責任は変数を初期化することであり、これはOCと一致しています。

おすすめ

転載: blog.csdn.net/Forever_wj/article/details/112001532