
Kami terus mempelajari kompiler Swift. Bagian ini didedikasikan untuk Bahasa Swift Intermediate.
Jika Anda belum melihat yang sebelumnya, saya sarankan Anda mengikuti tautan dan membaca:
Silgen
Langkah selanjutnya adalah mengubah AST yang diketik menjadi SIL mentah. Swift Intermediate Language (SIL) adalah representasi perantara yang dibuat khusus untuk Swift. Deskripsi semua instruksi dapat ditemukan dalam dokumentasi .
SIL memiliki bentuk SSA. Static Single Assignment (SSA) - Representasi kode di mana setiap variabel hanya diberi nilai satu kali. Itu dibuat dari kode biasa dengan menambahkan variabel tambahan. Misalnya, menggunakan sufiks numerik yang menunjukkan versi variabel setelah setiap penugasan.
Berkat formulir ini, kompiler lebih mudah mengoptimalkan kode. Di bawah ini adalah contoh pada pseudo-code. Jelas, baris pertama tidak perlu:
a = 1 a = 2 b = a
Tapi ini hanya untuk kita. Untuk mengajarkan kompiler untuk menentukan ini, kita harus menulis algoritma non-trivial. Tetapi dengan SSA, ini jauh lebih mudah. Sekarang, bahkan untuk kompiler sederhana, akan jelas bahwa nilai variabel a1 tidak digunakan, dan baris ini dapat dihapus:
a1 = 1 a2 = 2 b1 = a2
SIL memungkinkan Anda untuk menerapkan optimasi dan pemeriksaan khusus ke kode Swift yang akan sulit atau tidak mungkin untuk diselesaikan pada fase AST.
Menggunakan SIL Generator
Untuk menghasilkan SIL, gunakan flag -emit-silgen :
swiftc -emit-silgen main.swift
Hasil dari perintah:
sil_stage raw import Builtin import Swift import SwiftShims let x: Int // x sil_global hidden [let] @$S4main1xSivp : $Int // main sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): alloc_global @$S4main1xSivp // id: %2 %3 = global_addr @$S4main1xSivp : $*Int // user: %8 %4 = metatype $@thin Int.Type // user: %7 %5 = integer_literal $Builtin.Int2048, 16 // user: %7 // function_ref Int.init(_builtinIntegerLiteral:) %6 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %7 %7 = apply %6(%5, %4) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %8 store %7 to [trivial] %3 : $*Int // id: %8 %9 = integer_literal $Builtin.Int32, 0 // user: %10 %10 = struct $Int32 (%9 : $Builtin.Int32) // user: %11 return %10 : $Int32 // id: %11 } // end sil function 'main' // Int.init(_builtinIntegerLiteral:) sil [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int
SIL, seperti LLVM IR, dapat di-output sebagai kode sumber. Anda dapat menemukan di dalamnya bahwa pada tahap ini impor modul Swift Builtin, Swift dan SwiftShims telah ditambahkan.
Terlepas dari kenyataan bahwa kode Swift dapat ditulis langsung dalam lingkup global, SILGen menghasilkan fungsi utama - titik masuk ke program. Semua kode terletak di dalamnya, kecuali untuk menyatakan konstanta, karena bersifat global dan harus dapat diakses di mana saja.
Sebagian besar garis memiliki struktur yang serupa. Di sebelah kiri adalah pseudo-register di mana hasil instruksi disimpan. Berikutnya - instruksi itu sendiri dan parameternya, dan pada akhirnya - sebuah komentar yang menunjukkan register yang akan digunakan register ini.
Misalnya, bilangan bulat tipe Int2048 dan nilai 16. dibuat pada baris ini.Literal ini disimpan dalam register kelima dan akan digunakan untuk menghitung nilai ketujuh:
%5 = integer_literal $Builtin.Int2048, 16 // user: %7
Deklarasi fungsi dimulai dengan kata kunci sil. Berikut ini adalah nama dengan @ awalan, konvensi pemanggilan, parameter, tipe pengembalian, dan kode fungsi. Untuk initializer Int.init (_builtinIntegerLiteral :), tentu saja tidak ditentukan, karena fungsi ini dari modul lain, dan hanya perlu dideklarasikan, tetapi tidak didefinisikan. Tanda dolar menunjukkan awal dari indikasi jenis:
// Int.init(_builtinIntegerLiteral:) sil [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int
Konvensi panggilan menunjukkan cara memanggil fungsi dengan benar. Ini diperlukan untuk menghasilkan kode mesin. Penjelasan rinci tentang prinsip-prinsip ini berada di luar cakupan artikel ini.
Nama inisialisasi, serta nama struktur, kelas, metode, protokol, terdistorsi (nama mangling). Ini memecahkan beberapa masalah sekaligus.
Pertama, memungkinkan menggunakan nama yang sama di modul yang berbeda dan entitas bersarang. Misalnya, untuk metode pertama fff , nama S4main3AAAV3fffSiyF digunakan , dan untuk yang kedua, S4main3BBBVVffffSiyF digunakan :
struct AAA { func fff() -> Int { return 8 } } struct BBB { func fff() -> Int { return 8 } }
S berarti Swift, 4 adalah jumlah karakter dalam nama modul, dan 3 adalah dalam nama kelas. Dalam initializer literal, Si menunjukkan tipe standar Swift.Int.
Kedua, nama dan tipe argumen fungsi ditambahkan ke namanya. Ini memungkinkan penggunaan kelebihan. Misalnya, untuk metode pertama, S4main3AAAV3fff3iiiS2i_tF dihasilkan, dan untuk yang kedua - S4main3AAAV3fff3dddSiSd_tF :
struct AAA { func fff(iii internalName: Int) -> Int { return 8 } func fff(ddd internalName: Double) -> Int { return 8 } }
Setelah nama parameter, jenis nilai kembali ditunjukkan, diikuti oleh jenis parameter. Namun, nama internal mereka tidak ditunjukkan. Sayangnya, tidak ada dokumentasi tentang nama mangling di Swift, dan implementasinya dapat berubah sewaktu-waktu.
Nama fungsi diikuti oleh definisinya. Ini terdiri dari satu atau lebih blok dasar. Blok dasar adalah urutan instruksi dengan satu titik masuk, satu titik keluar, yang tidak mengandung instruksi atau ketentuan cabang untuk keluar awal.
Fungsi utama memiliki satu unit dasar, yang mengambil semua parameter yang diteruskan ke fungsi sebagai input dan berisi semua kodenya, karena tidak ada cabang di dalamnya:
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
Kita dapat mengasumsikan bahwa setiap ruang lingkup yang dibatasi oleh kawat gigi adalah unit dasar yang terpisah. Misalkan kode berisi cabang:
// before if 2 > 5 { // true } else { // false } // after
Dalam hal ini, setidaknya 4 blok dasar akan dihasilkan untuk:
- kode sebelum bercabang,
- kasus ketika ekspresi itu benar
- kasus ketika ekspresi salah
- kode setelah bercabang.
cond_br - instruksi untuk lompatan bersyarat. Jika nilai pseudo-register% 14 benar, maka transisi ke blok bb1 dilakukan . Jika tidak, maka di bb2 . br - lompatan tanpa syarat yang memulai eksekusi dari blok dasar yang ditentukan:
// before cond_br %14, bb1, bb2 // id: %15 bb1: // true br bb3 // id: %21 bb2: // Preds: bb0 // false br bb3 // id: %27 bb3: // Preds: bb2 bb1 // after
Kode Sumber:
Representasi perantara mentah yang diperoleh pada tahap terakhir dianalisis untuk kebenaran dan diubah menjadi kanonik: fungsi yang ditandai transparan adalah inline (pemanggilan fungsi diganti oleh tubuhnya), nilai-nilai ekspresi konstan dihitung, fungsi diperiksa bahwa fungsi yang mengembalikan nilai-nilai lakukan ini di semua cabang kode dan sebagainya.
Konversi ini wajib dan dilakukan bahkan jika optimasi kode dinonaktifkan.
Canon SIL Generation
Untuk menghasilkan SIL kanonik, flag -emit-sil digunakan:
swiftc -emit-sil main.swift
Hasil dari perintah:
sil_stage canonical import Builtin import Swift import SwiftShims let x: Int // x sil_global hidden [let] @$S4main1xSivp : $Int // main sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): alloc_global @$S4main1xSivp // id: %2 %3 = global_addr @$S4main1xSivp : $*Int // user: %6 %4 = integer_literal $Builtin.Int64, 16 // user: %5 %5 = struct $Int (%4 : $Builtin.Int64) // user: %6 store %5 to %3 : $*Int // id: %6 %7 = integer_literal $Builtin.Int32, 0 // user: %8 %8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9 return %8 : $Int32 // id: %9 } // end sil function 'main' // Int.init(_builtinIntegerLiteral:) sil public_external [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int { // %0 // user: %2 bb0(%0 : $Builtin.Int2048, %1 : $@thin Int.Type): %2 = builtin "s_to_s_checked_trunc_Int2048_Int64"(%0 : $Builtin.Int2048) : $(Builtin.Int64, Builtin.Int1) // user: %3 %3 = tuple_extract %2 : $(Builtin.Int64, Builtin.Int1), 0 // user: %4 %4 = struct $Int (%3 : $Builtin.Int64) // user: %5 return %4 : $Int // id: %5 } // end sil function '$SSi22_builtinIntegerLiteralSiBi2048__tcfC'
Ada beberapa perubahan dalam contoh sederhana. Untuk melihat karya pengoptimal yang sebenarnya, Anda perlu sedikit menyulitkan kode. Misalnya, tambahkan tambahan:
let x = 16 + 8
Dalam SIL mentahnya, Anda dapat menemukan penambahan literal ini:
%13 = function_ref @$SSi1poiyS2i_SitFZ : $@convention(method) (Int, Int, @thin Int.Type) -> Int // user: %14 %14 = apply %13(%8, %12, %4) : $@convention(method) (Int, Int, @thin Int.Type) -> Int // user: %15
Tetapi dalam kanonik itu tidak ada lagi. Sebagai gantinya, nilai konstan 24 digunakan:
%4 = integer_literal $Builtin.Int64, 24 // user: %5
Kode Sumber:
Optimasi Sil
Transformasi Swift-spesifik tambahan diterapkan jika optimisasi diaktifkan. Diantaranya adalah spesialisasi generik (mengoptimalkan kode generik untuk jenis parameter tertentu), devirtualization (mengganti panggilan dinamis dengan yang statis), inlining, optimasi ARC, dan banyak lagi. Penjelasan tentang teknik ini tidak cocok dengan artikel yang sudah terlalu banyak ditumbuhi.
Kode Sumber:
Karena SIL adalah fitur Swift, saya tidak menunjukkan contoh implementasi saat ini. Kami akan kembali ke kompiler tanda kurung di bagian berikutnya ketika kami akan terlibat dalam generasi IR LLVM.