Description des architectures de processeur dans LLVM utilisant TableGen

À l'heure actuelle, LLVM est déjà devenu un système très populaire, que de nombreuses personnes utilisent activement pour créer divers compilateurs, analyseurs, etc. Un grand nombre de documents utiles sur ce sujet ont déjà été rédigés, y compris en russe, ce qui est une bonne nouvelle. Cependant, dans la plupart des cas, le principal biais dans les articles concerne le LLVM frontal et intermédiaire. Bien sûr, lors de la description du schéma complet du fonctionnement de LLVM, la génération de code machine n'est pas contournée, mais fondamentalement, ce sujet est abordé avec désinvolture, en particulier dans les publications en russe. Dans le même temps, LLVM dispose d'un mécanisme plutôt flexible et intéressant pour décrire les architectures de processeur. Par conséquent, ce matériel sera consacré à l'utilitaire quelque peu négligé TableGen, qui fait partie de LLVM.

La raison pour laquelle le compilateur a besoin d'informations sur l'architecture de chacune des plates-formes cibles est assez évidente. Naturellement, chaque modèle de processeur possède son propre ensemble de registres, ses propres instructions machine, etc. Et le compilateur doit avoir toutes les informations nécessaires à leur sujet afin de pouvoir générer un code machine valide et efficace. Le compilateur résout diverses tâches spécifiques à la plate-forme: distribue les registres, etc. De plus, les backends LLVM effectuent également des optimisations déjà sur la machine IR, qui est plus proche des instructions réelles, ou sur les instructions d'assembleur elles-mêmes. Dans de telles optimisations, les instructions doivent être remplacées et transformées; en conséquence, toutes les informations les concernant doivent être disponibles.

Pour résoudre le problème de description de l'architecture du processeur, LLVM a adopté un format unique pour déterminer les propriétés du processeur nécessaires au compilateur. Pour chaque architecture prise en charge, un .td contient une description dans un langage formel spécial. Il est converti en fichiers .inc lors de la construction du compilateur à l'aide de l'utilitaire TableGen inclus avec LLVM. En fait, les fichiers résultants sont de source C, mais ont probablement une extension distincte, juste pour que ces fichiers générés automatiquement puissent être facilement distingués et filtrés. La documentation officielle de TableGen est ici et donne toutes les informations nécessaires, il y a aussi une description formelle de la langue et une introduction générale .

Bien sûr, il s'agit d'un sujet très complet, où il existe de nombreux détails sur lesquels vous pouvez écrire des articles individuels. Dans cet article, nous considérons simplement les points de base de la description des processeurs même sans un aperçu de toutes les fonctionnalités.

Description de l'architecture dans le fichier .td


Ainsi, le langage de description formel utilisé dans TableGen a des caractéristiques similaires aux langages de programmation ordinaires et vous permet de décrire les caractéristiques de l'architecture dans un style déclaratif. Et si je comprends bien, ce langage est aussi communément appelé TableGen. C'est-à-dire Dans cet article, TableGen utilise à la fois le nom du langage formel lui-même et l'utilitaire qui en génère les artefacts.

Les processeurs modernes sont des systèmes très complexes, il n'est donc pas surprenant que leur description soit assez volumineuse. En conséquence, pour créer la structure et simplifier la maintenance des fichiers .td , vous pouvez vous inclure en utilisant la directive #include habituelle pour les programmeurs C. Avec l'aide de cette directive, le fichier Target.td est toujours inclus en premier, contenant des interfaces indépendantes de la plate-forme qui doivent être implémentées pour fournir toutes les informations TableGen nécessaires. Ce fichier comprend déjà un fichier .td avec des descriptions intrinsèques LLVM, mais en lui-même il contient principalement des classes de base, telles que Register , Instruction , Processor , etc., dont vous devez hériter pour créer votre propre architecture pour un compilateur basé sur LLVM. D'après la phrase précédente, il est clair que TableGen a la notion de classes bien connue de tous les programmeurs.

En général, TableGen n'a que deux entités de base: les classes et les définitions .

Cours


Les classes TableGen sont également des abstractions, comme dans tous les langages de programmation orientés objet, mais ce sont des entités plus simples.

Les classes peuvent avoir des paramètres et des champs, et elles peuvent également hériter d'autres classes.
Par exemple, l'une des classes de base est présentée ci-dessous.

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

Les crochets angulaires indiquent les paramètres d'entrée affectés aux propriétés de la classe. Dans cet exemple, vous pouvez également remarquer que le langage TableGen est typé statiquement. Les types qui existent dans TableGen: bit (un analogue du type booléen avec les valeurs 0 et 1), int , string , code (un morceau de code, c'est un type, simplement parce qu'il n'y a pas de méthodes et de fonctions dans TableGen au sens habituel, les lignes de code sont écrites en [{ ... }] ), bits <n>, liste <type> (les valeurs sont définies à l'aide de crochets [...] comme en Python et dans d'autres langages de programmation), class type , dag .

La plupart des types doivent être compris, mais s'ils ont des questions, ils sont tous décrits en détail dans la spécification de la langue, disponible sur le lien donné au début de l'article.

L'héritage est également décrit par une syntaxe assez familière avec:.

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

Dans ce cas, la classe créée, bien sûr, peut remplacer les valeurs des champs spécifiés dans la classe de base à l'aide du mot clé let . Et il peut ajouter ses propres champs similaires à la description fournie dans l'exemple précédent, en indiquant le type de champ.

Définitions


Les définitions sont déjà des entités concrètes, vous pouvez les comparer avec le familier à tous les objets. Les définitions sont définies à l'aide du mot clé def et peuvent implémenter une classe, redéfinir les champs des classes de base exactement de la même manière que décrit ci-dessus, et également avoir leurs propres champs.

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

Multiclasses


Naturellement, un grand nombre d'instructions dans les processeurs ont une sémantique similaire. Par exemple, il peut y avoir un ensemble d'instructions à trois adresses qui prennent les deux formes “reg = reg op reg” et “reg = reg op imm” . Dans un cas, les valeurs sont extraites des registres et le résultat est également enregistré dans le registre, et dans l'autre cas, le deuxième opérande est une valeur constante (imm - opérande immédiat).

Lister toutes les combinaisons manuellement est assez fastidieux; le risque de faire une erreur augmente. Bien sûr, ils peuvent être générés automatiquement en écrivant un script simple, mais ce n'est pas nécessaire, car un concept tel que les multiclasses existe dans le langage 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)>; } 

Dans les multiclasses, vous devez décrire toutes les formes d'instructions possibles à l'aide du mot clé def . Mais ce n'est pas une forme complète d'instructions à générer. Dans le même temps, vous pouvez redéfinir les champs qu'ils contiennent et faire tout ce qui est possible dans les définitions habituelles. Pour créer de véritables définitions basées sur une multiclasse, vous devez utiliser le mot clé 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">; 

Et en conséquence, pour chacune de ces définitions données via defm en fait, plusieurs définitions seront construites qui sont une combinaison de l'instruction principale et de toutes les formes possibles décrites dans la multiclasse. Par conséquent, les instructions suivantes seront générées dans cet exemple: ADD_rr , ADD_ri , SUB_rr , SUB_ri , MUL_rr , MUL_ri .

Les multiclasses peuvent contenir non seulement des définitions avec def , mais également des defm imbriquées, permettant ainsi la génération de formes complexes d'instructions. Un exemple illustrant la création de telles chaînes se trouve dans la documentation officielle.

Sous-cibles


Une autre chose fondamentale et utile pour les processeurs qui ont différentes variations du jeu d'instructions est le support de subtarget dans LLVM. Un exemple d'utilisation est l'implémentation LLVM SPARC, qui couvre trois versions principales de l'architecture de microprocesseur SPARC à la fois: version 8 (architecture V8, 32 bits), version 9 (architecture V9, 64 bits) et architecture UltraSPARC. La différence entre les architectures est assez grande, un nombre différent de registres de différents types, l'ordre des octets pris en charge, etc. Dans de tels cas, s'il existe plusieurs configurations, il vaut la peine d'implémenter la classe XXXSubtarget pour l'architecture. L'utilisation de cette classe dans la description entraînera de nouvelles options de ligne de commande -mcpu= et -mattr= .

En plus de la classe Subtarget elle-même, la classe Subtarget importante.

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

Dans le fichier Sparc.td , vous pouvez trouver des exemples d'implémentation de SubtargetFeature , qui vous permettent de décrire la disponibilité d'un ensemble d'instructions pour chaque sous-type individuel de l'architecture.

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

Dans ce cas, de toute façon, Sparc.td définit toujours la classe Proc , qui est utilisée pour décrire des sous-types spécifiques de processeurs SPARC, qui peuvent simplement avoir les propriétés décrites ci-dessus, y compris différents ensembles d'instructions.

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

Relation entre les propriétés des instructions dans TableGen et le code backend LLVM


Les propriétés des classes et des définitions vous permettent de générer et de définir correctement les fonctionnalités architecturales, mais il n'y a pas d'accès direct à celles-ci depuis le code source du backend LLVM. Cependant, vous souhaitez parfois pouvoir obtenir certaines propriétés d'instructions spécifiques à la plate-forme directement dans le code du compilateur.

TSFlags


Pour ce faire, la classe de base Instruction possède un champ spécial, TSFlags 64 bits, qui est converti par TableGen en un champ d'objets C ++ de la classe MCInstrDesc , généré sur la base des données reçues de la description TableGen. Vous pouvez spécifier n'importe quel nombre de bits dont vous avez besoin pour stocker des informations. Il peut s'agir d'une valeur booléenne, par exemple, pour indiquer que nous utilisons une ALU scalaire.

 let TSFlags{0} = SALU; 

Ou nous pouvons stocker le type d'instruction. Ensuite, nous avons bien sûr besoin de plus d'un bit.

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

Par conséquent, il devient possible d'obtenir ces propriétés à partir de l'instruction dans le code principal.

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

Si la propriété est plus complexe, vous pouvez la comparer à la valeur décrite dans TableGen, qui sera ajoutée à l'énumération générée automatiquement.

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


Prédicats de fonction


De plus, les prédicats de fonction peuvent être utilisés pour obtenir les informations nécessaires sur les instructions. Avec leur aide, vous pouvez montrer à TableGen que vous devez générer une fonction qui sera donc disponible dans le code backend. La classe de base avec laquelle vous pouvez créer une telle définition de fonction est présentée ci-dessous.

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

Vous pouvez facilement trouver des exemples d'utilisation dans le backend pour X86. Il y a donc sa propre classe intermédiaire, à l'aide de laquelle les définitions nécessaires des fonctions sont déjà créées.

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

Par conséquent, vous pouvez utiliser la méthode isThreeOperandsLEA dans le code C ++.

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

Ici, TII est l'information d'instruction cible, qui peut être obtenue en utilisant la méthode getInstrInfo() du MCSubtargetInfo pour l'architecture souhaitée.

Transformation des instructions lors des optimisations. Mappage d'instructions


Lors d'un grand nombre d'optimisations effectuées dans les dernières étapes de la compilation, la tâche se pose souvent de convertir tout ou partie des instructions d'un formulaire en instructions d'un autre formulaire. Compte tenu de l'application des multiclasses décrites au début, nous pouvons avoir un grand nombre d'instructions avec une sémantique et des propriétés similaires. Dans le code, ces transformations, bien sûr, pourraient être écrites sous la forme de grandes constructions de switch-case , qui pour chaque instruction écrasaient la transformation correspondante. En partie, ces énormes constructions peuvent être réduites à l'aide de macros, qui formeraient le nom nécessaire de l'instruction selon des règles bien connues. Mais encore, cette approche est très gênante, elle est difficile à maintenir en raison du fait que tous les noms d'instructions sont explicitement répertoriés. L'ajout d'une nouvelle instruction peut très facilement entraîner une erreur, car vous devez vous rappeler de l'ajouter à toutes les conversions pertinentes. Ayant été tourmenté par cette approche, LLVM a créé un mécanisme spécial pour convertir efficacement une forme d'instruction en une autre Instruction Mapping .

L'idée est très simple, il faut décrire des modèles possibles pour transformer des instructions directement dans TableGen. Par conséquent, dans LLVM TableGen, il existe une classe de base pour décrire ces modèles.

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

Regardons un exemple donné dans la documentation. Les exemples qui peuvent être trouvés dans le code source sont maintenant encore plus simples, car seules deux colonnes sont obtenues dans le tableau final. Dans le code principal, vous pouvez trouver la conversion d'anciens formulaires en de nouvelles formes d'instructions, des instructions dsp en mmdsp, etc., décrites à l'aide du mappage d'instructions. En fait, ce mécanisme n'est pas si largement utilisé jusqu'à présent, simplement parce que la plupart des backends ont commencé à être créés avant son apparition, et pour qu'il fonctionne, vous devez toujours définir les propriétés correctes pour les instructions, donc passer à celui-ci n'est pas toujours facile, vous pouvez en avoir besoin refactoring.

Ainsi, par exemple. Supposons que nous ayons des formes d'instructions sans prédicats et des instructions où le prédicat est respectivement vrai et faux. Nous les décrivons à l'aide d'une multiclasse et d'une classe spéciale, que nous allons simplement utiliser comme filtre. Une description simplifiée sans paramètres et de nombreuses propriétés peut ressembler à ceci.

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

Dans cet exemple, en passant, il est également montré comment remplacer une propriété pour plusieurs définitions à la fois en utilisant la construction let … in . Par conséquent, nous avons de nombreuses instructions qui stockent leur nom de base et leur propriété qui décrivent de manière unique leur formulaire. Ensuite, vous pouvez créer un modèle de transformation.

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

Par conséquent, le tableau suivant sera généré à partir de cette description.

PredSense = ""PredSense = "vrai"PredSense = "faux"
AJOUTERADD_ptADD_pf
SUBSUB_ptSUB_pf
MulMUL_ptMUL_pf

Une fonction sera générée dans le fichier .inc

 int getPredOpcode(uint16_t Opcode, enum PredSense inPredSense) 

Qui, en conséquence, accepte un code d'instruction pour la conversion et la valeur de l'énumération générée automatiquement PredSense, qui contient toutes les valeurs possibles dans les colonnes. La mise en œuvre de cette fonction est très simple, car il renvoie l'élément de tableau souhaité pour l'instruction qui nous intéresse.

Et dans le code backend, au lieu d'écrire un switch-case suffit d'appeler simplement la fonction générée, qui renverra le code de l'instruction convertie. Une solution simple, où l'ajout de nouvelles instructions, n'entraînera pas la nécessité d'une action supplémentaire.

Artefacts générés automatiquement (fichiers .inc )


Toute l'interaction entre la description TableGen et le code backend LLVM est assurée par les fichiers .inc générés qui contiennent le code C. Pour obtenir une image complète, voyons un peu ce qu'ils sont exactement.

Après chaque build, pour chaque architecture, il y aura plusieurs fichiers .inc dans le répertoire de build, chacun d'entre eux stockant des informations distinctes sur l'architecture.Donc , il y a un fichier <TargetName>GenInstrInfo.inccontenant des informations sur les instructions <TargetName>GenRegisterInfo.inc, respectivement, qui contient des informations sur les registres, il y a des fichiers de travail directement avec l'assembleur et sa sortie <TargetName>GenAsmMatcher.incet <TargetName>GenAsmWriter.incetc.

En quoi consistent ces fichiers? En général, ils contiennent des énumérations, des tableaux, des structures et des fonctions simples. Par exemple, vous pouvez consulter les informations converties sur les instructions dans <TargetName>GenInstrInfo.inc.

Dans la première partie, dans l'espace de noms avec le nom de la cible se trouve une énumération contenant toutes les instructions qui ont été décrites.

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

Vient ensuite un tableau décrivant les propriétés des instructions const MCInstrDesc X86Insts[]. Les tableaux suivants contiennent des informations sur les noms d'instructions, etc. Fondamentalement, toutes les informations sont stockées dans des transferts et des tableaux.

Il existe également des fonctions qui ont été décrites à l'aide de prédicats. Sur la base de la définition de prédicat de fonction discutée dans la section précédente, la fonction suivante sera générée.

 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 } 

Mais il y a des données dans les fichiers et les structures générés. Dans X86GenSubtargetInfo.incvous pouvez trouver un exemple de la structure qui devrait être utilisée dans le code principal pour obtenir des informations sur l'architecture, à travers elle dans la section précédente, nous avons obtenu 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); } }; 

S'il est utilisé Subtargetpour décrire diverses configurations XXXGenSubtarget.inc, une énumération sera créée avec les propriétés décrites à l'aide de SubtargetFeaturetableaux avec des valeurs constantes pour indiquer les caractéristiques et les sous-types du CPU, et une fonction sera générée ParseSubtargetFeaturesqui traitera la chaîne avec le jeu d'options Subtarget. De plus, l'implémentation de la méthode XXXSubtargetdans le code backend doit correspondre au pseudo-code suivant, dans lequel il est nécessaire d'utiliser cette fonction:

 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 } 

Malgré le fait que les .incfichiers sont très volumineux et contiennent d'énormes tableaux, cela nous permet d'optimiser le temps d'accès aux informations, car l'accès à un élément du tableau a un temps constant. Les fonctions de recherche générées par des instructions sont implémentées à l'aide d'un algorithme de recherche binaire pour minimiser le temps de fonctionnement. Le stockage sous cette forme est donc tout à fait justifié.

Conclusion


En conséquence, grâce à TableGen dans LLVM, nous avons des descriptions d'architecture lisibles et facilement prises en charge dans un format unique avec divers mécanismes pour interagir et accéder aux informations à partir du code source backend LLVM pour les optimisations et la génération de code. Dans le même temps, une telle description n'affecte pas les performances du compilateur en raison du code généré automatiquement qui utilise des solutions et des structures de données efficaces.

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


All Articles