Are there ABIs which restrict the caller from modifying an argument passed by reference?

I’m researching how different ABIs work in relation to implementing tail calls in the Rust compiler. One thing that I’m currently interested in is if there are ABIs which prohibit the callee from modifying the arguments passed to it by-ref/by-stack.

I tried reading through various ABI specifications, but found them incredibly confusing and hard to find the information I’m interested in…

I did some experimentation using the following code:

typedef struct {
    uint64_t a;
    uint64_t b;
    uint64_t c;
    uint64_t d;
} Big;

void f(Big x);

void g(Big x) {
    f(x);
    f(x);
}

void h(Big x) {
    x.a = 1;
    f(x);
}

And for all ABIs I tried:

  • g has to make a copy of x since the first call to f might modify the argument passed to it
  • h doesn’t have to make a copy of x since it’s allowed to modify the argument it receives

But is this true for all ABIs (supported by llvm)?

“by-ref” is the a particularly confusing word choice here. There are 2 related IR attributes, byval and byref. byval has an implicit copy performed in the caller, and the pointer visible to the callee is theoretically writable (I’m not aware of anywhere taking advantage of that). There is also the byref attribute, which does not have the implied copy and requires the callee to explicitly copy the value. This is the default used for AMDGPU function calls