وصف بنيات المعالج في LLVM باستخدام TableGen

في الوقت الحالي ، أصبح LLVM بالفعل نظامًا شائعًا للغاية ، يستخدمه كثير من الناس بنشاط لإنشاء مختلف المترجمين والمحللين ، إلخ. لقد تم بالفعل كتابة عدد كبير من المواد المفيدة حول هذا الموضوع ، بما في ذلك باللغة الروسية ، وهذا خبر سار. ومع ذلك ، في معظم الحالات ، يتم التحيز الرئيسي في المقالات على الواجهة الأمامية و endend LLVM. بالطبع ، عند وصف المخطط الكامل لتشغيل LLVM ، لا يتم تجاوز إنشاء كود الآلة ، ولكن يتم التطرق إلى هذا الموضوع بشكل أساسي ، لا سيما في المنشورات باللغة الروسية. في الوقت نفسه ، لدى LLVM آلية مرنة ومثيرة للاهتمام لوصف معماريات المعالج. لذلك ، سيتم تخصيص هذه المواد إلى TableGen الأداة المساعدة المهملة إلى حد ما ، والتي هي جزء من LLVM.

السبب يحتاج المحول البرمجي إلى الحصول على معلومات حول هندسة كل من الأنظمة الأساسية الهدف واضح للغاية. بطبيعة الحال ، يحتوي كل طراز معالج على مجموعة السجلات الخاصة به وتعليمات الماكينة الخاصة به ، إلخ. ويحتاج المترجم إلى الحصول على جميع المعلومات اللازمة عنهم حتى يكون قادرًا على إنشاء كود صالح وفعال للآلة. يقوم المحول البرمجي بحل مختلف المهام الخاصة بالنظام الأساسي: توزيع السجلات ، إلخ. بالإضافة إلى ذلك ، تقوم LLVM backends أيضًا بإجراء تحسينات بالفعل على الأشعة تحت الحمراء للجهاز ، والتي هي أقرب إلى التعليمات الفعلية ، أو بناءً على تعليمات المجمّع نفسها. في مثل هذه التحسينات ، يجب استبدال التعليمات وتحويلها ؛ وبناءً على ذلك ، يجب أن تكون جميع المعلومات المتعلقة بها متاحة.

لحل مشكلة وصف بنية المعالج ، اعتمد LLVM تنسيقًا واحدًا لتحديد خصائص المعالج اللازمة للمترجم. لكل هندسة مدعومة ، يحتوي .td على وصف بلغة رسمية خاصة. يتم تحويله إلى ملفات .inc عند إنشاء برنامج التحويل البرمجي باستخدام الأداة المساعدة TableGen المضمنة في LLVM. الملفات الناتجة ، في الواقع ، هي مصدر C ، ولكن لها امتداد منفصل ، على الأرجح ، بحيث يمكن تمييز وتصفية هذه الملفات التي تم إنشاؤها تلقائيًا بسهولة. الوثائق الرسمية لـ TableGen موجودة هنا وتقدم جميع المعلومات اللازمة ، وهناك أيضًا وصف رسمي للغة ومقدمة عامة .

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

وصف العمارة في ملف .td


لذلك ، تحتوي لغة الوصف الرسمية المستخدمة في TableGen على ميزات مشابهة للغات البرمجة العادية وتتيح لك وصف خصائص الهندسة المعمارية بأسلوب تعريفي. وكما أفهمها ، تُسمى هذه اللغة أيضًا TableGen. أي في هذه المقالة ، يستخدم TableGen كلاً من اللغة الرسمية نفسها والأداة المساعدة التي تنشئ الآثار الناتجة عنها.

المعالجات الحديثة أنظمة معقدة للغاية ، لذلك ليس من المستغرب أن يكون وصفها ضخمًا للغاية. وفقًا لذلك ، لإنشاء الهيكل وتبسيط صيانة ملفات .td يمكن أن يتضمن بعضها البعض باستخدام التوجيه #include المعتاد لمبرمجي C. بمساعدة هذا التوجيه ، يتم دائمًا تضمين ملف Target.td أولاً ، ويحتوي على واجهات مستقلة عن النظام الأساسي يجب تنفيذها لتوفير جميع معلومات TableGen الضرورية. يحتوي هذا الملف بالفعل على ملف .td مع أوصاف LLVM ، ولكنه في حد ذاته يحتوي على فئات أساسية ، مثل Register ، Instruction ، Processor ، وما إلى ذلك ، والتي تحتاج إلى أن .td لإنشاء .td الخاص للمترجم استنادًا إلى LLVM. من الجملة السابقة ، من الواضح أن TableGen لديه فكرة الطبقات المعروفة لجميع المبرمجين.

بشكل عام ، لدى TableGen كيانان أساسيان فقط: الفئات والتعاريف .

فصول


فئات TableGen هي أيضًا أشكال تجريدية ، كما هو الحال في جميع لغات البرمجة الموجهة للكائنات ، لكنها كيانات أبسط.

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

 // A class representing the register size, spill size and spill alignment // in bits of a register. class RegInfo<int RS, int SS, int SA> { int RegSize = RS; // Register size in bits. int SpillSize = SS; // Spill slot size in bits. int SpillAlignment = SA; // Spill slot alignment in bits. } 

تشير أقواس الزاوية إلى معلمات الإدخال التي تم تعيينها لخصائص الفئة. من هذا المثال ، يمكنك أيضًا ملاحظة أن لغة TableGen مكتوبة بشكل ثابت. الأنواع الموجودة في TableGen: bit (تمثيلي للنوع Boolean مع القيمتين 0 و 1) ، int ، string ، code (قطعة من الكود ، هذا نوع ، ببساطة لأن TableGen ليس لديه طرق ووظائف بالمعنى المعتاد ، تتم كتابة سطور الكود في [{ ... }] ) ، البتات <n> ، وقائمة <النوع> (يتم تعيين القيم باستخدام أقواس مربعة [...] كما هو الحال في بيثون وبعض لغات البرمجة الأخرى) ، class type ، dag .

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

يوصف الوراثة أيضًا بصيغة مألوفة إلى حد ما مع:.

 class X86MemOperand<string printMethod, AsmOperandClass parserMatchClass = X86MemAsmOperand> : Operand<iPTR> { let PrintMethod = printMethod; let MIOperandInfo = (ops ptr_rc, i8imm, ptr_rc_nosp, i32imm, SEGMENT_REG); let ParserMatchClass = parserMatchClass; let OperandType = "OPERAND_MEMORY"; } 

في هذه الحالة ، يمكن للفئة التي تم إنشاؤها ، بالطبع ، تجاوز قيم الحقول المحددة في الفئة الأساسية باستخدام الكلمة الأساسية let . ويمكن أن تضيف حقولها الخاصة على غرار الوصف الوارد في المثال السابق ، مع الإشارة إلى نوع الحقل.

تعريفات (التعاريف)


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

 def i8mem : X86MemOperand<"printbytemem", X86Mem8AsmOperand>; def X86AbsMemAsmOperand : AsmOperandClass { let Name = "AbsMem"; let SuperClasses = [X86MemAsmOperand]; } 

Multiclasses (multiclasses)


بطبيعة الحال ، هناك عدد كبير من التعليمات في المعالجات لها دلالات مماثلة. على سبيل المثال ، قد يكون هناك مجموعة من الإرشادات المكونة من ثلاثة عناوين تأخذ النموذجين “reg = reg op reg” و “reg = reg op imm” . في إحدى الحالات ، يتم أخذ القيم من السجلات ويتم حفظ النتيجة أيضًا في السجل ، وفي الحالة الأخرى ، يكون المعامل الثاني قيمة ثابتة (المعامل غير المباشر).

يعد سرد جميع المجموعات يدويًا مملاً إلى حد ما ؛ ويزداد خطر ارتكاب خطأ. بالطبع ، يمكن إنشاؤها تلقائيًا عن طريق كتابة برنامج نصي بسيط ، ولكن هذا ليس ضروريًا ، لأنه يوجد مفهوم باسم multiclasses في لغة TableGen.

 multiclass ri_inst<int opc, string asmstr> { def _rr : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"), (ops GPR:$dst, GPR:$src1, GPR:$src2)>; def _ri : inst<opc, !strconcat(asmstr, " $dst, $src1, $src2"), (ops GPR:$dst, GPR:$src1, Imm:$src2)>; } 

داخل multiclasses ، تحتاج إلى وصف جميع أشكال التعليمات الممكنة باستخدام الكلمة المفتاحية def . لكن هذا ليس شكلًا كاملاً من التعليمات التي سيتم إنشاؤها. في الوقت نفسه ، يمكنك إعادة تحديد الحقول الموجودة فيها والقيام بكل ما هو ممكن في التعريفات المعتادة. لإنشاء تعريفات حقيقية تستند إلى multiclass ، تحتاج إلى استخدام الكلمة الأساسية defm .

 // Instantiations of the ri_inst multiclass. defm ADD : ri_inst<0b111, "add">; defm SUB : ri_inst<0b101, "sub">; defm MUL : ri_inst<0b100, "mul">; 

ونتيجة لذلك ، لكل تعريف يتم تقديمه من خلال defm في الواقع ، سيتم إنشاء العديد من التعريفات التي هي مزيج من التعليمات الرئيسية وجميع الأشكال الممكنة الموضحة في multiclass. نتيجةً لذلك ، سيتم إنشاء الإرشادات التالية في هذا المثال: ADD_rr و ADD_ri و SUB_rr و SUB_ri و MUL_rr و MUL_ri .

لا يمكن أن تحتوي الوسائط المتعددة على تعريفات مع def ، ولكن أيضًا على defm المتداخلة ، مما يسمح defm أشكال معقدة من التعليمات. يمكن العثور على مثال يوضح إنشاء مثل هذه السلاسل في الوثائق الرسمية.

الأهداف الفرعية


هناك شيء أساسي ومفيد آخر للمعالجات التي لها أشكال مختلفة من مجموعة التعليمات وهو دعم subtarget في LLVM. مثال على الاستخدام هو تطبيق LLVM SPARC ، والذي يغطي ثلاثة إصدارات رئيسية من بنية المعالج الدقيق SPARC في آن واحد: الإصدار 8 (V8 ، هندسة 32 بت) ، الإصدار 9 (V9 ، هندسة 64 بت) وهندسة UltraSPARC. الفرق بين البنى كبير جدًا ، وعدد مختلف من سجلات الأنواع المختلفة ، وترتيب البايت المدعوم ، إلخ. في مثل هذه الحالات ، إذا كان هناك العديد من التكوينات ، فإن الأمر يستحق تنفيذ فئة XXXSubtarget الخاصة بالهندسة المعمارية. سيؤدي استخدام هذه الفئة في الوصف إلى خيارات سطر أوامر جديدة -mattr= و -mattr= .

بالإضافة إلى فئة Subtarget نفسها ، فإن فئة Subtarget مهمة.

 class SubtargetFeature<string n, string a, string v, string d, list<SubtargetFeature> i = []> { string Name = n; string Attribute = a; string Value = v; string Desc = d; list<SubtargetFeature> Implies = i; } 

في ملف Sparc.td ، يمكنك العثور على أمثلة لتطبيق SubtargetFeature ، والتي تسمح لك بوصف توفر مجموعة من التعليمات لكل نوع فرعي من البنية.

 def FeatureV9 : SubtargetFeature<"v9", "IsV9", "true", "Enable SPARC-V9 instructions">; def FeatureV8Deprecated : SubtargetFeature<"deprecated-v8", "V8DeprecatedInsts", "true", "Enable deprecated V8 instructions in V9 mode">; def FeatureVIS : SubtargetFeature<"vis", "IsVIS", "true", "Enable UltraSPARC Visual Instruction Set extensions">; 

في هذه الحالة ، على أي حال ، لا يزال Sparc.td يعرّف فئة Proc ، والتي تستخدم لوصف أنواع فرعية محددة من معالجات SPARC ، والتي قد تحتوي فقط على الخصائص الموصوفة أعلاه ، بما في ذلك مجموعات مختلفة من التعليمات.

 class Proc<string Name, list<SubtargetFeature> Features> : Processor<Name, NoItineraries, Features>; def : Proc<"generic", []>; def : Proc<"v8", []>; def : Proc<"supersparc", []>; def : Proc<"sparclite", []>; def : Proc<"f934", []>; def : Proc<"hypersparc", []>; def : Proc<"sparclite86x", []>; def : Proc<"sparclet", []>; def : Proc<"tsc701", []>; def : Proc<"v9", [FeatureV9]>; def : Proc<"ultrasparc", [FeatureV9, FeatureV8Deprecated]>; def : Proc<"ultrasparc3", [FeatureV9, FeatureV8Deprecated]>; def : Proc<"ultrasparc3-vis", [FeatureV9, FeatureV8Deprecated, FeatureVIS]>; 

العلاقة بين خصائص التعليمات في TableGen ورمز الواجهة الخلفية LLVM


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

TSFlags


للقيام بذلك ، تحتوي فئة Instruction الأساسية على حقل TSFlags خاص ، TSFlags 64 بت ، ويتم تحويله بواسطة TableGen إلى حقل كائنات C ++ للفئة MCInstrDesc ، تم إنشاؤه على أساس البيانات الواردة من وصف TableGen. يمكنك تحديد أي عدد من وحدات البت التي تحتاجها لتخزين المعلومات. قد تكون هذه بعض القيم المنطقية ، على سبيل المثال ، للإشارة إلى أننا نستخدم ALU العددية.

 let TSFlags{0} = SALU; 

أو يمكننا تخزين نوع التعليمات. ثم نحتاج ، بالطبع ، أكثر من بت.

 // Instruction type according to the ISA. IType Type = type; let TSFlags{7-1} = Type.Value; 

نتيجة لذلك ، يصبح من الممكن الحصول على هذه الخصائص من التعليمات في التعليمات البرمجية الخلفية.

 bool isSALU = MI.getDesc().TSFlags & SIInstrFlags::SALU; 

إذا كانت الخاصية أكثر تعقيدًا ، فيمكنك مقارنتها بالقيمة الموضحة في TableGen ، والتي ستتم إضافتها إلى التعداد الذي تم إنشاؤه تلقائيًا.

 (Desc.TSFlags & X86II::FormMask) == X86II::MRMSrcMem 


دالة يتنبأ


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

 // Base class for function predicates. class FunctionPredicateBase<string name, MCStatement body> { string FunctionName = name; MCStatement Body = body; } 

يمكنك بسهولة العثور على أمثلة الاستخدام في الواجهة الخلفية لـ X86. إذن هناك فئة متوسطة خاصة بها ، والتي يتم بالفعل إنشاء التعريفات الضرورية للوظائف.

 // Check that a call to method `Name` in class "XXXInstrInfo" (where XXX is // the name of a target) returns true. // // TIIPredicate definitions are used to model calls to the target-specific // InstrInfo. A TIIPredicate is treated specially by the InstrInfoEmitter // tablegen backend, which will use it to automatically generate a definition in // the target specific `InstrInfo` class. // // There cannot be multiple TIIPredicate definitions with the same name for the // same target class TIIPredicate<string Name, MCStatement body> : FunctionPredicateBase<Name, body>, MCInstPredicate; // This predicate evaluates to true only if the input machine instruction is a // 3-operands LEA. Tablegen automatically generates a new method for it in // X86GenInstrInfo. def IsThreeOperandsLEAFn : TIIPredicate<"isThreeOperandsLEA", IsThreeOperandsLEABody>; //   -    ,  -  ,       // Used to generate the body of a TII member function. def IsThreeOperandsLEABody : MCOpcodeSwitchStatement<[LEACases], MCReturnStatement<FalsePred>>; 

نتيجة لذلك ، يمكنك استخدام الأسلوب isThreeOperandsLEA في رمز C ++.

 if (!(TII->isThreeOperandsLEA(MI) || hasInefficientLEABaseReg(Base, Index)) || !TII->isSafeToClobberEFLAGS(MBB, MI) || Segment.getReg() != X86::NoRegister) return; 

هنا TII هي معلومات التعليمات الهدف ، والتي يمكن الحصول عليها باستخدام طريقة getInstrInfo() من MCSubtargetInfo للعمارة المطلوبة.

تحويل التعليمات خلال التحسينات. رسم الخرائط التعليمات


أثناء عدد كبير من التحسينات التي يتم إجراؤها في المراحل اللاحقة من التحويل البرمجي ، تنشأ المهمة غالبًا في تحويل كل أو جزء فقط من الإرشادات الخاصة بنموذج ما إلى تعليمات من نموذج آخر. بالنظر إلى تطبيق multiclasses الموصوفة في البداية ، يمكننا الحصول على عدد كبير من التعليمات مع دلالات وخصائص مماثلة. في الكود ، يمكن كتابة هذه التحولات ، بطبيعة الحال ، في شكل إنشاءات كبيرة switch-case ، مما سحق التحول المقابل لكل تعليمة. جزئيًا ، يمكن تقليل هذه الإنشاءات الضخمة بمساعدة وحدات الماكرو ، والتي ستشكل الاسم اللازم للتعليمات وفقًا لقواعد معروفة. ولكن لا يزال ، هذا النهج غير مريح للغاية ، من الصعب الحفاظ عليه بسبب حقيقة أن جميع أسماء التعليمات مدرجة صراحة. يمكن أن تؤدي إضافة تعليمات جديدة بسهولة إلى حدوث خطأ ، لأن يجب أن تتذكر إضافته إلى جميع التحويلات ذات الصلة. بعد تعذُّبها بهذا النهج ، أنشأت LLVM آلية خاصة لتحويل شكل من أشكال التعليمات بكفاءة إلى Instruction Mapping آخر.

الفكرة بسيطة للغاية ، فمن الضروري وصف النماذج الممكنة لتحويل التعليمات مباشرة في TableGen. لذلك ، في LLVM TableGen هناك فئة أساسية لوصف هذه النماذج.

 class InstrMapping { // Used to reduce search space only to the instructions using this // relation model. string FilterClass; // List of fields/attributes that should be same for all the instructions in // a row of the relation table. Think of this as a set of properties shared // by all the instructions related by this relationship. list<string> RowFields = []; // List of fields/attributes that are same for all the instructions // in a column of the relation table. list<string> ColFields = []; // Values for the fields/attributes listed in 'ColFields' corresponding to // the key instruction. This is the instruction that will be transformed // using this relation model. list<string> KeyCol = []; // List of values for the fields/attributes listed in 'ColFields', one for // each column in the relation table. These are the instructions a key // instruction will be transformed into. list<list<string> > ValueCols = []; } 

دعونا نلقي نظرة على مثال يرد في الوثائق. الأمثلة التي يمكن العثور عليها في التعليمات البرمجية المصدر أصبحت الآن أكثر بساطة ، حيث يتم الحصول على عمودين فقط في الجدول النهائي. في رمز الواجهة الخلفية ، يمكنك العثور على تحويل النماذج القديمة إلى أشكال جديدة من التعليمات ، وتعليمات dsp في mmdsp ، وما إلى ذلك ، الموضحة باستخدام تعيين التعليمات. في الواقع ، لم يتم استخدام هذه الآلية على نطاق واسع حتى الآن ، وذلك ببساطة لأن معظم المؤثرات الخلفية بدأت في الظهور قبل ظهورها ، ولكي تعمل ، لا تزال بحاجة إلى تعيين الخصائص الصحيحة للتعليمات ، وبالتالي فإن التحول إليها ليس سهلاً دائمًا ، فقد تحتاج إلى بعض إعادة بيع ديون.

هكذا ، على سبيل المثال. لنفترض أن لدينا أشكالًا من التعليمات دون تقدير وتعليمات حيث تكون المسند صحيحة وكاذبة على التوالي. نحن نصفهم بمساعدة متعدد الطبقات وفئة خاصة ، والتي سوف نستخدمها فقط كعامل تصفية. وصف مبسط بدون معلمات والعديد من الخصائص قد يكون شيء من هذا القبيل.

 class PredRel; multiclass MyInstruction<string name> { let BaseOpcode = name in { def : PredRel { let PredSense = ""; } def _pt: PredRel { let PredSense = "true"; } def _pf: PredRel { let PredSense = "false"; } } } defm ADD: MyInstruction<”ADD”>; defm SUB: MyIntruction<”SUB”>; defm MUL: MyInstruction<”MUL”>; … 

في هذا المثال ، بالمناسبة ، يتم أيضًا إظهار كيفية تجاوز خاصية لعدة تعريفات في وقت واحد باستخدام علامة let … in الإنشاء. نتيجة لذلك ، لدينا العديد من الإرشادات التي تخزن اسمها الأساسي والممتلكات التي تصف شكلها بشكل فريد. ثم يمكنك إنشاء نموذج التحول.

 def getPredOpcode : InstrMapping { // ,       - PredRel  let FilterClass = "PredRel"; //         ,      let RowFields = ["BaseOpcode"]; //          PredSense. let ColFields = ["PredSense"]; //  ,  ,       ,     PredSense=”” let KeyCol = [""]; //   PredSense      let ValueCols = [["true"], ["false"]]; } 

نتيجة لذلك ، سيتم إنشاء الجدول التالي من هذا الوصف.

PredSense = ""PredSense = "صواب"PredSense = "خطأ"
أضفADD_ptADD_pf
SUBSUB_ptSUB_pf
MULMUL_ptMUL_pf

سيتم إنشاء وظيفة في ملف .inc

 int getPredOpcode(uint16_t Opcode, enum PredSense inPredSense) 

والتي ، وفقًا لذلك ، تقبل رمز تعليمات للتحويل وقيمة التعداد PredSense الذي تم إنشاؤه تلقائيًا ، والذي يحتوي على جميع القيم الممكنة في الأعمدة. تنفيذ هذه الوظيفة بسيط جدا ، لأنه تقوم بإرجاع عنصر الصفيف المطلوب للتعليمات التي تهمنا.

وفي الكود الخلفي ، بدلاً من كتابة حالة switch-case يكفي استدعاء الوظيفة التي تم إنشاؤها ، والتي ستُرجع رمز التعليمة المحولة. لن يؤدي الحل البسيط ، عند إضافة تعليمات جديدة ، إلى الحاجة إلى اتخاذ إجراءات إضافية.

التحف التي تم إنشاؤها تلقائيًا (ملفات .inc )


يتم ضمان كل التفاعل بين وصف TableGen ورمز الواجهة الخلفية LLVM بواسطة ملفات .inc التي تم إنشاؤها والتي تحتوي على رمز C. للحصول على صورة كاملة ، دعونا نرى قليلاً ما هي بالضبط.

بعد كل بناء ، لكل هندسة ، سيكون هناك العديد من ملفات .inc في دليل .inc ، كل منها يخزن أجزاء منفصلة من المعلومات حول الهيكل.حتى لا يكون هناك ملف <TargetName>GenInstrInfo.incيحتوي على معلومات حول تعليمات <TargetName>GenRegisterInfo.inc، على التوالي، والذي يحتوي على معلومات حول السجلات وهناك ملفات للعمل مباشرة مع المجمع وانتاجها <TargetName>GenAsmMatcher.incو <TargetName>GenAsmWriter.incالخ

إذن ماذا تتكون هذه الملفات؟ بشكل عام ، تحتوي على تعدادات ومصفوفات وهياكل ووظائف بسيطة. على سبيل المثال ، يمكنك إلقاء نظرة على المعلومات المحولة في الإرشادات الواردة في <TargetName>GenInstrInfo.inc.

في الجزء الأول ، في مساحة الاسم مع اسم الهدف ، يوجد تعداد يحتوي على جميع الإرشادات الموضحة.

 namespace X86 { enum { PHI = 0, … ADD16i16 = 287, ADD16mi = 288, ADD16mi8 = 289, ADD16mr = 290, ADD16ri = 291, ADD16ri8 = 292, ADD16rm = 293, ADD16rr = 294, ADD16rr_REV = 295, … } 

التالي صفيف يصف خصائص التعليمات const MCInstrDesc X86Insts[]. تحتوي الصفائف التالية على معلومات حول أسماء التعليمات ، إلخ. في الأساس ، يتم تخزين جميع المعلومات في عمليات النقل والمصفوفات.

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

 bool X86InstrInfo::isThreeOperandsLEA(const MachineInstr &MI) { switch(MI.getOpcode()) { case X86::LEA32r: case X86::LEA64r: case X86::LEA64_32r: case X86::LEA16r: return ( MI.getOperand(1).isReg() && MI.getOperand(1).getReg() != 0 && MI.getOperand(3).isReg() && MI.getOperand(3).getReg() != 0 && ( ( MI.getOperand(4).isImm() && MI.getOperand(4).getImm() != 0 ) || (MI.getOperand(4).isGlobal()) ) ); default: return false; } // end of switch-stmt } 

ولكن هناك بيانات في الملفات والهياكل التي تم إنشاؤها. في X86GenSubtargetInfo.incيمكنك العثور على مثال للهيكل الذي يجب استخدامه في رمز الواجهة الخلفية للحصول على معلومات حول الهيكل ، من خلاله في القسم السابق ، تم إيقاف تشغيل TTI.

 struct X86GenMCSubtargetInfo : public MCSubtargetInfo { X86GenMCSubtargetInfo(const Triple &TT, StringRef CPU, StringRef FS, ArrayRef<SubtargetFeatureKV> PF, ArrayRef<SubtargetSubTypeKV> PD, const MCWriteProcResEntry *WPR, const MCWriteLatencyEntry *WL, const MCReadAdvanceEntry *RA, const InstrStage *IS, const unsigned *OC, const unsigned *FP) : MCSubtargetInfo(TT, CPU, FS, PF, PD, WPR, WL, RA, IS, OC, FP) { } unsigned resolveVariantSchedClass(unsigned SchedClass, const MCInst *MI, unsigned CPUID) const override { return X86_MC::resolveVariantSchedClassImpl(SchedClass, MI, CPUID); } }; 

إذا تم استخدامه Subtargetلوصف التكوينات المختلفة XXXGenSubtarget.inc، سيتم إنشاء تعداد بالخصائص الموضحة باستخدام SubtargetFeatureصفائف ذات قيم ثابتة للإشارة إلى خصائص وأنواع فرعية لوحدة المعالجة المركزية ، وسيتم إنشاء دالة ParseSubtargetFeaturesتعالج السلسلة باستخدام مجموعة الخيارات Subtarget. علاوة على ذلك ، XXXSubtargetيجب أن يتوافق تنفيذ الطريقة في الكود الخلفي مع الكود الزائف التالي ، والذي من الضروري فيه استخدام هذه الوظيفة:

 XXXSubtarget::XXXSubtarget(const Module &M, const std::string &FS) { // Set the default features // Determine default and user specified characteristics of the CPU // Call ParseSubtargetFeatures(FS, CPU) to parse the features string // Perform any additional operations } 

على الرغم من أن .incالملفات ضخمة جدًا وتحتوي على مصفوفات ضخمة ، فإن هذا يسمح لنا بتحسين وقت الوصول إلى المعلومات ، لأن الوصول إلى عنصر الصفيف له وقت ثابت. يتم تنفيذ وظائف البحث التي تم إنشاؤها بواسطة التعليمات باستخدام خوارزمية البحث الثنائية لتقليل وقت التشغيل. لذلك التخزين في هذا النموذج له ما يبرره تماما.

استنتاج


نتيجة لذلك ، بفضل TableGen في LLVM ، لدينا أوصاف معمارية قابلة للقراءة ومدعومة بسهولة بتنسيق واحد مع آليات مختلفة للتفاعل والوصول إلى المعلومات من شفرة مصدر الواجهة الخلفية LLVM من أجل التحسينات وإنشاء الشفرة. في الوقت نفسه ، لا يؤثر مثل هذا الوصف على أداء برنامج التحويل البرمجي نظرًا لرمز تم إنشاؤه تلقائيًا يستخدم حلول فعالة وهياكل البيانات.

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


All Articles