Deskripsi arsitektur prosesor dalam LLVM menggunakan TableGen

Saat ini, LLVM telah menjadi sistem yang sangat populer, yang banyak orang gunakan secara aktif untuk membuat berbagai kompiler, analisa, dll. Sejumlah besar bahan berguna tentang topik ini telah ditulis, termasuk dalam bahasa Rusia, yang merupakan berita baik. Namun, dalam kebanyakan kasus, bias utama dalam artikel dibuat di LLVM frontend dan midend. Tentu saja, ketika menggambarkan skema penuh operasi LLVM, pembuatan kode mesin tidak dilewati, tetapi pada dasarnya topik ini disentuh dengan santai, terutama dalam publikasi di Rusia. Pada saat yang sama, LLVM memiliki mekanisme yang agak fleksibel dan menarik untuk menggambarkan arsitektur prosesor. Oleh karena itu, materi ini akan dikhususkan untuk TableGen utilitas yang agak terabaikan, yang merupakan bagian dari LLVM.

Alasan kompiler perlu memiliki informasi tentang arsitektur masing-masing platform target cukup jelas. Secara alami, setiap model prosesor memiliki set register sendiri, instruksi mesinnya sendiri, dll. Dan kompiler perlu memiliki semua informasi yang diperlukan tentang mereka agar dapat menghasilkan kode mesin yang valid dan efisien. Kompiler menyelesaikan berbagai tugas khusus platform: mendistribusikan register, dll. Selain itu, backend LLVM juga melakukan optimasi pada mesin IR, yang lebih dekat dengan instruksi aktual, atau pada instruksi assembler sendiri. Dalam optimisasi semacam itu, instruksi perlu diganti dan diubah, sehingga semua informasi tentangnya harus tersedia.

Untuk memecahkan masalah menggambarkan arsitektur prosesor, LLVM mengadopsi format tunggal untuk menentukan properti prosesor yang diperlukan untuk kompiler. Untuk setiap arsitektur yang didukung, .td berisi deskripsi dalam bahasa formal khusus. Itu dikonversi ke file .inc ketika membangun kompiler menggunakan utilitas TableGen yang disertakan dengan LLVM. File yang dihasilkan, pada kenyataannya, adalah sumber C, tetapi kemungkinan besar memiliki ekstensi yang terpisah, hanya agar file-file yang dihasilkan secara otomatis ini dapat dengan mudah dibedakan dan difilter. Dokumentasi resmi untuk TableGen ada di sini dan memberikan semua informasi yang diperlukan, ada juga deskripsi resmi bahasa dan pengantar umum .

Tentu saja, ini adalah topik yang sangat luas, di mana ada banyak detail tentang mana Anda dapat menulis artikel individual. Dalam artikel ini, kami hanya mempertimbangkan poin dasar dari deskripsi prosesor bahkan tanpa tinjauan umum dari semua fitur.

Deskripsi arsitektur dalam file .td


Jadi, bahasa deskripsi formal yang digunakan dalam TableGen memiliki fitur yang mirip dengan bahasa pemrograman biasa dan memungkinkan Anda untuk menggambarkan karakteristik arsitektur dalam gaya deklaratif. Dan seperti yang saya pahami, bahasa ini juga biasa disebut TableGen. Yaitu Dalam artikel ini, TableGen menggunakan nama bahasa formal itu sendiri dan utilitas yang menghasilkan artefak yang dihasilkan dari itu.

Prosesor modern adalah sistem yang sangat kompleks, sehingga tidak mengherankan bahwa deskripsi mereka cukup banyak. Oleh karena itu, untuk membuat struktur dan menyederhanakan pemeliharaan file .td dapat menyertakan satu sama lain menggunakan arahan #include biasa untuk programmer C. Dengan bantuan arahan ini, file Target.td selalu disertakan pertama kali, yang berisi antarmuka platform independen yang harus diimplementasikan untuk memberikan semua informasi TableGen yang diperlukan. File ini sudah termasuk file .td dengan deskripsi intrinsik LLVM, tetapi dengan sendirinya sebagian besar berisi kelas dasar, seperti Register , Instruction , Processor , dll., Dari mana Anda perlu mewarisi untuk membuat arsitektur Anda sendiri untuk kompiler berdasarkan LLVM. Dari kalimat sebelumnya, jelas bahwa TableGen memiliki gagasan tentang kelas yang dikenal oleh semua programmer.

Secara umum, TableGen hanya memiliki dua entitas dasar: kelas dan definisi .

Kelas


Kelas TableGen juga abstraksi, seperti dalam semua bahasa pemrograman berorientasi objek, tetapi mereka adalah entitas yang lebih sederhana.

Kelas dapat memiliki parameter dan bidang, dan mereka juga bisa mewarisi kelas lain.
Sebagai contoh, salah satu kelas dasar disajikan di bawah ini.

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

Kurung sudut menunjukkan parameter input yang ditugaskan ke properti kelas. Dari contoh ini, Anda juga dapat melihat bahwa bahasa TableGen diketik secara statis. Tipe-tipe yang ada di TableGen: bit (analog dari tipe Boolean dengan nilai 0 dan 1), int , string , code (sepotong kode, ini adalah tipe, hanya karena TableGen tidak memiliki metode dan fungsi dalam arti biasa, baris kode ditulis dalam [{ ... }] ), bit <n>, daftar <type> (nilai-nilai diatur menggunakan tanda kurung siku [...] seperti dalam Python dan beberapa bahasa pemrograman lain), class type , dag .

Sebagian besar jenis harus dipahami, tetapi jika mereka memiliki pertanyaan, mereka semua dijelaskan secara rinci dalam spesifikasi bahasa, tersedia di tautan yang diberikan di awal artikel.

Warisan juga dijelaskan oleh sintaks yang cukup akrab dengan :

 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"; } 

Dalam hal ini, kelas yang dibuat, tentu saja, dapat menimpa nilai bidang yang ditentukan dalam kelas dasar menggunakan kata kunci let . Dan itu dapat menambahkan bidangnya sendiri mirip dengan deskripsi yang diberikan dalam contoh sebelumnya, menunjukkan jenis bidang.

Definisi


Definisi sudah entitas konkret, Anda dapat membandingkannya dengan yang akrab dengan semua objek. Definisi didefinisikan menggunakan kata kunci def dan dapat mengimplementasikan kelas, mendefinisikan kembali bidang kelas dasar dengan cara yang persis sama seperti yang dijelaskan di atas, dan juga memiliki bidangnya sendiri.

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

Multiclasses


Secara alami, sejumlah besar instruksi dalam prosesor memiliki semantik yang serupa. Misalnya, mungkin ada satu set instruksi tiga alamat yang mengambil dua bentuk β€œreg = reg op reg” dan β€œreg = reg op imm” . Dalam satu kasus, nilai diambil dari register dan hasilnya juga disimpan dalam register, dan dalam kasus lain, operan kedua adalah nilai konstan (operan tidak langsung).

Mendaftar semua kombinasi secara manual agak membosankan, risiko membuat kesalahan meningkat. Tentu saja, mereka dapat dihasilkan secara otomatis dengan menulis skrip sederhana, tetapi ini tidak perlu, karena konsep seperti multiclasses ada dalam bahasa 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)>; } 

Di dalam multiclasses, Anda perlu menjelaskan semua bentuk instruksi yang mungkin menggunakan kata kunci def . Tapi ini bukan bentuk instruksi yang lengkap untuk dihasilkan. Pada saat yang sama, Anda dapat mendefinisikan ulang bidang di dalamnya dan melakukan segala sesuatu yang mungkin dalam definisi yang biasa. Untuk membuat definisi nyata berdasarkan multiclass, Anda perlu menggunakan kata kunci 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">; 

Dan sebagai hasilnya, untuk setiap definisi yang diberikan melalui defm pada kenyataannya, beberapa definisi akan dibangun yang merupakan kombinasi dari instruksi utama dan semua bentuk yang mungkin dijelaskan dalam multiclass. Sebagai hasilnya, instruksi berikut akan dihasilkan dalam contoh ini: ADD_rr , ADD_ri , SUB_rr , SUB_ri , MUL_rr , MUL_ri .

Multiclasses dapat berisi tidak hanya definisi dengan def , tetapi juga defm bersarang, sehingga memungkinkan pembentukan bentuk instruksi yang kompleks. Contoh yang menggambarkan pembuatan rantai semacam itu dapat ditemukan dalam dokumentasi resmi.

Subtarget


Hal dasar dan berguna lainnya untuk prosesor yang memiliki variasi set instruksi yang berbeda adalah dukungan subtarget dalam LLVM. Contoh penggunaan adalah implementasi LLVM SPARC, yang mencakup tiga versi utama arsitektur mikroprosesor SPARC sekaligus: Versi 8 (V8, arsitektur 32-bit), Versi 9 (V9, arsitektur 64-bit) dan arsitektur UltraSPARC. Perbedaan antara arsitektur cukup besar, jumlah register yang berbeda dari jenis yang berbeda, urutan byte yang didukung, dll. Dalam kasus seperti itu, jika ada beberapa konfigurasi, ada baiknya menerapkan kelas XXXSubtarget untuk arsitektur. Menggunakan kelas ini dalam deskripsi akan menghasilkan opsi baris perintah baru -mcpu= dan -mattr= .

Selain kelas Subtarget itu sendiri, kelas Subtarget penting.

 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; } 

Dalam file Sparc.td , Anda dapat menemukan contoh implementasi SubtargetFeature , yang memungkinkan Anda untuk menggambarkan ketersediaan serangkaian instruksi untuk setiap subtipe individu arsitektur.

 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">; 

Dalam kasus ini, Sparc.td masih mendefinisikan kelas Proc , yang digunakan untuk menggambarkan subtipe spesifik dari prosesor SPARC, yang mungkin memiliki sifat-sifat yang dijelaskan di atas, termasuk set instruksi yang berbeda.

 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]>; 

Hubungan antara sifat-sifat instruksi di TableGen dan kode backend LLVM


Properti kelas dan definisi memungkinkan Anda untuk menghasilkan dan mengatur fitur arsitektur dengan benar, tetapi tidak ada akses langsung ke mereka dari kode sumber backend LLVM. Namun, kadang-kadang Anda ingin mendapatkan beberapa properti khusus platform secara langsung dalam kode kompiler.

TSFlags


Untuk melakukan ini, kelas dasar Instruction memiliki bidang TSFlags khusus, TSFlags 64 bit, yang dikonversi oleh TableGen ke bidang objek C ++ kelas MCInstrDesc , yang dihasilkan berdasarkan data yang diterima dari deskripsi TableGen. Anda dapat menentukan jumlah bit yang Anda perlukan untuk menyimpan informasi. Ini mungkin beberapa nilai Boolean, misalnya, untuk menunjukkan bahwa kami menggunakan ALU skalar.

 let TSFlags{0} = SALU; 

Atau kita bisa menyimpan jenis instruksinya. Maka kita perlu, tentu saja, lebih dari satu bit.

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

Akibatnya, menjadi mungkin untuk mendapatkan properti ini dari instruksi di kode backend.

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

Jika properti lebih kompleks, maka Anda dapat membandingkannya dengan nilai yang dijelaskan dalam TableGen, yang akan ditambahkan ke enumerasi yang dibuat secara otomatis.

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


Predikat fungsi


Juga, predikat fungsi dapat digunakan untuk mendapatkan informasi yang diperlukan tentang instruksi. Dengan bantuan mereka, Anda bisa menunjukkan kepada TableGen bahwa Anda perlu membuat fungsi yang sesuai akan tersedia dalam kode backend. Kelas dasar yang dengannya Anda dapat membuat definisi fungsi disajikan di bawah ini.

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

Anda dapat dengan mudah menemukan contoh penggunaan di backend untuk X86. Jadi ada kelas menengahnya sendiri, dengan bantuan definisi fungsi yang diperlukan sudah dibuat.

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

Sebagai hasilnya, Anda dapat menggunakan metode isThreeOperandsLEA dalam kode C ++.

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

Di sini TII adalah info instruksi target, yang dapat diperoleh dengan menggunakan metode getInstrInfo() dari MCSubtargetInfo untuk arsitektur yang diinginkan.

Transformasi instruksi selama optimisasi. Pemetaan instruksi


Selama sejumlah besar optimasi dilakukan pada tahap akhir kompilasi, tugas tersebut sering muncul untuk mengubah semua atau hanya sebagian dari instruksi dari satu formulir menjadi instruksi dari formulir lain. Mengingat penerapan multiclasses yang dijelaskan di awal, kita dapat memiliki sejumlah besar instruksi dengan semantik dan properti yang serupa. Dalam kode, transformasi ini, tentu saja, dapat ditulis dalam bentuk konstruksi switch-case besar, yang untuk setiap instruksi menghancurkan transformasi yang sesuai. Sebagian, konstruksi besar ini dapat dikurangi dengan bantuan makro, yang akan membentuk nama instruksi yang diperlukan sesuai dengan aturan yang terkenal. Namun tetap saja, pendekatan ini sangat merepotkan, sulit dipertahankan karena fakta bahwa semua nama instruksi didaftar secara eksplisit. Menambahkan instruksi baru dapat dengan mudah menyebabkan kesalahan, karena Anda harus ingat untuk menambahkannya ke semua konversi yang relevan. Setelah disiksa dengan pendekatan ini, LLVM menciptakan mekanisme khusus untuk secara efisien mengubah satu bentuk instruksi ke Instruction Mapping lainnya.

Idenya sangat sederhana, perlu untuk menggambarkan model yang mungkin untuk mengubah instruksi secara langsung di TableGen. Oleh karena itu, dalam TableGen LLVM ada kelas dasar untuk menggambarkan model seperti itu.

 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 = []; } 

Mari kita lihat contoh yang diberikan dalam dokumentasi. Contoh-contoh yang dapat ditemukan dalam kode sumber sekarang bahkan lebih sederhana, karena hanya dua kolom yang diperoleh pada tabel akhir. Dalam kode backend Anda dapat menemukan konversi formulir lama ke bentuk baru instruksi, instruksi dsp dalam mmdsp, dll., Dijelaskan menggunakan Pemetaan Instruksi. Sebenarnya, mekanisme ini tidak begitu banyak digunakan sejauh ini, hanya karena sebagian besar backend mulai dibuat sebelum muncul, dan agar bisa berfungsi, Anda masih perlu mengatur properti yang benar untuk instruksi, jadi beralih ke itu tidak selalu mudah, Anda mungkin memerlukan beberapa refactoring.

Jadi misalnya. Misalkan kita memiliki bentuk instruksi tanpa predikat dan instruksi di mana predikatnya masing-masing benar dan salah. Kami menggambarkannya dengan bantuan multiklass dan kelas khusus, yang hanya akan kami gunakan sebagai filter. Deskripsi yang disederhanakan tanpa parameter dan banyak properti mungkin seperti ini.

 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”>; … 

Dalam contoh ini, omong-omong, juga ditunjukkan bagaimana menimpa properti untuk beberapa definisi sekaligus menggunakan let … in konstruk. Sebagai hasilnya, kami memiliki banyak instruksi yang menyimpan nama dasar dan properti mereka yang secara unik menggambarkan formulir mereka. Kemudian Anda dapat membuat model transformasi.

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

Hasilnya, tabel berikut akan dihasilkan dari deskripsi ini.

PredSense = ""PredSense = "true"PredSense = ”false”
ADDADD_ptADD_pf
SUBSUB_ptSUB_pf
MulMUL_ptMUL_pf

Suatu fungsi akan dihasilkan dalam file .inc

 int getPredOpcode(uint16_t Opcode, enum PredSense inPredSense) 

Dengan demikian, yang menerima kode instruksi untuk konversi dan nilai enumerasi otomatis yang dihasilkan PredSense, yang berisi semua nilai yang mungkin dalam kolom. Implementasi fungsi ini sangat sederhana, karena itu mengembalikan elemen array yang diinginkan untuk instruksi yang menarik bagi kami.

Dan dalam kode backend, alih-alih menulis switch-case cukup dengan memanggil fungsi yang dihasilkan, yang akan mengembalikan kode dari instruksi yang dikonversi. Solusi sederhana, di mana menambahkan instruksi baru, tidak akan mengarah pada perlunya tindakan tambahan.

Artefak yang dibuat secara otomatis (file .inc )


Semua interaksi antara deskripsi TableGen dan kode backend LLVM dipastikan oleh file .inc dihasilkan yang berisi kode C. Untuk mendapatkan gambar yang lengkap, mari kita lihat sedikit apa sebenarnya mereka.

Setelah masing-masing membangun, untuk setiap arsitektur, akan ada beberapa file .inc dalam direktori build, masing-masing menyimpan potongan informasi terpisah tentang arsitektur.Jadi ada file <TargetName>GenInstrInfo.incyang berisi informasi tentang petunjuk <TargetName>GenRegisterInfo.inc, masing-masing, yang berisi informasi tentang register, ada file untuk bekerja secara langsung dengan assembler dan output <TargetName>GenAsmMatcher.incdan <TargetName>GenAsmWriter.inclain-lain

Jadi terdiri dari apakah file-file ini? Secara umum, mereka berisi enumerasi, array, struktur, dan fungsi sederhana. Misalnya, Anda dapat melihat informasi yang dikonversi pada instruksi di <TargetName>GenInstrInfo.inc.

Pada bagian pertama, di namespace dengan nama target adalah enumerasi yang berisi semua instruksi yang telah dijelaskan.

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

Berikutnya adalah array yang menggambarkan properti instruksi const MCInstrDesc X86Insts[]. Array berikut berisi informasi tentang nama instruksi, dll. Pada dasarnya, semua informasi disimpan dalam transfer dan array.

Ada juga fungsi yang telah dijelaskan menggunakan predikat. Berdasarkan definisi predikat fungsi yang dibahas di bagian sebelumnya, fungsi berikut akan dihasilkan.

 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 } 

Tetapi ada data dalam file dan struktur yang dihasilkan. Di X86GenSubtargetInfo.incAnda dapat menemukan contoh struktur yang harus digunakan dalam kode backend untuk mendapatkan informasi tentang arsitektur, melalui itu di bagian sebelumnya ternyata 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); } }; 

Jika digunakan Subtargetuntuk menggambarkan berbagai konfigurasi XXXGenSubtarget.inc, enumerasi akan dibuat dengan properti yang dijelaskan menggunakan SubtargetFeaturearray dengan nilai konstan untuk menunjukkan karakteristik dan subtipe CPU, dan sebuah fungsi akan dihasilkan ParseSubtargetFeaturesyang memproses string dengan set opsi Subtarget. Selain itu, implementasi metode XXXSubtargetdalam kode backend harus sesuai dengan pseudo-code berikut, di mana perlu untuk menggunakan fungsi ini:

 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 } 

Terlepas dari kenyataan bahwa .incfile sangat tebal dan mengandung array besar, ini memungkinkan kami untuk mengoptimalkan waktu akses ke informasi, karena mengakses elemen array memiliki waktu yang konstan. Fungsi pencarian yang dihasilkan dengan instruksi diimplementasikan menggunakan algoritma pencarian biner untuk meminimalkan waktu operasi. Jadi penyimpanan dalam bentuk ini cukup dibenarkan.

Kesimpulan


Sebagai hasilnya, terima kasih kepada TableGen di LLVM, kami memiliki deskripsi arsitektur yang mudah dibaca dan didukung dalam format tunggal dengan berbagai mekanisme untuk berinteraksi dan mengakses informasi dari kode sumber backend LLVM untuk optimasi dan pembuatan kode. Pada saat yang sama, deskripsi seperti itu tidak mempengaruhi kinerja kompiler karena kode yang dihasilkan secara otomatis yang menggunakan solusi dan struktur data yang efisien.

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


All Articles