We're sorry but this page doesn't work properly without JavaScript enabled. Please enable it to continue.
Feedback

Lessons in TableGen

00:00

Formale Metadaten

Titel
Lessons in TableGen
Serientitel
Anzahl der Teile
561
Autor
Lizenz
CC-Namensnennung 2.0 Belgien:
Sie dürfen das Werk bzw. den Inhalt zu jedem legalen Zweck nutzen, verändern und in unveränderter oder veränderter Form vervielfältigen, verbreiten und öffentlich zugänglich machen, sofern Sie den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
Identifikatoren
Herausgeber
Erscheinungsjahr
Sprache

Inhaltliche Metadaten

Fachgebiet
Genre
Abstract
TableGen is LLVM's DSL for describing intrinsics, backends' machine instructions, physical registers, machine scheduling models, and a bunch of other things. It is extremely flexible and powerful, but can also be rather aggravating. Most people who spend a significant amount of time working on an LLVM backend probably develop a love/hate-relationship with it. The goal of this talk is to give a brief overview of what TableGen offers -- frontend, application-specific backends, generic table emission backend, idiosyncratic type system, and these days even limited functional-style programming -- and a brief introduction on how to use it. The focus will be mostly on the frontend -- that is, syntax and semantics of the TableGen DSL itself -- rather than on specific backends. Along the way, I want to share some lessons learned and decisions made during a major refactoring of the TableGen frontend that I undertook in early 2018 to iron out many of TableGen's quirks and shortcomings that had accumulated over the years, as well as some glimpses of the advanced TableGen uses in the AMDGPU backend that motivated that refactoring.
10
58
80
111
137
Vorschaubild
15:21
159
Vorschaubild
18:51
168
Vorschaubild
26:18
213
221
Vorschaubild
15:22
234
Vorschaubild
49:51
248
Vorschaubild
23:06
256
268
283
Vorschaubild
28:38
313
Vorschaubild
1:00:10
318
Vorschaubild
21:35
343
345
Vorschaubild
36:13
353
Vorschaubild
18:44
369
370
373
Vorschaubild
44:37
396
Vorschaubild
28:21
413
Vorschaubild
16:24
439
455
Vorschaubild
25:10
529
Vorschaubild
15:36
535
Vorschaubild
28:04
552
Formale SpracheSpezialrechnerTypsystemKlasse <Mathematik>TabelleLeistungsbewertungBefehl <Informatik>Elektronische PublikationDisassemblerSpeicherabzugArchitektur <Informatik>Desintegration <Mathematik>ProgrammbibliothekDefaultGebäude <Mathematik>CodeVirtuelle MaschineDatensatzSoftwareentwicklerRegulärer GraphGeneigte EbeneInklusion <Mathematik>QuellcodeOrdnung <Mathematik>MultiplikationssatzTypentheorieGenerizitätFunktionalZeichenketteFolge <Mathematik>Umsetzung <Informatik>Automatische IndexierungSpannweite <Stochastik>MaschinenspracheSchedulingQuellcodeDatensatzGlobale OptimierungProgrammbibliothekTypentheorieFormale SpracheFront-End <Software>FunktionalPoisson-KlammerQuadratzahlZahlensystemZeichenketteProblemorientierte ProgrammierspracheGanze ZahlCodeRegulärer Ausdruck <Textverarbeitung>CASE <Informatik>Gebäude <Mathematik>BitIntegralFolge <Mathematik>PunktInverser LimesMereologieMusterspracheZweiWinkelMessage-PassingShader <Informatik>Sampler <Musikinstrument>Reguläres MaßLeistungsbewertungMailing-ListeENUMVirtuelle MaschineMikroarchitekturTrennschärfe <Statistik>Syntaktische AnalyseVererbungshierarchieByte-CodeSpeicherabzugStochastische AbhängigkeitTemplateKonstanteQuick-SortNatürliche ZahlKategorie <Mathematik>Klasse <Mathematik>WärmeausdehnungSchlüsselverwaltungRechenschieberMultiplikationsoperatorElektronische PublikationKernel <Informatik>SchnittmengePhysikalisches SystemDatenflussAdditionLie-GruppeGenerator <Informatik>DisassemblerAutomatische IndexierungTabelleMomentenproblemDeskriptive StatistikUmwandlungsenthalpieVollständiger VerbandDebuggingHilfesystemTypinferenzInklusion <Mathematik>Data DictionarySystemzusammenbruchRechter WinkelFehlermeldungGeschlecht <Mathematik>Program SlicingComputeranimationFlussdiagramm
TypentheorieCodierung <Programmierung>Konvexe HülleRegulärer Ausdruck <Textverarbeitung>ÄquivalenzklasseVirtuelle MaschineZeichenketteSchlussregelKlasse <Mathematik>TemplateParametersystemDefaultVererbungshierarchieMinkowski-MetrikQuellcodeFormale SpracheGleichheitszeichenNotepad-ComputerMultiplikationLeistungsbewertungBefehl <Informatik>ProgrammierungVariableTermOrdnungsbegriffZellularer AutomatTextur-MappingKlasse <Mathematik>MereologieMinimumGruppenoperationData DictionaryDatenfeldFolge <Mathematik>Stochastische AbhängigkeitBitSchnittmengeReelle ZahlFront-End <Software>BefehlscodeWinkelTemplateRechenschieberUmsetzung <Informatik>MultiplikationRechter WinkelDatensatzMaschinenspracheNichtlinearer OperatorFunktionalParametersystemTopologieBefehl <Informatik>Vorzeichen <Mathematik>VariableSoftwaretestSelbstrepräsentationInstantiierungDefaultTrennschärfe <Statistik>Arithmetischer AusdruckGraphVererbungshierarchieKonstanteRelativitätstheorieCodierung <Programmierung>Mailing-ListeFamilie <Mathematik>Funktionale ProgrammierspracheQuellcodeFreier ParameterSchlussregelKartesische KoordinatenPoisson-KlammerFormale SpracheOvalTypentheorieMusterspracheTabelleCASE <Informatik>DämpfungDisjunktion <Logik>InformationsspeicherungDatenstrukturComputeranimation
Ganze ZahlIterationSpannweite <Stochastik>IndexberechnungRechenwerkMultiplikationSchnittmengeRegulärer GraphProgrammMotion CapturingWechselseitige InformationARM <Computerarchitektur>NP-hartes ProblemNichtlineares ZuordnungsproblemFunktion <Mathematik>ZeichenketteAuswahlverfahrenFormale SpracheSpezialrechnerTypentheorieKlasse <Mathematik>Befehl <Informatik>LeistungsbewertungFunktionalOperations ResearchZahlenbereichAdressraumDifferenteDimensionsanalyseVirtuelle MaschineTabelleVersionsverwaltungBefehlscodeDerivation <Algebra>InformationENUMHausdorff-DimensionGradientMailing-ListeParametersystemInstallation <Informatik>BildschirmmaskeLokales MinimumMenütechnikProgrammiergerätProgram SlicingRechter WinkelTouchscreenKlasse <Mathematik>SchnittmengeCASE <Informatik>MusterspracheMereologieParametersystemÄhnlichkeitsgeometrieDatenfeldZeichenketteLoopDatensatzFunktionalFront-End <Software>Gleitendes MittelPartielle DifferentiationTypentheorieMapping <Computergraphik>VariableZweiDatenbankGradientMailing-ListeMaschinenspracheQuellcodeMatchingGrenzschichtablösungEinsTabelleDerivation <Algebra>Bildgebendes VerfahrenStochastische AbhängigkeitNichtlinearer OperatorGraphikprozessorGanze ZahlCodeZahlenbereichElement <Gruppentheorie>Kartesische KoordinatenFamilie <Mathematik>RichtungMultiplikationAuswahlverfahrenMAPBitMultiplikationsoperatorSpannweite <Stochastik>ProgrammverifikationOrthogonalitätDämpfungKomplex <Algebra>Codierung <Programmierung>AusnahmebehandlungSchlüsselverwaltungBefehl <Informatik>AuswahlaxiomBildschirmmaskeHash-AlgorithmusQuaderDifferenteENUMProfil <Aerodynamik>KombinatorikGenerizitätKonfiguration <Informatik>MaßerweiterungKoordinaten
MenütechnikTypentheorieWurm <Informatik>Formale SpracheSpezialrechnerTypsystemKlasse <Mathematik>TabelleBefehl <Informatik>LeistungsbewertungProgrammierungRichtungMaßerweiterungNichtlinearer OperatorMultiplikationVererbungshierarchieCodeZeichenkettePrädikat <Logik>Komplex <Algebra>OrthogonalitätMessage-PassingFehlermeldungDatensatzCASE <Informatik>MultiplikationsoperatorArray <Informatik>Klasse <Mathematik>Projektive EbeneTrennschärfe <Statistik>TabelleSystemzusammenbruchRekursive FunktionFehlermeldungMatchingPunktHash-AlgorithmusBefehl <Informatik>SpeicherabzugOrdnung <Mathematik>Web logAutomatische HandlungsplanungFunktionalTuring-TestFormale SpracheDivergente ReiheVideokonferenzFront-End <Software>Elektronische PublikationKonditionszahlAutomatische IndexierungMusterspracheQuick-SortBitRechter WinkelTypentheorieVererbungshierarchieInformationMailing-ListeSystemaufrufAuswahlverfahrenInverser LimesParametersystemSelbstbezüglichkeitGenerator <Informatik>ImplementierungFunktion <Mathematik>PräprozessorEin-AusgabeRechenschieberDateiformatZeichenketteNichtlinearer OperatorDebuggingSyntaktische AnalyseMultiplikationPhysikalisches SystemComputeranimation
PunktwolkeComputeranimation
Transkript: Englisch(automatisch erzeugt)
All right. Hello, everybody. My name is Nicola Henle. I work at AMD on the GPU side of the company where we develop the AMD GPU target in LLVM, which is upstream, and we use to
compile compute kernels and graphics shaders directly into binary that runs on the GPU. But this talk is not about that. This talk is about TableGen, which is the domain-specific language that is used in part to build LLVM itself. Now, my own personal history with TableGen is probably fairly standard initially. I mean, I started working on the backend, and you just
have to use it at some point. I copied and pasted stuff. And then last year, there was something that I worked on in our backend that really ran into the limitations of the TableGen frontend, ran into weird errors, crashes, and so on. So, I dug in more deeply, and in part,
this talk is my attempt to spread some of the knowledge that I gained while looking deeply into TableGen, fixing some of these problems that I encountered. So, the agenda is a brief overview of what is TableGen, where it is used very roughly, and then really look at what
are the features of the TableGen language. What can you do with it? Maybe you'll learn about some interesting patterns that you can use in your own TableGen sources. And depending on how time permits, at the end, I want to take a bit of a more deep dive into one of the more advanced uses of TableGen language features in our backend. The nature of this
talk is such that you may have questions that it makes sense to ask right then and there, so don't hesitate to do that when it makes sense. All right, so what is TableGen? So, TableGen is a tool, LLVM-TableGen, into which you
can feed a TableGen source, like the example you see on the left-hand side, and it spits you out some automatically generated C++ code that you then include in your handwritten C++ code. It is used for all sorts of things in backends, so for example, machine instruction
descriptions, register descriptions, scheduling properties of the microarchitectures of the machines that you're targeting. It automatically generates bytecode for instruction selection, assembly parsers and emitters, disassemblers, et cetera. And it all does this based
on these kind of sources that you see here in this example. It's just the definition of four machine instructions that we have in our backends, you know, scalar bitwise and an OR in 32 and 64 bits. So, I said it's one tool, LLVM-TableGen, that's a bit of
a lie. I know actually of two TableGen tools, one in LLVM and one in Clang. They use the same frontend, which is just a library that is reusable, a part of LLVM, and they have different backends for different purposes. So, in general, the flow of what TableGen does is it reads your TableGen sources, the frontend parses it, does some evaluation
on it, and produces a big list of record definitions, which I'll show you later. And then the backends, they don't care about the original source anymore, they just look at this set of records, they maybe filter them to look at only the records that they
are interested in, which depends on the backend, interpret what is in those records, and then use that to generate the purpose-specific autogenerated C++. You specify the backend that you want to use on the TableGen command line, and if you don't specify any, then
it'll just dump all the record definitions. That's extremely verbose, but it's also extremely helpful sometimes if you want to kind of debug some gnarly problem that you have with your TableGen source. So, if there is one lesson, only one lesson to take away from this talk, it should actually be this, that if you're running into problems with TableGen, don't
be afraid to just dump all the records and use, you know, Regex searches in what you get to figure out in detail what's going on. But usually you don't invoke TableGen yourself, you let CMake do it, it's all integrated. Usually you don't have to worry about it too much, there's some snippet of how it is integrated as an example. The one lesson that you should take away from this is that there is this setting, LLVM optimized
TableGen, which you should absolutely use in debug builds, because even if you do a debug build of LLVM, usually you don't want a debug build of TableGen, right? So, optimize that, it helps your build times. Okay, so records. So, what are records?
Well, they're basically just key value dictionaries, usually with a name. So, on the right-hand side, you see at the top this little snippet that I showed two slides earlier, and this little snippet actually gets expanded into this, you know, big record definition, and this is actually only just a small excerpt of it. And the way that this
expansion happens is TableGen has a notion of classes, which are basically templates for records. So, at the top you see this, you know, S and B32 colon SOP2-32. SOP2-32 is a class, TableGen class, which is defined in our back-end's TableGen sources, which then in
itself, you know, derives from other classes, and once all the transitive inheritance is done, you get this big record. You have at the top, this is something that the record dumping prints out in these double slash comments, a list of all the classes that were transitively
inherited. So, most of these are target specific, but in particular, there is this class called instruction. So, instruction is a target independent class, which all the back-ends that are interested in machine instructions use to filter out the record definitions. So, they look at all the record definitions that somehow directly or indirectly
inherit from instruction, and then they work with those. And yeah, like I said, usually records are named. So, in this case, we have a name that actually appears as, you know, the enum in C++ to refer to that machine instruction. Not all of them have to be named. Like,
if you have stand-alone instruction selection patterns, they don't need a name, although just giving them a name can be helpful for debugging issues. Yeah, so TableGen is not just a tool, it's also this domain-specific language, and it has a kind of a core language, which is used to write down record definitions, and then a larger set of features around it that allow
you to generate kind of regular sets of records fairly easily, right? Like you saw before in the example, we have this, you know, scalar and scalar or, and obviously these instructions are going to be very similar. So, we have tools to generate these big records that are mostly
the same in an easy way. So, this is kind of a sketch of the kind of TableGen source files that live in LLVM, and how they include each other. So, the main bulk is typically in just targets, back-end definitions, and you usually have one top-level file that includes everything else,
because you can have textual inclusion, but not really include guards, and also, yeah, there were some additions fairly recently, but that's the way things work. You include all the various files in your back-end that define instructions, scheduling, and et cetera, and you
include the target independent stuff. And the other big chunk is the intrinsic definitions, and there is some random other things as well. All right, so, so much for the very brief overview. Now, I want to go into just TableGen language features, and there is a list of them
that I want to cover. Okay, so very brief look at the type system. It's fairly standard, although, I mean, a bit quirky. You have bits and integer types. The integers are 64-bit only.
You have bit sequences. You can cast between them. You can slice bit sequences. There is a string type. There is also a code type, which is a bit strange, but, you know, there is this notation with the square and curly brackets to have, like, a convenient way to have C++
fragments in your TableGen source, which then gets pasted into some auto-generated larger function. Really, you'd only need one type for that, but whatever. There is a list type, which is, you know, just a homogenous list. TableGen does some type inference,
but the TableGen front-end is basically one single pass through the source, so there is no, you know, like, type inferencing pass or anything. So, there are some cases where you need to help out and put the type in these angle brackets, as the second example shows. You don't need that often, but sometimes you need it. You can index into lists,
but only with literal constants at the moment, and then there are some other unset DAG and class record types that I will explain in the next few slides. Okay. So, unset values is an interesting thing because, of course, usually all the
key values, like the values in your dictionary, in your records, should be defined, right? But sometimes they're not, and there is actually one. So, at the top right, you see an example of how these variables or values can end up unset. Both of them are unset in the same way, one explicitly with the question mark,
the other implicitly. But there is one nice application having to do with instruction encoding for having unset values on purpose. So, what you see on the left-hand side is just a short extract of the table-gen source of our backend, which defines one encoding type.
So, it's a 32-bit instructions, and it's the ENK32. It has this inst variable, which is a placeholder for the 32 bits of the instruction, and then there is an encoding called vinterp, which defines the fields of the encoding. You see that the top, so the bits 31 to 26, they're assigned to a constant value, right? That's what defines this encoding class.
And then other bits out of these 32 are parceled out to variables that are there defined at the top, like the vdest, which is just undefined, or the op, which is the opcode, which is actually passed in as a kind of a template argument to this vinterp class.
And on the right-hand side, you see one of the machine instructions or the record corresponding to it that uses this encoding. And you see that now this inst field with its 32 bits has been expanded. So, there should be eight entries in each row there. And you see that, for example,
in blue, the two bits that correspond to the opcode have been filled in to 00. But a lot of the fields, like vdest, vsource, et cetera, which are just unset bit sequences. And the whole
machinery for instruction encoding and disassembly is built based on these relations. So, the relations of which fields, defining registers, et cetera, are where is read out of this. And it's also tied to, you see there, the DAG out operand list, for example. It mentions the name vdest, which is matched to the variable name vdest to tie the representation
of operands in your machine instruction to the encoding. Then there is the DAG type. So, it's called DAG, like directed acyclic graph, because it's used in instruction selection. But
I think the best way to think about it is really that it's a kind of s-expression where you have an operator and then a list of arguments, except that each of the arguments can optionally also be assigned a name. It's a convenient way of having heterogeneous nested structures.
And like I said, most of it's used for instruction selection patterns. So, there is an example down here, also from our back end. The first row in there describes a pattern that is looked for in the selection DAG. And the row below is the machine instruction
that should be generated for that pattern. And so, on the top row, you see the inner thing is a bitwise XOR that is used once. XOR is something that is named for 0 with a constant. This is then converted or interpreted as a 16-bit float and converted to a 32-bit float.
And it so happens if you XOR that value to a 16-bit float, well, it flips the sign bit. Right? And in our ISA, many instructions have the ability to, aside from whatever else they're doing, to flip the sign bit. Right? And so, we just replace it by this conversion instruction with a modifier that says negate the source. Right? Yeah?
Yeah. Even though the name is DAG, it really more represents a tree than a DAG. So, that's why I said the name is a bit misleading. Yeah. I mean,
you can't really express a DAG in the source language with this syntax. All right. And then, of course, there's classes. Right? As I already said, classes are basically templates for records. They have inheritance, although I actually don't have
an example of that here on the slide, but the syntax is basically the same as C++ for the inheritance. And so, on the right-hand side, right, in the example above, you see some source that's not taken from anything real. That's just a random example. And at the
right-hand side, so there are two records that are explicitly defined. Right? My record derives from both of these classes that are defined. And I think the main thing to point out here is an interesting feature, which is in the other record, which has this B
angle brackets three, which is actually an anonymous instantiation of the class. So, you see here this, you know, anonymous zero record, which is generated automatically, which is quite a useful feature. And the other thing that's interesting to note is that every class has an implicit template argument called name, which is replaced by the name of the record
that is being instantiated. All right. So, when you know about classes and records, it's very tempting to try to define all the variables in your records as functions of template arguments, class template arguments. This works, but it tends to lead to a design
of your table gen classes where they have lots and lots of template arguments and it becomes a bit of a mess. For these things, it's better to use let statements. So, let statements are a way to override values that are defined in the classes that you inherit from, for example. And one very interesting thing about this is that actually expressions
are evaluated late. So, if you have here the class A again on the left is an example source, it has a template P, template P is assigned to a variable X and then X is assigned to Y. This assignment doesn't happen immediately. It's delayed as late as possible, which
means that below when you instantiate A2, for example, and then say let X equal 17, in the fully evaluated instantiation on the right-hand side, you see that both X and Y get this value 17. Rather than having the value 2, that would be implied by the
template parameter that you passed in. It's important to remember that the let statement is not the one that you know from Rust or functional languages. It doesn't define a new variable. It just allows you to override an existing one. So, classes, I said, are
basically templates for records. Multi-classes are templates for sets of records. Okay? So, maybe best to look at an example there on the bottom left. This is part of a definition of intrinsics. We have a def M. So, def M is used to instantiate multi-classes,
which is given a name, which is kind of a base name for intrinsics, AMD DCN work group ID. It instantiates this multi-class that is defined above, and this multi-class defines three records with names, you know, underscore X, underscore Y, underscore Z, which are
concatenated with the base name. And each of those then inherits from, you know, some class, helper class that we defined, which in turn derives from a target independent intrinsic class, which means that we're defining intrinsics here. So, we're defining three records and at once. Like classes, multi-classes have this implicit
name argument. So, by default, the names that are instantiated are just like the base name that comes from the def M concatenated from the name that the record has inside the multi-class. But you can play around with this, like the example on the right hand side shows. If you look at this def of the rec three, the rec three is actually
prefixed to the base name. So, the rule is that if you explicitly mention the name template argument, then that overrides the default concatenation. That can be useful in some ISAs where you have, like, instruction families where some of the instructions are prefixed with something.
There's some interesting corner cases. So, interestingly, multi-classes also support inheritance from other multi-classes, which is really basically the same as just putting a def M of the base multi-class, as I've shown there, while preparing the slides. I noticed that that's not entirely true, and maybe that should be changed, but yeah.
If you have a def M, so the def M instantiates a multi-class, right? But it can also actually inherit from classes, which can be useful for tagging the records that you're instantiating with something like instruction mappings and stuff like that. But it cannot have a body, so if you want to override anything that is, you know, any
variable that is defined in the records that you instantiate, you need to use global let statements. Okay, so multi-class is one way of generating many regular records. There is another way of doing that, which is for each.
And for each is, yeah, it's a for each loop. Like, you loop over a list or a range of integers, so there is nothing too special about it. In case I haven't mentioned it here in the example at the top, you know, the hash is a concatenation operator, string concatenation, which is good to know. And there is also this exclamation mark add, which is a built-in function to add integers. You know, this is just part of
the encoding of the register in the ISA. One interesting thing about for each is that you can abuse it as an if statement. So, this is an example from our back-end as well. So, we have a family of instructions that we call just VOP1 instructions,
and they have multiple incarnations, right? They have the basic E32 VOP1, there is an extended 64-bit encoding, there's something that has a feature called SDWA for all of them. But then for some of them, there is also an incarnation that uses a feature called DPP. And now, if you have a class of instructions that is regular in that way except for this
one thing, you have multiple options for realizing that in TableGen, right? One way would be to just have different multi-classes, right? Multi-class for VOP1 with and multi-class for VOP1 without. The problem is with this kind of approach that you can easily get a combinatorial explosion of multi-classes. So, what we do here instead is this if
statement basically. So, we have this notion of a VOP profile which is passed as a template argument to the multi-class, and it has a bit that tells us whether the instruction should have this DPP or not. And then there is an interesting pattern here which is basically
using TableGen classes as functions, right? This bool to list class, it takes this bit value as an argument, and then it has a variable called ret which is the return value of the function that we're defining. And if the bit is zero, then we return an empty list
which means that the for each will not do nothing or we return a one element list and the for each will do something. So, that's a pattern that we use quite extensively and it's quite useful. Yeah, for each versus multi-class, both of them serve kind of similar
purposes. I would say that multi-class is more idiomatic for TableGen and it's often easier to reuse, but for each has some programmability advantages. So, both have their place and use them as it makes sense. Another kind of niche feature is the DefSet
which as the example on the top right shows you allows you to capture all the records that are defined inside, you know, the outer curly braces into a list that you can then later reference and use in a for each, for example, to iterate over them.
We use this in AMD GPU to define for these intrinsics that are up there a generic table. So, generic table is generic searchable tables is one back end that is a fairly versatile
way of exporting data out of TableGen without writing your own custom back end. So, what's happening here is that we just define a generic table and you really should think of it as like a database table which is called resource intrinsics. The fields are listed there, you know, three fields. The rows of the table come from records that
are derived from the given filter class and TableGen, the searchable tables back end will give you a function which you pass in a key of your choice and it will search the table and produce the corresponding struct if it exists and you can actually
also define multiple keys over generic tables which is nice for mapping from between two things back and forth in both directions. So, here we use the for each based on lists that we previously captured to define the rows of this table. And now, again, you
might ask, so def set is really a niche feature and even though I added it myself, I have to say that it's not very idiomatic for TableGen. In most cases, you'll probably want to instead use like multi classes or possibly heterogeneous multi classes where
you define maybe some intrinsics, sorry, some instruct machine instructions and then some other class maybe for those tables at the same time. But in this particular case, so something interesting happens, right? Because the intrinsics are defined in target independent TableGen sources which are included by all back ends, by all targets, right?
And they have to be defined there because it's a global enum that is the same for all targets. It's not target specific. But the table, like if we were to define the table in the same place, then every target would get this table which is really specific to our back end, right?
That doesn't make sense. So, instead, we do the separation using the deficit feature. We already saw one built-in function, this exclamation mark add. There are a whole bunch of others. The most interesting ones are probably the casts. So, you are able to cast between
strings and records actually, which means that if you cast a record to a string, it gives you the name. If you cast a string to a record, then it looks for a record of that name and gives it back to you if it has been defined previously in the source file. And then, so there is for each which is basically a map function is what it would be called
in Haskell or something. And there is a left fold, which is also nice to have. All right. So, this is what we went over. We went over most of the stuff. Now, I said we could maybe do a deep dive into some more advanced application of TableGen if we have the
time. We don't have that much time anymore. So, I wonder which part might be best. So, we have this very complex family of intrinsics for image operations in our backend, right?
There is a lot of orthogonality here where part of these image instructions is the dimensionality of the image, right? If it's 1D, 2D array, 2D. And then the arguments of the intrinsic depends on that dimensionality, of course. It's either only an S coordinate or it's an S and a T and a slice in the array or it's just an S and T, et cetera. You may have this
dot D, which stands for partial derivatives, in which case you have all these green, you know, DS over DH, DT over DH, which are partial derivatives of coordinates with respect to screen coordinates. And this really large number of intrinsics we want to define some way. And we do
all of this in TableGen. So, you get code like this, which at the bottom left, you see it defines the notion of a 2D array dimensionality of an image. We give it a name, 2D array, and says,
okay, there are two coordinates that are relevant for derivatives, namely S and T and one coordinate which isn't. And then in this class that we define above, we define a variable gradient args, which is the list of gradient arguments for intrinsics that need them, which are defined basically using function calls, right? We first iterate over the given coordinate
names and use string concatenations to define these names, DS, DH, et cetera. And then we pass that to that class which has really a role of a function, which is defined above make argument list, and it produces what is there on the right in the box. So, why do we want, so what is that that we have on the right? And why do we want it? Well,
when we define intrinsics, we have to define the types of the arguments of the intrinsics, right? And there are, we can give a type explicitly such as 32-bit float, but we actually want to be able to pass not just 32-bit floats but also half precision floats. So,
we need an any float type for the DS, DH argument. And then the arguments that come after that should be constrained by the IR verifier to be of the same type. So, that's what the LLVM match type is for. And what this make argument list function does is precisely generate an
array of this form by taking, you know, the first name, combining it with the base type, and then doing a mapping which maps all the names to this AMD GPR pair of match type and an argument name. Okay, and any questions about this? This is a lot to-
Oh, those are things that we use, we don't use them for much internally. They're convenient for debugging and I have plans for other things that we can do with them, but yeah. All right. Other questions about this? I will upload the slides, of course,
so you can look at it later. We do more stuff like this. So, for example, if you have two of these arrays of arguments, you need to, and all of them use LLVM match types, you need to adjust the index that the match type refers
to. That's what that stuff is about, especially that stuff on that slide, but we don't really have the time to look at it in detail. All right. So, I just want to close with some final thoughts, maybe a brief look at what we covered, you know, brief overview of TableGen, quick run-through of pretty much all the features of the front-end language, except the
built-in functions not in detail, and a brief example of what you can use, what you can do with it. You know, some things to keep in mind, you know, multiclass versus foreach, calling TableGen directly if you have problems to dump all the records. These
are kind of things that I think you should take home. Some possible things to improve in TableGen in the future, I'm not going to work on them because, well, priorities just aren't like that, but there are still some cleanups that could be done in the type system, right, to smooth some of these corner cases. It would be convenient to have the hash operator, not just
work for strings, but also for lists and DAGs. That would be convenient. The thing with multiclass inheritance that I mentioned, and then, you know, back-ends. I mean, it used to be, like last year when I started diving deep into TableGen, my problem was that I always ran into
crashes and errors in the front-end. This, at least for me, this doesn't happen anymore. If it happens for you, talk to me, but now the problem is that, the main problem is that I do something with selection DAG, you know, the iCells patterns, and I just get weird error messages that are super difficult to understand, at least for me, and I think there are some things there that are also in the feature set that could maybe be more orthogonal, but yeah,
that's, that would be something, probably another big project and not for this talk. So, with that, thank you very much. So much for this talk. I found it immediately useful, and like, I know what I need to do to go debug some issues I'm facing right now.
Thank you. You mentioned dumping, like invoking TableGen itself, so given a TD file, you can just invoke TableGen on it to dump, like preprocess kind of just record definitions, and then is there a way to go from that to then dump the C++ that gets submitted? Okay, so the question is, can you, I said you can run TableGen itself to, so you
can dump the records, can you then go from there to the, to the generated include files? So, of course, you can go to the generated include files directly by just invoking the
back end directly, but if you want to, like, edit the stuff that's in between, that's currently not possible, because, so you could just cut out the record definitions and feed them back into TableGen as input. I think there are two problems that you'd run into. One problem is that I don't think the output of the dump is in, like, you have to define
a variable before you refer to it, and I don't think the dumping makes sure to preserve that order. I think it just dumps alphabetically. That's one thing. The other thing is that the back ends filter the records out based on the classes that they inherit, and the way that the records are dumped currently, you get the information
of the inheritance in a comment, and not as actual, you know, inheritance in the TableGen language. So, I think those would be the two major hurdles to enable that. Still, thank you so much for the talk. I appreciate it. Yeah. Something other than C++? Yeah, so you, obviously you can generate other stuff than C++ from a back end, and
in fact, somebody not so long ago added a back end that dumps the records in JSON format. So, you know, you could then use some other tool, but yeah, you could dump other things than, you know, generate other things in C++ if you wanted to, yeah.
The back ends are written in C++. You can do whatever. Yeah. You also explained how to use classes to actually implement something like function calls. Would it be better to just have function calls syntax rather than within a more obfuscated
way? So, right. The question was, I showed how you can kind of abuse classes as functions, and wouldn't it be better to have a dedicated syntax for that? I've been, I've thought about this myself. I didn't, back when I did all this work, I didn't find a solution to it that I was completely convinced of.
But it's certainly something to think about, you know, maybe there, maybe keep it as classes but allow invoking it with the exclamation mark syntax to make it at least a little bit more approachable. I mean, I—
I'm maybe wondering, every so often I just have to have a brief look at table gen files, and it always takes me a very long time to ramp up on understanding what this is actually mean. Yeah. I'm just wondering if that's one of the very small tweaks that helps five percent too much. Yeah. You can just read the stuff.
It might, it might help the understandability, yeah, but to have like a dedicated defined function syntax, you'd, it's, it's, it's a thought that I was also thinking about this, but it would have to be, because, because this pattern isn't so, isn't used that
often today. All right. So, so it would have to be something that's very intuitive, but also fit well with the other stuff in table gen, so. All right. I mean, yeah? Do you think there might be a scope for a, because I know there is a table gen manual, but it's pretty much a reference manual. It's not really a tutorial manual.
Do you think there's a scope to contribute some of this to a sort of, there's been a table gen, a how-to table gen, and it's simply connected to the video. Yeah. So, the question was, is there a scope to, to add better documentation or tutorial, you know, to, to LLVM? I wish I had the time. You know, I started a series of, of blog posts on my blog where the idea was that
maybe that could one day lead to something like that. But then, I just like, yeah, it's a problem with time. Yeah? Is table gen Turing complete yet? Is table gen Turing complete? Not intentionally. I, I don't know, I don't know.
So, so there are some places like, you know, with the, with these function calls and everything where the question actually arises. But, at least last year, whenever I was at a point where I thought, well, maybe it would be nice to have arbitrary recursion or something in there. I went the way of not adding it. So, I don't think it is, but maybe you will prove me wrong.
So, you, you can, there is a very limited scope for self-references in that when you define a record, you can define a variable, which you assign a cast from
the name of the record, and then the record will contain a reference to itself. That is the only way, like, that was, stuff like that was actually being used in existing table gen when I started looking this deep. You shouldn't be teaching us this. I'm almost sure you're telling us this.
Yeah, I mean, go, go ahead. Next, next positive you present table gen is Turing complete, yeah. Yeah, I don't think so, but I would not be super surprised if it were. Other questions?
Yeah? Because I was lazy, and there was, at the time that I first thought about it, there was, like, one place where I wanted to use it. By now, I think there are a few more places where it's used, so maybe, maybe, yeah.
Oh, sorry, the question was, I showed how to abuse for each as an if statement, and the question was whether maybe there should be an actual if statement in the table gen front-end language. Yeah, I think there are recently statements which may be excluded. For textual conditional parsing, yeah, yeah.
I don't think that would, no, that's very far away from the kind of thing that I showed. Okay. Thank you very much. Thank you.