mem2reg is an important optimization pass in llvm. I want to understand how this optimization works but didn't find good articles, books, tutorials and similar.
I found these two links:
https://blog.katastros.com/a?ID=01300-3d6589c1-1993-4fb1-8975-939f10c20503
https://www.zzzconsulting.se/2018/07/16/llvm-exercise.html
Both links explains that one can use Cytron's classical SSA algorithm to implement this pass, but reading the original paper I didn't see how alloca instructions are converted to registers.
As alloca is an instruction specific to llvm IR, I wonder if the algorithm that converts alloca instructions to registers is an ad-hoc algorithm that only works for llvm. Or if there is a general theory framework that I just don't know the name yet that explains how to promote memory variables to registers variables.
On the official documentation, it says:
mem2reg: This file promotes memory references to be register references. It promotes alloca instructions which only have loads and stores as uses. An alloca is transformed by using dominator frontiers to place phi nodes, then traversing the function in depth-first order to rewrite loads and stores as appropriate. This is just the standard SSA construction algorithm to construct “pruned” SSA form.
By this description, it seems that one just need to check if all users of the variable are load and store instructions and if so, it can be promoted to a register.
So if you can link me to articles, books, algorithms and so on I would appreciate.
"Efficiently Computing Static Single Assignment Form and the Control Dependence Graph" by Cytron, Ferrante et al.
The alloca instruction is named after the alloca() function in C, the rarely used counterpart to malloc() that allocates memory on the stack instead of the heap, so that the memory disappears when the function returns without needing to be explicitly freed.
In the paper, figure 2 shows an example of "straight-line code":
V ← 4
← V + 5
V ← 6
← V + 7
That text isn't valid LLVM IR, if we wanted to rewrite the same example in pre-mem2reg LLVM IR, it would look like this:
; V ← 4
store i32 4, i32* %V.addr
; ← V + 5
%tmp1 = load i32* %V.addr
%tmp2 = add i32 %tmp1, 5
store i32 %tmp2, i32* %V.addr
; V ← 6
store i32 6, i32* %V.addr
; ← V + 7
%tmp3 = load i32* %V.addr
%tmp4 = add i32 %tmp3, i32 7
store i32 %tmp4, i32* %V.addr
It's easy enough to see in this example how you could always replace %tmp1 with i32 4 using store-to-load forwarding, but you can't always remove the final store. Knowing nothing about %V.addr means that we must assume it may be used elsewhere.
If you know that %V.addr is an alloca, you can simplify a lot of things. You can see the alloca instruction so you know you can see all the uses, you never have to worry that a store to %ptr may alias with %V.addr. You see the allocation so you know what its alignment is. You know pointer accesses can not fault anywhere in the function, there is no free() equivalent for an alloca that anyone could call. If you see a store that isn't loaded before the end of the function, you can eliminate that store as a dead store since you know the alloca's lifetime ends at the function return. Finally you may delete the alloca itself if you've removed all its uses.
Thus if you start with an alloca whose only users are loads and stores, the problem has little in common with most memory optimization problems: there's no possibility of pointer aliasing nor concern about faulting memory accesses. What you do need to do is place the ϕ functions (phi instructions in LLVM) in the right places given the control flow graph, and that's what the paper describes how to do.
Related
I'm in a process of making a native compiled language using LLVM as backend.
For a couple of special features I need to be able to do two things via LLVM API:
Provide custom relocations into both data and code segments to LLVM
Ability to insert a constant value (specifically arrays, but it doesn't really matter) into code segment, in specific places in between functions and create a relocation of it for LLVM-defined objects (assume functions, but it doesn't matter).
It looks like if I insert non-zero initialized global variables, they are going in the segment in the order of their declaration in LLVM IR module, I would like to to the same in the code segment, but it is read-only at the runtime, so let it be constants as in rdata segment.
For example:
#myConst1 = const [2 x i32 (i32)*] {MyProc1, MyProc2} // how do I put this into code segment before first instruction of MyProc1?
define i32 #MyProc1() !dbg !524 {
ret i32 5
}
#myConst2 = const [16 x i8] zeroinitializer // ideally would like to do this, and create relocations manually into this array for two pointers to both MyProc1 and MyProc2
define i32 #MyProc2(i32 %0) !dbg !524 {
ret i32 %0
}
Is this even possible to do with LLVM and it's API?
If yes, I need help to understand how, as after reading a ton of documentation I'm unable to figure out how.
Thank you.
Can someone please explain me what is wrong with this code?
I think this should fetch the second argument from global array, but in fact it silently crushes somewhere inside JIT compilation routine.
My suppositions:
GEP instruction calculates memory address of the element by applying offset and returns pointer.
load instruction loads value referenced by given pointer (it dereferences a pointer, in other words).
ret instruction exits function and passes given value to caller.
Seems like I've missed something basic, but time point from which i should give up looking for answer myself is gone and i have to seek for help.
#arr = common global [256 x i64], align 8
define i64 #iterArray() {
entry:
%0 = load i64* getelementptr inbounds ([256 x i64]* #arr, i32 1, i32 0)
ret i64 %0
}
You requested the 257th item in a 256-item array, and that's a problem.
The first index given to a gep instruction means how many steps are made through the value operand - and here the value operand is not an array but a pointer to an array. That means every step there skips the entire size of the array forward - and that's why the gep actually asks for the 257th item. Using 0 as the first gep index will probably fix the problem. Then using 1 as the 2nd index will get you the 2nd item in the array, which is what you wanted. Read more about it here: http://llvm.org/docs/GetElementPtr.html#what-is-the-first-index-of-the-gep-instruction
Alternatively, it's more appropriate here to use the extractvalue instruction, which is similar to gep with implicitly uses a 0 for the first index (and there are a couple of other differences).
Regarding why the compiler crashes, I'm not sure - I'm guessing that while normally such a memory access would compile fine (and at runtime either generate a segfault or just return a bad value), here you specifically requested the gep to be inbounds, which means that a bounds check is done - and it will fail here - so a poison value is returned, which means your function is now effectively load undef. I'm not sure what LLVM does with load undef - it should probably be optimized out and the function be made to just return undef - but maybe it did something different which led to a rejection of your code.
I'm holding a Type* in my hand. How do I find out its size (the size objects of this type will occupy in memory) in bits / bytes? I see all kinds of methods allowing me to get "primitive" or "scalar" size, but that won't help me with aggregate types...
If you only need the size because you are inserting it into the IR (e.g., so you can send it to a call to malloc()), you can use the getelementptr instruction to do the dirty work (with a little casting), as described here (with updating for modern LLVM):
Though LLVM does not contain a special purpose sizeof/offsetof instruction, the
getelementptr instruction can be used to evaluate these values. The basic idea
is to use getelementptr from the null pointer to compute the value as desired.
Because getelementptr produces the value as a pointer, the result is casted to
an integer before use.
For example, to get the size of some type, %T, we would use something like
this:
%Size = getelementptr %T* null, i32 1
%SizeI = ptrtoint %T* %Size to i32
This code is effectively pretending that there is an array of T elements,
starting at the null pointer. This gets a pointer to the 2nd T element
(element #1) in the array and treats it as an integer. This computes the
size of one T element.
The good thing about doing this is that it is useful in exactly the cases where you do not care what the value is; where you just need to pass the correct value from the IR to something. That's by far the most common case for my need for sizeof()-alike operations in the IR generation.
The page also goes on to describe how to do an offsetof() equivalent:
To get the offset of some field in a structure, a similar trick is used. For
example, to get the address of the 2nd element (element #1) of { i8, i32* }
(which depends on the target alignment requirement for pointers), something
like this should be used:
%Offset = getelementptr {i8,i32*}* null, i32 0, i32 1
%OffsetI = ptrtoint i32** %Offset to i32
This works the same way as the sizeof trick: we pretend there is an instance of
the type at the null pointer and get the address of the field we are interested
in. This address is the offset of the field.
Note that in both of these cases, the expression will be evaluated to a
constant at code generation time, so there is no runtime overhead to using this
technique.
The IR optimizer also converts the values to constants.
The size depends on the target (for several reasons, alignment being one of them).
In LLVM versions 3.2 and above, you need to use DataLayout, in particular its getTypeAllocSize method. This returns the size in bytes, there's also a bit version named getTypeAllocSizeInBits. A DataLayout instance can be obtained by creating it from the current module: DataLayout* TD = new DataLayout(M).
With LLVM up to version 3.1 (including), use TargetData instead of DataLayout. It exposes the same getTypeAllocSize methods, though.
I am writing an LLVM pass that modifies the intermediate code. I want to check each terminating instruction of a basic block to see if it has a back edge. To make it more clear, in the following example, I want to see if to reach labels land.lhs.true or if.end, a back jump is required.
entry:
%pa = alloca %struct.Vertex, align 4
.........
br i1 %cmp, label %land.lhs.true, label %if.end
Not sure what you mean by back edge or back jump here, as LLVM intermediate code has no explicit layout in memory. You should think of basic blocks within each function has having no explicit order and no explicit assignment to memory addresses. This is handled by the backend when emitting assembly code.
What are the performance benefits or penalties of using goto with a modern C++ compiler?
I am writing a C++ code generator and use of goto will make it easier to write. No one will touch the resulting C++ files so don't get all "goto is bad" on me. As a benefit, they save the use of temporary variables.
I was wondering, from a purely compiler optimization perspective, the result that goto has on the compiler's optimizer? Does it make code faster, slower, or generally no change in performance compared to using temporaries / flags.
The part of a compiler that would be affected works with a flow graph. The syntax you use to create a particular flow graph will normally be irrelevant as long as you're writing strictly portable code--if you create something like a while loop using a goto instead of an actual while statement, it's not going to produce the same flow graph as if you used the syntax for a while loop. Using non-portable code, however, modern compilers allow you to add annotations to loops to predict whether they'll be taken or not. Depending on the compiler, you may or may not be able to duplicate that extra information using a goto (but most that have annotation for loops also have annotation for if statements, so a likely taken or likely not taken on the if controlling a goto would generally have the same effect as a similar annotation on the corresponding loop).
It is possible, however, to produce a flow graph with gotos that couldn't be produced by any normal flow control statements (loops, switch, etc.), such conditionally jumping directly into the middle of a loop, depending on the value in a global. In such a case, you may produce an irreducible flow graph, and when/if you do, that will often limit the ability of the compiler to optimize the code.
In other words, if (for example) you took code that was written with normal for, while, switch, etc., and converted it to use goto in every case, but retained the same structure, almost any reasonably modern compiler could probably produce essentially identical code either way. If, however, you use gotos to produce the mess of spaghetti like some of the FORTRAN I had to look at decades ago, then the compiler probably won't be able to do much with it.
How do you think that loops are represented, at the assembly level ?
Using jump instructions to labels...
Many compilers will actually use jumps even in their Intermediate Representation:
int loop(int* i) {
int result = 0;
while(*i) {
result += *i;
}
return result;
}
int jump(int* i) {
int result = 0;
while (true) {
if (not *i) { goto end; }
result += *i;
}
end:
return result;
}
Yields in LLVM:
define i32 #_Z4loopPi(i32* nocapture %i) nounwind uwtable readonly {
%1 = load i32* %i, align 4, !tbaa !0
%2 = icmp eq i32 %1, 0
br i1 %2, label %3, label %.lr.ph..lr.ph.split_crit_edge
.lr.ph..lr.ph.split_crit_edge: ; preds = %.lr.ph..lr.ph.split_crit_edge, %0
br label %.lr.ph..lr.ph.split_crit_edge
; <label>:3 ; preds = %0
ret i32 0
}
define i32 #_Z4jumpPi(i32* nocapture %i) nounwind uwtable readonly {
%1 = load i32* %i, align 4, !tbaa !0
%2 = icmp eq i32 %1, 0
br i1 %2, label %3, label %.lr.ph..lr.ph.split_crit_edge
.lr.ph..lr.ph.split_crit_edge: ; preds = %.lr.ph..lr.ph.split_crit_edge, %0
br label %.lr.ph..lr.ph.split_crit_edge
; <label>:3 ; preds = %0
ret i32 0
}
Where br is the branch instruction (a conditional jump).
All optimizations are performed on this structure. So, goto is the bread and butter of optimizers.
I was wondering, from a purely compiler optimzation prespective, the result that goto's have on the compiler's optimizer? Does it make code faster, slower, or generally no change in performance compared to using temporaries / flags.
Why do you care? Your primary concern should be getting your code generator to create the correct code. Efficiency is of much less importance than correctness. Your question should be "Will my use of gotos make my generated code more likely or less likely to be correct?"
Look at the code generated by lex/yacc or flex/bison. That code is chock full of gotos. There's a good reason for that. lex and yacc implement finite state machines. Since the machine goes to another state at state transitions, the goto is arguably the most natural tool for such transitions.
There is a simple way to eliminate those gotos in many cases by using a while loop around a switch statement. This is structured code. Per Douglas Jones (Jones D. W., How (not) to code a finite state machine, SIGPLAN Not. 23, 8 (Aug. 1988), 19-22.), this is the worst way to encode a FSM. He argues that a goto-based scheme is better.
He also argues that there is an even better approach, which is convert your FSM to a control flow diagram using graph theory techniques. That's not always easy. It is an NP hard problem. That's why you still see a lot of FSMs, particularly auto-generated FSMs, implemented as either a loop around a switch or with state transitions implemented via gotos.
I agree heartily with David Hammen's answer, but I only have one point to add.
When people are taught about compilers, they are taught about all the wonderful optimizations that compilers can do.
They are not taught that the actual value of this depends on who the user is.
If the code you are writing (or generating) and compiling contains very few function calls and could itself consume a large fraction of some other program's time, then yes, compiler optimization matters.
If the code being generated contains function calls, or if for some other reason the program counter spends a small fraction of its time in the generated code, it's not worth worrying about.
Why? Because even if that code could be so aggressively optimized that it took zero time, it would save no more than that small fraction, and there are probably much bigger performance issues, that the compiler can't fix, that are happy to be evading your attention.