General questions about the implementation of a memory instrumenting backend pass

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:

  1. Double the size of every memory location except for volatile accesses
  2. On every non-volatile store: Encode the value and transfer it to memory
  3. 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.

  1. In IR: Double size of all global variables
    a. Insert cast operation for all users() to keep the IR valid
    b. Question: Can this be also done in the backend easily?
  2. Insert a MachineFunctionPass as the last pass in addPreEmitPass2()
    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:

  1. Double size of each stack frame such that every variable is doubled in size
    a. Find them via llvm::MachineInstr::FrameSetup and llvm::MachineInstr::FrameDestroy
  2. 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 via MachineInstr::hasOrderedMemoryRef()?

In addition, I have the following questions:

  1. Would that in general work? Or have I overseen some important facts?
  2. Is addPreEmitPass2() the correct location for inserting my pass? Or should I do that earlier in the pipeline?
  3. Is it possible to implement those things target-independent?
  4. 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