Thank you, so currently I'm working on a new approach to call the safe registers in LVM.
I find it pretty interesting, so I'm going to tell you what it is.
So this work is still ongoing.
There is like a perfect concept branch which people can try and see how it works.
So now I'm in the process of breaking up into smaller commits and upstream in them.
By one, let's first quickly recall what call the safe registers are.
So there must be preserved by function, meaning that the value of the register and entry has to be the same as the value on the exit.
So for the caller it means that call instruction does not modify any call the safe register.
So caller can use it for the things and for the call it means that if a call is once to use call the safe register.
But I'll make sure it does not swallow.
Here's a by speeding, sometimes it can copy register somewhere else.
And we should register the call the safe to use the specific file by calling the condition.
So let's go through a minimal example but it's still interesting.
So we have this tiny function which calls who.
And this is there simply that we get for risk five.
At least this was what I got a couple of years ago.
Right, so.
We have a return address register which is modified by call to fill.
And since return address is call the safe.
You have to, the caller has to preserve it.
So first it stores return address to the stack because call full could overwrite it otherwise.
Then at the exit we restore it.
Okay, then the arguments are passed in a zero.
So x would be passed to the caller in a zero and value of x is used in x plus two.
The expression.
So since call to full real.
Call for a zero.
We first move it.
Yeah, we move a zero to s zero because this is called set.
And then we use it here.
So we have used s zero.
So it means we have to store it and then later load it back.
Well, this is five instructions.
But we can actually do this for.
So this is there simply for my patch.
Same version of all of the m just my patch enabled.
So here we store directly a zero.
And then before we use it we load it back.
So we don't need the move.
So what is this example?
Step through a VM pipeline and see if you can do something about this copy.
Right after front end is we have the full LVM IR, which is pretty dumb.
We have this first.
Okay, it's on space.
Then we store to stack the almost immediately reload.
So the point of front end is simply to lower like high level.
When which to LVM IR.
Then a lot of need and passes happen in the optimizations.
So these are mostly activity bundled.
And they get rid of these unnecessary instructions.
And also communicate with IR.
Even though we can't really see much.
In this example, accept the category of this necessary instructions.
Okay, then next part is really interesting.
The scope is traction selection.
So after all the machine independent instructions.
This stage for lower VM IR to machine IR.
Which is kind of target specific instructions.
So the code is still in the same form.
And it uses infinitely many virtual registers.
Except for calling convention.
So in such a selection, also takes care of parts of calling convention.
So it declares all the incoming arguments as live-ins.
Then copies them in the second instruction to virtual register.
Then at the exit it copies of the return values to whatever they supposed to be.
By the calling convention.
And if full had any arguments, we would pass the arguments to full also at this stage.
All right, so then manual machine passes happen.
And we arrive at register allocator.
Register allocator will assign virtual registers to physical ones.
And while doing so, it tries to choose assignments which makes copies identity.
So it tries to get rid of the copies.
So for example, this copy near the end.
Register allocator assign virtual register to external.
So they don't need the scope anymore.
But we cannot get rid of the first copy.
Because external, it's sorry, because the live-range of zero, it includes the call instruction,
which provides external.
So we have interference and we choose X8.
For virtual register zero.
Next, after we allocate the registers, we know the registers are used by the function.
So the call lock and insertter needs to save all the coil-safe registers.
So in this case, we have to store X1, which is the retail register and X0, X8.
And then reload them back.
And so, after that, we need to assembly.
And that is this copy instruction, which we don't really want.
So original motivation for this work was shrink ripen.
In this example, we only need to save some registers under store if you take the branch.
Because if you don't, then nothing clobberes anything.
And we don't need to, we don't need any catalog of catalogs.
But, LVM still needs to follow the catalog at the function entry.
So it is executed even though we might not take the branch.
So with my patch, this is what we would like to happen is that the catalog code moved inside the branch.
So how can we do that?
Okay, I have read some, well, when I saw this, I said it again.
Maybe there is some papers on shrink ripen.
And there are, from nineties.
And as we did it, it seems like what a fork and which you complicated.
And it also seems like, it also seems like what the basically trying to do is to choose a good place to save register and the store.
Each coin is saved register.
Well, how does this different form just choosing a good spill for any other register?
So basically what I'm saying is register allocator is supposed to be good at choosing a good place to insert store to stack under store.
And this problem is not really different from any other spill.
So can we make register allocator, do all this work for us?
And so I tried this kind of hack.
Then we write up the instruction selection, we can, we declare every call you saved register as live will.
Then we copy each physical call you saved register into virtual one.
And then at each return instruction, we copy from the corresponding virtual register back to physical register.
And make the return instruction use this physical register implicitly.
So basically this hack says that the value of the register on entry has to be the same as on exit.
So somehow this worked.
And actually this approach is supposed to simplify register allocator in because right now it's a register allocator.
So there is some logic which tells that if you want to use call you saved register for the first time, there is some cost.
Then you would have to save it and restore.
So with this approach we don't really need to save this anymore.
So we can get rid of that, even though I haven't tried yet.
So then I first implemented this tree.
I saw the gradations like a level benchmark.
And then I start investigating why this happens.
And basically it's because now we so effect the size previous passes in new ways.
I now we have recreated many of the old life ranges.
And so things like machine written volume code motion and register allocator.
And some other passes they just they needed some improvements.
So I did couple tweaks and I got good results.
So this is this result for part of integer spec.
And this is just the any construction count.
And I did this measurement on trail work load.
So we got almost you have eight point five percent on jc, which is good.
So I also measured what improvements on other benchmarks like FP benchmarks, but no one has verified
them yet so I kind of hesitant to post this here.
But then I discovered that C5 is not so easy now.
Default this work I didn't even know what C5 is.
So I understand the C5's they.
So there is a requirement to a program that at any point we should be able to restore values of all the qualified registers.
And C5 instructions.
Well they're not really instructions, they are more like assembler directives.
Which in code basically where qualified registers are at any important point.
So before we had a dedicated pass to meet and pull a couple log.
So when we come to this pass you know okay I'm saving the score you saved register.
I will need C5 accordingly.
But what we did what we did here is said to basically back and optimize the result.
Do whatever you want but preserve this register somehow.
And now we don't really know where the score happens this day.
So you have to now instead calculate where C5 should be.
We can do so.
So this is example with one register hope to make clear.
So we look how can we track what happens to X-Tem.
So we know that on return the value is X-Tem.
And then we just reach the initial analysis to see where each definition is rich.
That X-Tem in this case is just this one load instruction.
Then we say okay which definitions, which instructions right to this stack slot zero.
Well it's these two instructions.
And then we continue tracking backwards until we come through like the entry or function.
And by doing this backtracking a pitch port of the program we basically know where call set register.
So we can meet C5 accordingly.
Yeah so as I said this is still like far from completed.
It's still on progress.
And many people help his reviews and suggestions and PRs.
And thank you for listening.
We have questions.
Yes?
So I understand you have changed this to how C5 registers are handled.
To simplify some parts of the code because you don't have the special trigger of C5 registers.
And it can produce better code if you adjust some of our team.
Thank you very much.
Our X-Tem.
So this is our net wins.
But I expect there could be some drawbacks.
For example, compilation type.
And perhaps find a resize.
If you include the C5, that can be more complicated.
We have never mentioned on the drawbacks as well.
So can you repeat the last part of the question?
No.
If you repeat it.
The microphone.
Ah.
I'm supposed to repeat.
Yes.
The question is if there are some impacts on binary size and compilation time.
Yes.
The C5 sections increase by non-factor of C.
Which maybe could be reduced.
I don't know.
So far I just focus on like you need to know correctly.
And the compilation time is yes.
For example, recently I looked.
I had to modify rich and definition analysis as well.
For example, because it could be a textlet before.
And like after my modification.
When the usage increased by 60%.
But I'm working on it.
Very still work to do and we don't know how much impact.
Yeah.
We're not having the end.
Yeah.
And what is the C5 information used for, is it for exception?
Yes.
Exceptional handling.
Then the question was, what is C5 information is used for?
It's used for exception handling.
It's used by like operating system.
Unwind.
Backtrays.
My other things.
Like I only know about C5.
And then you can do this.
So that's all I know.
For spectrays also in debugger.
Yeah.
The debugger?
Yeah.
But the one is debugger is not a complication.
Because the debugger has like it needs to know what
co-locate lock ends.
Because when you single step.
So that's simply.
You basically need to know.
If you inside pro locate lock or not.
So now pro locate lock is like in many different places.
So that has to be fixed somehow.
Yeah.
Is this specific to any one back end?
Yeah.
So far I already tried to turn the risk five.
But I think it's principle could work on other targets too.
But you have the back end all fast to do that work.
And well, hopefully we will like this part.
Or try to make a target independent.
It's just for now to simplify things to just work with one target.
Yeah.
Yes?
Do you know how to compile it?
I don't know.
Other compilers in NGCC.
Oh, question is if I have a compilers in your service similar.
Not the time of all.
All right.
Did there are no questions?
Thank you very much.
Thank you.
No.
Thank you.