jasmin
jasmin copied to clipboard
Mutable arguments that are not meant to be returned
Apparently before PR https://github.com/jasmin-lang/jasmin/pull/82, some people were relying on a pattern involving inline functions and reg ptr
arguments used as a scratchpad. Here is a small example.
inline fn f (reg ptr u64[1] a, reg ptr u64[1] scratchpad) -> reg ptr u64[1] {
reg u64 tmp;
scratchpad[0] = 2;
tmp = scratchpad[0];
a[0] = tmp;
return a;
}
export fn main () -> reg u64 {
stack u64[1] a scratchpad;
reg u64 res;
a[0] = 1;
scratchpad[0] = 1;
a = f (a, scratchpad);
res = a[0];
return res;
}
Function f
takes as an argument variable scratchpad
that is used to write temporary data and is useless after the function call, that's why it's not returned. This example used to work because:
-
scratchpad
is not returned so is marked asconst
- the function is inline, otherwise it would fail at line
scratchpad[0] = 2;
during stack alloc - we don't read
scratchpad
after the call
With PR https://github.com/jasmin-lang/jasmin/pull/82, typing fails on scratchpad[0] = 2;
since writing in a const
array is now forbidden. Do we want this feature back? I don't think we want it back as is, because it was hacky. But one thing we could do is allowing reg mut ptr
not to be returned, meaning such a variable would be unusable after the function call. Maybe this would allow to reduce the lifetime of the variable because we wouldn't have to insert an assignment after the function call. And it could be supported also for non-inline functions.
cc @AntoineSere who mentioned that problem to me. Did I describe the issue faithfully?
Yes, the issue seems to me to be accurately described.
Are there any good reasons to use this kind of pattern? It seems that the inline function could declare its own local stack variable instead of receiving it from the caller.
If the same scratchpad can be used by different inlined functions, then this construct makes sense, as it allow you to only allocate one scratchpad instead of many if you were to declare a local stack variable in each inlined function.
Here is a small modification of @eponier 's example illustrating this usecase:
inline fn f (reg ptr u64[1] a, reg ptr u64[1] scratchpad) -> reg ptr u64[1] {
reg u64 tmp;
scratchpad[0] = 1;
tmp = scratchpad[0];
a[0] = tmp;
return a;
}
inline fn g (reg ptr u64[1] a, reg ptr u64[1] scratchpad) -> reg ptr u64[1] {
reg u64 tmp;
scratchpad[0] = 2;
tmp = scratchpad[0];
a[0] = tmp;
return a;
}
export fn main () -> reg u64 {
stack u64[1] a scratchpad;
reg u64 res;
a[0] = 1;
scratchpad[0] = 1;
a = f (a, scratchpad);
a = g (a, scratchpad);
res = a[0];
return res;
}
In this example, only one scratchpad is needed.
The compiler does that for you.
/* --------------------------------------------------- */
inline fn f (reg ptr u64[1] a, reg ptr u64[1] scratchpad) -> reg ptr u64[1], reg ptr u64[1] {
reg u64 tmp;
scratchpad[0] = 1;
tmp = scratchpad[0];
a[0] = tmp;
return a, scratchpad;
}
inline fn g (reg ptr u64[1] a, reg ptr u64[1] scratchpad) -> reg ptr u64[1], reg ptr u64[1] {
reg u64 tmp;
scratchpad[0] = 2;
tmp = scratchpad[0];
a[0] = tmp;
return a, scratchpad;
}
#[stacksize=16]
export fn main () -> reg u64 {
stack u64[1] a scratchpad;
reg u64 res;
a[0] = 1;
// scratchpad[0] = 1;
a, scratchpad = f (a, scratchpad);
a, _ = g (a, scratchpad);
res = a[0];
return res;
}
/* --------------------------------------------------- */
inline fn h (reg ptr u64[1] a) -> reg ptr u64[1] {
reg u64 tmp;
stack u64[1] scratchpad;
scratchpad[0] = 1;
tmp = scratchpad[0];
a[0] = tmp;
return a;
}
inline fn i (reg ptr u64[1] a) -> reg ptr u64[1] {
reg u64 tmp;
stack u64[1] scratchpad;
scratchpad[0] = 2;
tmp = scratchpad[0];
a[0] = tmp;
return a;
}
#[stacksize=16]
export fn good () -> reg u64 {
stack u64[1] a;
reg u64 res;
a[0] = 1;
a = h (a);
a = i (a);
res = a[0];
return res;
}