2019 EuroLLVM Developers’ Meeting: V. Bridgers & F. Piovezan “LLVM IR Tutorial - Phis, GEPs ...”

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello welcome to our talk today thanks for coming my name is Vince I'm from Austin Texas and I work on FPGA compilers based on LOV M hi I'm Filipe I also work on RF j compiler and I'm from Toronto Canada this is a tutorial that we put together about the LVM intermediate representation or simply known as the IR will I'll be given the first part of the talk in Felipe will do the rest so how many experienced developers are there in the room today okay how many beginners do we have in LVM and maybe IR okay so I think some of you will see some benefit from this if if you're experienced developer you can maybe take this back to some of your new hires or you know people that may need a refresher and IR so why have a basic tutorial about IR and a conference for all of the M developers well this has been requested by Tim knees in the past and many developers come from a mix of backgrounds and skills and so some developers also kind of get you know pigeonholed into doing things like infrastructure and builds and things like that and maybe they want to you know push the horizons of their skills and and learn more than just these things so the we thought a tutorial like this would be useful so if if we were to put together a tutorial about how to learn the IR this is how we would do it if you're familiar with these concepts please bear with us we do consider this a community project so we very much want to hear feedback from you on how to improve this tutorial we assume no previous iron knowledge but this is not a lecture about compiler theory either so we will cover some some compiler theory just to introduce this to you but we will not go into any great depth here after this tutorial you should be able to understand the common LVM tools you should be able to write and comprehend simple er/ir and be able to understand that and use the language reference to inspect compiler generated IR so what is the IR IR is a low-level programming language it's a risk light instruction set something like an abstract some language if you look at ir you'll see things like loads and stores and arithmetic sire must be able to represent high-level language ideas so it must be able to represent many things that source languages can do and it also enables efficient code optimization so how was I represented one way is in bit code another way is in human readable form that we call LL files bit code files can be converted to LL files using the tool called elleven dis and ll files can be converted to bit code files using a tool called lov ms there is another form that's called in-memory representation that we will not cover today but you just in case you want to go read about this you can the focus of the presentation will be on the ello form will show you some examples and we'll walk through these with you so how does ir fit into the compile process so suppose you start with the main dot cpp we compile this file using claim that creates an ir file this can be optimized and then suppose we have a factorial dot c which you will see in the presentation we use this as a teaching example factorial deci can be compiled into an IFR file using playing it can be optimized these optimized ir files can be linked and then they can be lowered to target machine code so you can see here that the ir is the centerpiece of yellow of ELO vm development kit and so it's important to understand ir to some degree in order to understand how how eyars use throughout the the tool flow so now that we've seen where the IRS used let's take a look at its internal elements so we'll be showing these diagrams throughout the presentation so that you can maintain some context for some of the low-level details that we're talking about so let's start with the module a module has target information it contains global symbols 0 or more global variables 0 more function declarations and zero or more function definitions that's what the star is in case you don't know what that is and it contains some other stuff that we're not going to go into detail about here today but you can look at look at that later so what's inside of a function a function contains zero or more arguments an entry basic block and zero or more basic blocks what's inside of a basic block a basic block contains a label zero or more fee instructions and zero or more instructions followed by a terminator instruction so let's look at some sample target information just to see what's inside of a module and iron module always starts or usually starts with a pair of strings describing the target so in this particular example we have Indianness information we have mangling information we have a bi alignment information and we also have information about the native integer widths we also have information about it we also have a target triple which describes the architecture that the module is to be targeted to compiled to the vendor system and the API so let's look at a basic main program and see how this is represented in the IR just as a starting point and you'll see that we'll use these examples as we continue and we'll build upon these as the presentation progresses so this is a main function that calls a factorial function and you can see that the main function takes an int arc C takes a char double star arc V it returns the result of calling factorial with an argument of two times seven checking for equivalence 242 so how does this look in IR first there's a function declaration for factorial you can see that there is a declare statement here which declares that this is a function declaration there are 32-bit integer definitions that are that we call I 32 in the IR and there's the function name factorial then we have then we have the main declaration the the main definition so this is there is a defined statement that says this is a main function definition there is an i-32 arc arc see an eye double star arc V the next IR statement is a call to factorial takes an argument of of two of type I 32 and factorial the result of the factorial is returned to a variable that we call percent one the next statement is a multiply that you've seen in the C code this this statement multiplies the variable percent one times seven it returns that two percent two and then there's an integer comparison of that result to i 42 we're turning that result two percent three the next statement is a zero extension of the result from I compared to I 32 which is then returned from the main function and you can see in this example this is a sequentially executing series of IR instructions this will become important later as we talk about other concepts so what were their percent variables that we saw in the previous example these are called virtual registers and these are these are also known as local variables there are two flavors of these names there are unnamed virtual registers in this case there's percent one percent two percent three and these are sequential usually sequentially increasing in the IR then there are named virtual registers in this case there's a percent result I the LOV Mir has an infinite number of registers that leaves register allocation as a problem for the backend so as we mentioned before IR is a very strongly typed language you'll see many types throughout the generated I are having types in the our makes it easier to follow and reason about so what does this example look like if we were to remove all the code except for the types this is interesting so you'll see types on pretty much every line and some types you'll see some lines you'll see two different types note that the instructions explicitly dictate the types expected this makes it easier to figure out the argument types and easy to figure out the return types for the most part and we'll talk about that so you can see that the call to i-32 returns an i-32 and accepts a 932 the usage of 0 extension accepts an i1 and extends that to an i-32 the multiply the I compare are not so obvious but we'll talk about this the ir has no implicit conversions so what would happen in this case if we were to remove this instruction and try to return % 3 anybody's home yep exactly so you'll see an error like this it says % 3 define what type I want but expected I 32 so why is that it's because main is defined to return in I 32 so there is a way to check this if you wish to do so you can use the OP to with a verify flag and this will tell you that if these issues exist or not and this is useful for writing your own IR or debugging compiler generated IR so let's talk about the language reference so we'll come back to some of the other things that we mentioned we talked about a handful of instructions but if not showed you where to find the information yet that you might need so for example the I compare in the the multiply instruction instructions often had many variants so just looking at the call instruction you can see in this particular case why what else would a call instruction possibly need so you can see the call instruction language reference here it defines the call instruction there's a syntax definition here and we'll add our instruction here just for reference so you can correlate the instruction we had in our example with the information that's in the syntax section percent one corresponds to the result there is a call instruction here that corresponds to required text and the i30 to add factorial and the list of arguments is also required some more information I'll review arguments there's information about semantics and here are plenty of examples in this particular case for you to look at at this point I'll turn over the rest of the presentation to Felipe thank you all right now that I have a basic information about the IR we are ready to look at some more complicated code and where some of the the most peculiar features of the LLVM IR reside for that let's try to look at how we can implement our factorial function first in a recursive way so we have a simple piece of C code here where we test if our argument is 0 if it is 0 then we just return a constant 1 otherwise you're gonna recursively call our factorial function or return that times our input argument to write this in the IR we can start by using our comments in text which is just semicolon and then we're gonna define this factorial function saying that he has an i-32 as an argument and it also returns this same type of integer and the first thing we're gonna do here is that task for a if statement so we're essentially comparing our value to 0 and based on the result of this comparison we want to do two different things right we want our code to jump to we want to jump to two different points in our code and here we're using the concept of labels to identify a part of our code we have what we call our base case which essentially has a single instruction returning one and in our recursive case we're gonna a subtract one from our input called the factorial function multiply that value and eventually return so now we are seeing some more structure in our functions in particular you see that we have some names some labels a sequence of instructions that always terminate in a specific way and this leads us to one of the the core compiler concepts which is that of basic blocks the idea of a basic block is to essentially to model a sequence of in interruptible instructions in l VAR in particular we're gonna say that basic block is a list of a null terminator instructions ending with the Terminator and what is this terminator it's essentially an instruction that will take our control flow our executing point of the code to some other point we've seen two example so far branches and return statements but there are others we have switches there are some loser and unreachable statement and a bunch of exception handling instructions but we're not going to focus on those on these in this presentation so going back to our example we can see that we have our first basic block that just has like a comparison and it terminates with a branch instruction we have a base case which simply contains the Terminator instruction and our recursive case also terminates with a return statement so when we have a return statement this is essentially transferring the execution of the program to the calling function whereas for branch statements we transfer the control flow to another basic block and we have a special name for this basic block which is the successor of the currently executing basic block this successor predecessor relationship also has a special name and it's also classic compiler concept which we call the control Graff which essentially it's a graph where vertices are basic blocks and edges capture the predecessor success or relationship between them in this example we have here the label is not shown for the first basic block that is a mistake but let's call it entry so here our control flow graph is saying that our entry has two successors our base case basic block and the recursive case if this had been compiler generated IR we would have seen that the compiler was kind enough to generate comments saying which basic blocks what are the predecessors of a given basic block we can have this those graphs plotted for us by the optimizer itself if you call it with those flags and if you want the instructions included in there as well you can also get there this might get a little bit cluttered but it's useful for code inspection now one important feature of basic blocks is that they always have a label even if it's not explicit in fact that's related to the mistake we had in the slides so let's let's name that first basic block and let's suppose that removed that first label right here is this I are valid or not based on what I said before it should be valid because they always have a name if it's not explicit right but there's a problem here so we're gonna get this error message saying I expected this instruction to be numbered percent one this is just a technical detail of how the unnamed variables work where we expect them to start with percent zero and count up but because we didn't give a name to that basic block label the compiler treated treats it as percent zero so now we're using percent zero twice simple fix for that just rename our variables and this is also true for function arguments you don't necessarily have to give them a name but if you don't there will be an implicit name there at this point we've talked about a lot of the concepts we had in module diagram we're still missing global variables and fin structions to talk about those we're gonna we're gonna have to we need we're gonna need a more complicated example so we're going to implement our factorial function again but this time in an iterative manner so how do we go about doing that this code simply iterates from I equals to two to our input value multiplying and accumulating and eventually we return this temporary Val variable so we're going to start by initializing two variables here two two and one this addition to zero is not really necessary we're gonna get rid of them later but for now bear with me and once we've done this initialization we want to branch to a piece of code that will test whether we want to execute our loop or not so how do we do that we're gonna have a comparison instruction asking is I less than or equal to value and if so we want to go to the body of the for loop if it's not we want to go to the end of this loop so in the for body loop and the far body itself the last thing we're going to do is just return to this to check why do we need to keep iterating or not and once you're done with the loop we just want to be able to return to stand priority now we really want to be able to do this just update our temporary in our I variables right so we would like to do that but the compiler will say no you can't do it so why here it's complaining about multiple definition of a local variable name 10 this is a common thing you're doing a high-level language but you can't really do it in IR why well the IR enforces a property called static single assignment and this is again one of those really important compiler concepts and to keep it simple we're just gonna say that every variable must be assigned exactly once and every variable must be defined before they are used so if we go back to what we had before okay we can't use the same name twice so let's give it a different name but now this code is not very useful right because we're never updating our temporary in our I we're always returning one and our comparison is always comparing against two to fix this while still enforcing the static single assignment property are just SSA we're gonna need a more elaborate mechanism and a very related concept is that of a fee or five depending on how you want to pronounce it this this is an instruction in the IR that will select a value based on where a control flow is coming from so here we the syntax has a type which is just a type that this instruction is going to return and we're gonna say that we want we wanted to take the value 0 if you're coming from a basic blog called label 0 or similarly if our control flow is coming from a basic block labeled label 1 we wanted to take value 1 ok so how do we use this to fix our problem before here's the same IR but now I plotted in a control flow graph way just to make it easier to visualize how the fee instructions are working so the first thing we're going to do in our check for condition basic block is to select the appropriate value of I that is if we're coming from the entry basic block we want to take our constant one whereas if we're coming from the end of the basic block we want it to be I plus 1 right and once we have this we have to update all other uses of the old wah I to be this new I so if you look at the far body we're just going to replace I with current I and also our I in the entry block is now useful it's not being used anywhere so we can just remove it and we're going to do it the exact same thing to our temporary it should be 1 if we're coming from the entry block and if you're coming from the end of the loop we just wanted to take the form of the new temporary so we've been able to solve the SSA but we've been able to enforce the SSA property while implementing our for loop but if we were to look at what the compiler of the front-end would have generated it would have been something different it would have gotten around this restriction by reading and writing from memory now because every presentation holidays needs a garbled link let's quickly look at what compiler Explorer can give us so you can actually use a compiler Explorer to generate IR and here I have the exact same code in as in our example I'm giving it clang the emitter LVM flag so I'm asking you to to generate IR instead of machine code sorry this is a one I actually want o 0 and here 0 and I'm removing debug information so if you look at this IR it looks nothing like we had before it has these alipin instructions it has some stores and some loads but if we were to go to our slightly optimized version with a 1 here we have a much more familiar piece of IR so what is the front end doing here what is being generated well we're using this alyc instruction which essentially is a form of memory allocation in a way where you give it a type and it will return you a pointer to that type so you say a loci 32 you get a pointer to an i-32 you say i'll ok any type you got a pointer of that type this is essentially allocating memory in the stack frame of the currently executing function and it will be automatically released once your return so that's one of the reasons you would never return a pointer returned by an auto construction and as we saw this really plays a big part in generating IR in this a safe form it makes it a lot easier for the front-end to do so so how do we use those Allah constructions to enforce the SSA property again the same code we had previously in the entry block we're now going to allocate space for our I and we're gonna store the initial value in that memory region so we're gonna start to to the address returned by the aleca at the end of the four body we're going to store to the same memory location the updated value of I in again in the entry block we're going to do the same thing to our temporary so we allocate spaced for it for that we store the initial value and again at the end of the far body we're going to store the updated value of this temporary to that memory location at this point we're pretty much we're ready to go to our check for a condition basic block and load the correct value from our memory locations right so here we are where we had a fee instruction now we simply have loads and stores a lockers are one way to get memory from in dir but it's not the only way to do so another way to use memory is with global variables so we're an a locker gives you memory in the function scope a global variable represents a memory region that is visible in every basic block of your module so just like a lockers there are always pointers and to get a global variable the syntax is quite simple it's a name that starts with the add character and we've seen another in other names with the same prefix so we saw that function names also had this and this symbol is essentially used to to define names that are global symbols so global global variables and functions continuing with this example we're going to say that this variable this global variable has a type it must be initialized unless we're simply declaring it and we use the global keyword unless this is a special kind of global variable we should call a constant it essentially says which essentially means that this thing is never stored to do now just as an aside a lot of people get confused about this this is not the same as the C++ Const keyword so because of global variables are pointers whenever you want to use those values you would load or store from memory and in particular because we're doing a memory allocation in a static way here the compiler is able to reason about the address of a global variable at compile time so we're going to say that those are constant pointers unfortunately we don't have time to go into detail about constants but keep that in mind as you look at the IR now an interesting fact about global variables is that they are quite complicated if you look at the language reference page for them they have quite a lot of possible qualifiers can anyone in this room tell me what each and all of these do yeah I can't either so I always check the language reference if you you don't know what one who does do cool we've pretty much covered all of the the module structure as a whole but this presentation would not be complete if we didn't talk about possibly one of the most confusing instructions of the LLVM intermediate representation which are infamous gap element pointers are gap instructions to do so we're going to take a brief detour to the type system and then come back to this to those instructions from the language reference if you look at what they say about type systems it's going to tell you that we have void types we have function types and we have our first class types those are essentially types that can be returned by an instruction among those who have types that represent a single value those are your integers or floats your pointers and we have types representing aggregates arrays and structs there are some other types here like labels labels are actually they actually have a type metadata but we're not going to focus on those in this presentation so let's start with a simple aggregate type which is that of an array and build from there an array is simply defined Valentin's a name in this case we define a global variable but an array essentially has a size that is the number of elements in the array and a type because here we have a global variable we also need an initializer we're using this special keyword 0 initializer to say please initialize this memory region with all zeros once you have a race you can't do much with analyze you can index specific elements of those arrays and this is where the infamous get element pointer instruction comes into play they are supposed to make our lives easier by giving a way to calculate pointer offsets but we'll abstract in a way some details you want to you don't want to think about life is there padding inside a struct or what is the size of those types so the goal here is we want to reason about indices inside an aggregate type rather than about specific byte offsets in that type so it should be intuitive to use once you understand a few basic principles and that's what we're gonna cover the syntax for the gap element pointer instruction or just GAAP is you need to give it a base type for the first index of this gap you need to give it a base pointer where we're gonna start our calculations from and you need a sequence of offsets and this is essentially 1 for aggregate type it sounds a little bit confusing but let's look at some concrete examples suppose you have a memory region with those values and let's suppose you have a pointer pointing to that initial value this is not a physical view of the memory just a logical one if we write a gap instruction like this saying my base type is an i-32 my base pointer is this pointer and my first index is 0 I am saying to this instruction please return me a pointer that is offset 0 elements of that based type of instruction in this case this is not a very useful gap because it's just giving me the exact same pointer that we started with right but once we change this index now we are starting to move to different points of our memory so so far it seems simple right we start with the base pointer we change an index and we are offsetting by a given type in our in memory it seems simple but I bet most of us got really confused by the first index when we were learning about gaps and we'll see why two slides from now so one of the fundamental things to understand is what this first index is doing it doesn't change the return the returning the resulting pointer type and it always offsets base on that base type that is encoded that is written in the instruction why is this confusing well let's look at erase suppose we have this array so just six integers initialize to zero and maybe our memory looks like this we don't really care about the other values we just have a bunch of zeros at the start of this memory chunk and our pointer probably looks something like this if we write a gap just like we had before with our base type as an array and using our global variable as a starting point ER using zero as the first index we're gonna get a pointer to the same memory location but now comes a really important question what is the type of this pointer if you're thinking of an integer that's why most people get confusing it's not a pointer to an i-32 it's a pointer to an array of the same type as our original pointer to see here if you were following let's change that 0 to a 1 and think about it where is this new pointer going to point to yes it's not to the first element of the array but in fact probably to some out-of-bounds region why because the first pointer is always offsetting by that base type of having the instruction so this first index it is upsetting an entire array at a time and I think most of us got confused by this when we were learning about Elvia in my arm so how do you get a pointer to a specific element here of the array well we have to add more indices to this gap so while the first index is always offsetting based on that base type all other indices are essentially stripping away a layer of aggregate type and getting a pointer inside this aggregate let's let's use our previous example to understand this so this is the situation we had now we're going to add an extra zero to this gap and this extra zero is saying get the zeroth element of the aggregate we're looking at of the array we're looking at so this will give us the desired pointer with the type that we expected that is a pointer to an integer to the first element of the array and now we can just change that 0 to a 1 to a 2 and we're iterating over each element of the array gaps as I mentioned gaps are used to they can be used to look at aggregate types in general and but arrays are not the only type of aggregate we have right so let's look at structs and see how gaps related relate to them to define a struct in the ll of mir you simply need a name and here we're going to use percent the percent a symbol even though it can be put into the global scope it needs the type keyword and it needs a list of types so here we're defining a struct that has a character an integer and an array of three integers let's use a gap to look inside this struct so same we have the same struct as before we're going to initialize a global variable with some random initial values and maybe our memory looks like this so we have a pointer to the beginning of this memory chunk if we write a get element pointer instruction with 0 as the first index as you saw before we're gonna get a pointer of this of the same type into the same location as our global variable and hopefully by now you're convinced study for change that 0 to a 1 we're probably pointing to something out of bounds now this is a legal instruction but it's probably not what what you meant to write so let's go back to 0 as the first index and add an extra index there with this we get a pointer to the first element of our struct if we change that 0 to a 1 we're getting a pointer to the second element of the struct etc so when we ask for the last element of this drugged this is again another aggregate type right this is an array and because this is another aggregate we can once again add another index to our gap so we add a 0 and now we're asking for a pointer to the first element of the array we can change this to 0 as the 0 to 1 etc now one thing to observe here is that when we are offsetting inside a struct each member of the struct possibly has a different type so this index here must be a constant this is one of the restrictions of the instruction that is if you're upsetting inside a struct because each element could have a different size those indices must be constants otherwise we wouldn't know how much to offset by and that's one of the fundamental thing points about our gaps struct indices must be constants I have one final point to make about gaps and this is where things need to get be a little bit complicated but if you understand this you can say that you understand gaps so to accomplish this we're going to we're going to start with two global variables we're going to start to have a struct that has two pointers inside it and we're going to initialize one variable of this struct type with two pointers that is two pointers to each of our global variables so visually we might have something like this our two global variables might fill the first row of this memory chunk in our struct has our two pointers in the last row our goal with this exercise will be let's get a pointer to an i8 that is to the second element of our second global variable in other words I want two pointer to that five green but we're going to impose in you an extra restriction we want to start with our struct global variable how do we do this well let's start with a gap that is indexing that is getting a pointer to the second element of our struct so this pointer is probably pointing there should do this second element of the last row now what is the type of this pointer so we have a pointer to the second element of our struct and our second element of the struct is a pointer to an array so this gap is returning a pointer to a pointer to an array and why am i emphasizing this point because I want to be able to ask this question can we add an extra zero to this gap so I mentioned that extra indices of the gap always index always look inside an aggregate type but here we don't have a pointer to an array right we have a pointer to a pointer to an array and that's not looking at an aggregate it's just looking at a pointer so every index that is not the first is always trying to strip away one layer of aggregate which is not what we had in that example and there's an important consequence here that is gaps would never load to memory to accomplish what we were trying to do in the previous exercise we need to load that address that is stored in memory before we can continue with our exercise what do I mean by that well let's go back to what we had before let's load the value that is stored in that memory location and you can see here that the result of the load is a pointer to an array and once we have a pointer to an array well we have just a pointer to our second global variable which is something we've seen how to use before right we're just going to write a simple gap with two indices where the first is 0 the second is is 1 and that gives us a pointer to our desired 5 so this is pretty much as confusing as gaps get if you stare at this slide long enough and convince yourself that you've understood it you're pretty much like good to go with gaps some final remarks the elohim ir is constantly changing but we hope that we've what we've presented today are fundamental things that are unlikely to change any type time soon possibly with the exception of pointers which might be losing their types but that shouldn't change any of the things we talked about today so where can you go from here we didn't really talk about any of those topics what are constants how do you model exceptions debug information metadata so there are a lot more topics to explore one of the themes of that our pre-conference on Sunday and up some roundtables yesterday was how can we improve our tutorials and documentation to make LLVM are accessible to beginners we hope to have contributed somehow to this with this presentation and you should expect a lot more improvements in the near future in the meantime you can look at how the optimizer or any other part of the compiler that you like manipulates I are using the owl of iam api's that's pretty much what we had so thank you if we want to some global optimizations or to some global analysis based on the IR files and it may be cross many ifs but I think for the crown or the opt tours just compile the file by the module by the function or by the installing it cannot get to the global informations for multi our files so is there any tours do you have any suggestion so I think what you're alluding to here is the idea of link time optimizations where you have multiple modules merged into a single module and front optimizations on top of that where then you would have information about global variables in different modules so I think what you're looking at is link time optimization and we definitely have that in all of young but link optimization based on every link was a communica but we just wanted to analysis based on our files oh please switch to the I think a real mink oh yes it can combine I have father to two to one yes gainer yeah if you merge all of your modules into a single unit using the linker you can probably also achieve what you're trying to do here because files can contain different antibody infos like the file names or the lining force if we combines to 1000 it was already need more files in the debug info it shouldn't as far as I know it shouldn't place someone correct me if I'm wrong but you shouldn't lose any debug information by just linking modules together okay thank you yes thank you for the nice talk I wanted to get back on the discussion about fee notes the use of a low caste the use of global variables etcetera in the general case do you think is it better to stick to fee notes to use fee notes and let the backend do the I mean it is the backend that better knows what kind of what is the better what is the best way to what is the BET code to generate the register allocation etcetera or do you think about special cases well it would be better to use global variables to avoid feed fee nodes I mean in the general case wouldn't it be better to use fee notes yeah so I showed it the other way around here but what usually happens is the front end will generate a locus for you and some optimizer passes like mentor AG will converge those a locusts into registers potentially creating fee nodes along the way now what about what what's better it really depends on your target right but as a general as a general principle we start with a locus convert everything we can to registers and then optimize from there yeah like we because we here we have infinite registers right but once we get to the back end then you have information about how many registers you have what is the pressure like and do I need to put something on the stack or not that's that hope those will be decisions from the back end questions so if you're trying to make sense of what LLVM I are actually means what optimize a level would you recommend for humans I mean I guess that depends on how complex your code is but it can get messy right if we change this to O 3 I think you'll get something you probably don't recognize at all assuming have insurance yeah so like it's it's completely different right it's probably some vectorization going on here and so I usually start with no organization and then go from there no problem a nice dog is that that final should be at the start of a block is this really enforced or is it just good practice no that's that's a that's part of the definition of the basic block every instruction has to the first instruction of a basic block must be a fee note if it exists questioning your experience at least in our experience the finals order of finals at some times change the behavior of optimization for later coaching but this sometimes is not a deterministic what we experienced for instance sometimes some unrelated a change that will have the order of finals at the beginning of the basic block changed such that yeah we will create a different result yeah not simulation error but the the later result will be quite different to have such kind of experiences so what is it what exactly is different the final Aiyar that gets generated okay yeah the funneled older oh the order of the few notes yeah no that's a good question I honestly you know I've never seen that it has this kind of thing spurious yeah okay sorry it looks like the definition of a structure is logical it's not linked to the layer to the final layout of the structure so I guess that this kind of information must be added in a later pass and then my question is do you have some other some interest instructions that works in for example getting the second byte of a structure whatever is the structure behavior or not safe and clear but can you bypass the type system for example I mean so one of the instructions we didn't cover here is how you cast pointers to different types so in a way you could always cast your pointer from whatever type it is to an eye a pointer and just offset from there you can also convert for pointers to integers and back to pointers so you can do all sorts of those tricks but information about the delay data layout itself is in the data layout string it depends on the target right one thing you can express is that you might want your struct to be packed so you can just add angle brackets to the struct definition and that would give you a packet struct for whatever definition of fact you have any more questions Vince Felipe thank you very much for you [Applause]
Info
Channel: LLVM
Views: 12,256
Rating: 4.9487181 out of 5
Keywords: LLVM, EuroLLVM, LLVM Developers' Meeting
Id: m8G_S5LwlTo
Channel Id: undefined
Length: 50min 45sec (3045 seconds)
Published: Sun Apr 28 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.