Hi everybody,
for a research project, I need to change every memory access in a program.
Every memory location should be doubled in size. Before each store the value is encoded and after each load the value is decoded to it’s original value.
I’m aware that I cannot do that as an IR Pass because the backend might insert additional stores and loads (calling convention, register spills, prolog/epilog). Therefore, I want to create a backend pass that handles everything for me. I’m targeting RISCV as a backend but if there’s a way to do that generalized for all targets that would be also great.
As a high-level overview I need to achieve three things:
- Double the size of every memory location except for volatile accesses
- On every non-volatile store: Encode the value and transfer it to memory
- On every non-volatile load: Load back the value and decode it back to the original value.
Let me describe the algorithm which I have in mind and my reasoning behind that.
- In IR: Double size of all global variables
a. Insert cast operation for allusers()
to keep the IR valid
b. Question: Can this be also done in the backend easily? - Insert a
MachineFunctionPass
as the last pass inaddPreEmitPass2()
a. Such that register allocation and epilog/prolog insertion already happened
b. My pass “sees” all the memory operations which should be executed
Now my pass starts with the following:
- Double size of each stack frame such that every variable is doubled in size
a. Find them viallvm::MachineInstr::FrameSetup
andllvm::MachineInstr::FrameDestroy
- Change each store/load instruction with their doubled-size-equivalent and do some value encoding/decoding before the load/store is executed
a. Question: How do I get the correct location on the stack? Because framesize has doubled and every variable has now twice the original size.
b. Question: I need to ignore volatile stores and loads, do I get that information viaMachineInstr::hasOrderedMemoryRef()
?
In addition, I have the following questions:
- Would that in general work? Or have I overseen some important facts?
- Is
addPreEmitPass2()
the correct location for inserting my pass? Or should I do that earlier in the pipeline? - Is it possible to implement those things target-independent?
- Because the size of every memory location is doubled, I need to deal with 64-bit values on a 32-bit CPU. To do that efficiently, I need a second register (in addition to the register which is used by the store/load). But because register allocation is already done, how do I get a free register. I’ve heard about the
RegScavenger
which is used in the Prolog/Epilog inserter. Would that be the correct class for my use case?
Thank you very much in advance. I hope it is clear, what I’m trying to achieve. If not, please let me know and I can provide more information.
Best regards,
Maurice