جهاز التحويل البرمجي السريع. الجزء 3


نواصل دراسة المترجم سويفت. هذا الجزء مخصص للغة سويفت المتوسطة.


إذا لم تكن قد رأيت السابقة ، نوصيك باتباع الرابط وقراءة:



سيلجن


الخطوة التالية هي تحويل AST المكتوب إلى SIL الخام. Swift Intermediate Language (SIL) هي تمثيل وسيط تم إنشاؤه خصيصًا لـ Swift. يمكن العثور على وصف لجميع التعليمات في الوثائق .


SIL لديه شكل SSA. Static Single Assignment (SSA) - تمثيل للكود يتم من خلاله تعيين كل متغير قيمة مرة واحدة فقط. يتم إنشاؤه من التعليمات البرمجية العادية عن طريق إضافة متغيرات إضافية. على سبيل المثال ، استخدام لاحقة رقمية تشير إلى إصدار متغير بعد كل مهمة.


بفضل هذا النموذج ، أصبح من السهل على المترجم تحسين الكود. يوجد أدناه مثال على الكود الزائف. من الواضح أن السطر الأول غير ضروري:


a = 1 a = 2 b = a 

ولكن هذا هو فقط بالنسبة لنا. لتعليم المترجم لتحديد ذلك ، يجب على المرء أن يكتب خوارزميات غير تافهة. ولكن مع SSA ، هذا أسهل بكثير. الآن ، حتى بالنسبة للمترجم البسيط ، سيكون من الواضح أن قيمة المتغير a1 غير مستخدمة ، ويمكن حذف هذا السطر:


 a1 = 1 a2 = 2 b1 = a2 

يتيح لك SIL تطبيق تحسينات وفحوصات محددة على كود سويفت سيكون من الصعب أو المستحيل إكماله في مرحلة AST.


باستخدام سيل مولد


لإنشاء SIL ، استخدم علامة - silgen العلامة :


 swiftc -emit-silgen main.swift 

نتيجة الأمر:


 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 ، مثل LLVM IR ، ككود مصدر. يمكنك أن تجد أنه في هذه المرحلة تم إضافة استيراد وحدات Swift مدمجة ، سويفت و سويفت شيمز.


على الرغم من حقيقة أن كود Swift يمكن كتابته مباشرة في النطاق العالمي ، فإن SILGen تنشئ الوظيفة الرئيسية - نقطة الدخول إلى البرنامج. كانت جميع الشفرات موجودة بداخلها ، باستثناء الإعلان عن ثابت ، لأنها عالمية ويجب أن تكون متاحة في كل مكان.


معظم الخطوط لها هيكل مماثل. على اليسار يوجد سجل زائف يتم فيه حفظ نتيجة التعليمات. التالي - التعليمات نفسها ومعاييرها ، وفي النهاية - تعليق يشير إلى السجل الذي سيتم استخدام هذا السجل من أجله.


على سبيل المثال ، يتم إنشاء حرفي صحيح من النوع Int2048 وقيمة 16. في هذا السطر ، يتم تخزين هذا الحرفي في السجل الخامس وسيتم استخدامه لحساب قيمة السابع:


 %5 = integer_literal $Builtin.Int2048, 16 // user: %7 

إعلان دالة يبدأ الكلمة sil. التالي هو الاسم مع بادئة @ ، اصطلاح استدعاء ، معلمات ، نوع الإرجاع ، ورمز الوظيفة. بالنسبة إلى مُهيئ Int.init (_builtinIntegerLiteral :) ، فهو ، بالطبع ، غير محدد ، حيث أن هذه الوظيفة من وحدة نمطية أخرى ، ويجب أن يتم الإعلان عنها فقط ، ولكن لا يتم تعريفها. تشير علامة الدولار إلى بداية مؤشر النوع:


 // Int.init(_builtinIntegerLiteral:) sil [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int 

يشير مصطلح الاتصال إلى كيفية استدعاء وظيفة بشكل صحيح. هذا ضروري لإنشاء رمز الجهاز. وصف مفصل لهذه المبادئ هو خارج نطاق هذه المقالة.


يتم تشويه اسم المُهيئات ، وكذلك أسماء الهياكل والفئات والطرق والبروتوكولات (اسم الاسم). هذا يحل العديد من المشاكل في وقت واحد.


أولاً ، يسمح باستخدام نفس الأسماء في الوحدات النمطية المختلفة والكيانات المتداخلة. على سبيل المثال ، بالنسبة للطريقة الأولى fff ، يتم استخدام اسم S4main3AAAV3fffSiyF ، وللأسلوب الثاني ، يتم استخدام S4main3BBBV3fffSiyF :


 struct AAA { func fff() -> Int { return 8 } } struct BBB { func fff() -> Int { return 8 } } 

يعني S Swift ، 4 هو عدد الأحرف في اسم الوحدة النمطية ، و 3 في اسم الفئة. في أداة التهيئة الحرفية ، تشير Si إلى النوع القياسي Swift.Int.


ثانياً ، تتم إضافة أسماء وأنواع وسائط الدالة إلى الاسم. وهذا يسمح باستخدام الزائد. على سبيل المثال ، بالنسبة للطريقة الأولى ، يتم إنشاء S4main3AAAV3fff3iiiS2i_tF ، وللأسلوب الثاني - S4main3AAAV3fff3dddSiSd_tF :


 struct AAA { func fff(iii internalName: Int) -> Int { return 8 } func fff(ddd internalName: Double) -> Int { return 8 } } 

بعد أسماء المعلمات ، تتم الإشارة إلى نوع قيمة الإرجاع ، متبوعة بأنواع المعلمات. ومع ذلك ، لا تتم الإشارة إلى أسمائهم الداخلية. لسوء الحظ ، لا توجد وثائق حول تغيير الاسم في Swift ، وقد يتغير تنفيذه في أي وقت.


يتبع اسم الوظيفة تعريفها. يتكون من واحد أو أكثر من القطع الأساسية. الكتلة الأساسية هي سلسلة من التعليمات مع نقطة دخول واحدة ونقطة خروج واحدة ، والتي لا تحتوي على تعليمات أو شروط الفرع لخروج مبكر.


تحتوي الوظيفة الرئيسية على وحدة أساسية واحدة ، والتي تأخذ جميع المعلمات التي تم تمريرها إلى الوظيفة كمدخل وتحتوي على جميع الكود الخاص بها ، حيث لا توجد فروع لها:


 bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): 

يمكننا أن نفترض أن كل نطاق تحده الأقواس هو وحدة أساسية منفصلة. افترض أن الكود يحتوي على فرع:


 // before if 2 > 5 { // true } else { // false } // after 

في هذه الحالة ، سيتم إنشاء 4 كتل أساسية على الأقل من أجل:


  • رمز قبل المتفرعة ،
  • الحالات عندما يكون التعبير صحيحا
  • الحالات عندما يكون التعبير خاطئ
  • رمز بعد المتفرعة.

cond_br - تعليمات للقفز الشرطي. إذا كانت قيمة السجل الزائف٪ 14 صحيحة ، فسيتم إجراء الانتقال إلى الكتلة bb1 . إذا لم يكن كذلك ، ثم في bb2 . br - قفزة غير مشروطة تبدأ تنفيذ الكتلة الأساسية المحددة:


 // 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 

كود المصدر:



SIL مضمونة التحولات


يتم تحليل التمثيل الوسيط الخام الذي تم الحصول عليه في المرحلة الأخيرة للتأكد من صحته وتحويله إلى الكنسي: الدوال ذات العلامة الشفافة مضمنة (يتم استدعاء استدعاء الوظيفة بجسمها) ، ويتم احتساب قيم التعبيرات الثابتة ، ويتم التحقق من أن الوظيفة التي ترجع القيم القيام بذلك في جميع فروع القانون وهلم جرا.


هذه التحويلات إلزامية ويتم تنفيذها حتى إذا تم تعطيل تحسين الكود.


كانون سيل جيل


لإنشاء SIL الكنسي ، يتم استخدام العلم -mit-sil :


 swiftc -emit-sil main.swift 

نتيجة الأمر:


 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' 

هناك بعض التغييرات في هذا المثال البسيط. لرؤية العمل الحقيقي للمحسن ، تحتاج إلى تعقيد الكود قليلاً. على سبيل المثال ، أضف الإضافة:


 let x = 16 + 8 

في SIL الخام ، يمكنك العثور على إضافة هذه الحرف:


 %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 

ولكن في الكنسي لم يعد هناك. بدلاً من ذلك ، يتم استخدام قيمة ثابتة من 24:


 %4 = integer_literal $Builtin.Int64, 24 // user: %5 

كود المصدر:



Sil التحسين


يتم تطبيق التحويلات الإضافية الخاصة بـ Swift في حالة تمكين التحسين. من بينها تخصص الوراثة (تحسين الكود العام لنوع معين من المعلمات) ، والافتراضية (استبدال المكالمات الديناميكية مع المكالمات الثابتة) ، والمضمنة ، وتحسين ARC ، وأكثر من ذلك بكثير. لا يتوافق تفسير هذه التقنيات مع مقال كبير بالفعل.


كود المصدر:



نظرًا لأن SIL ميزة Swift ، لم أقم بعرض أمثلة للتنفيذ هذه المرة. سنعود إلى مترجم الأقواس في الجزء التالي عندما نشارك في توليد LLVM IR.

Source: https://habr.com/ru/post/ar438696/


All Articles