1
1
mirror of https://github.com/anoma/juvix.git synced 2024-11-30 05:42:26 +03:00
juvix/tests/Reg/positive/test038.jvr
Łukasz Czajka ee2f8aefbc
Fix memory access order in the JuvixReg to CASM translation. (#2697)
Cairo VM imposes restrictions on memory access order stricter than
described in the documentation, which necessitates changing the
compilation concept for local variables.

Summary
-------------

To ensure that memory is accessed sequentially at all times, we divide
instructions into basic blocks. Within each basic block, the `ap` offset
(i.e. how much `ap` increased since the beginning of the block) is known
at each instruction, which allows to statically associate `fp` offsets
to local variables while still generating only sequential assignments to
`[ap]` with increasing `ap`. When the `ap` offset can no longer be
statically determined for new local variables (e.g. due to an
intervening recursive call), we switch to the next basic block by
calling it with the `call` instruction. The arguments of the basic block
call are the variables live at the beginning of the called block. Note
that the `fp` offsets of "old" variables are still statically determined
even after the current `ap` offset becomes unknown -- the arbitrary
increase of `ap` does not influence the previous variable associations.
Hence, we can transfer the needed local variables to the next basic
block.

Example
-----------

The JuvixReg function
```
function f(integer) : integer {
  tmp[0] = add arg[0] 1;
  tmp[1] = call g(tmp[0]);
  tmp[2] = add tmp[1] arg[0];
  tmp[3] = mul tmp[2] 2;
  tmp[4] = call g(tmp[2]);
  tmp[5] = add tmp[4] tmp[3];
  ret tmp[5];
}
```
is compiled to
```
f:
  -- code for basic block 1
  [ap] = [fp - 3] + 1; ap++
  -- now [fp] is tmp[0], because fp = ap at function start (ap offset is zero) 
  -- transfer call argument (in this case, could be optimized away)
  [ap] = [fp]; ap++
  call g
  -- now [ap - 1] contains the result tmp[1] (it is already a call argument now)
  -- we additionally transfer arg[0] which is live in the next block
  [ap] = [fp - 3]; ap++
  call rel 3
  ret
  nop

  -- code for basic block 2
  -- the above "call rel" jumps here
  -- [fp - 4] is tmp[1] 
  -- [fp - 3] is arg[0]
  [ap] = [fp - 4] + [fp - 3]; ap++
  -- now [fp] is tmp[2]
  [ap] = [fp] * 2; ap++
  -- now [fp + 1] is tmp[3]
  [ap] = [fp]; ap++
  call g
  -- now [ap - 1] is tmp[4]
  [ap] = [fp + 1]; ap++
  call rel 3
  ret
  nop

  -- code for basic block 3
  -- [fp - 4] is tmp[4]
  -- [fp - 3] is tmp[3]
  [ap] = [fp - 4] + [fp - 3]; ap++
  -- now [fp] is tmp[5]
  -- the next assignment could be optimized away in this case
  [ap] = [fp]; ap++
  ret  
```
There are three basic blocks separated by the `call` instructions. In
each basic block, we know statically the `ap` offset at each instruction
(i.e. how much `ap` increased since the beginning of the block). We can
therefore associate the temporary variables with `[fp + k]` for
appropriate `k`. At basic block boundaries we transfer live temporary
variables as arguments for the call to the next basic block.

Checklist
------------
- [x] Divide JuvixReg instructions into [basic
blocks](https://en.wikipedia.org/wiki/Basic_block).
- [x] Implement liveness analysis for each basic block.
- [x] Translate transitions between basic blocks into CASM relative
calls with local live variable transfer.
- [x] Tests for the translation from JuvixReg to Cairo bytecode executed
with the Cairo VM
2024-03-27 10:40:24 +01:00

269 lines
7.0 KiB
Plaintext

function apply_1(*, *) : *;
function apply_2(*, *, *) : *;
function apply_3(*, *, *, *) : *;
function apply_4(*, *, *, *, *) : *;
function S(*, *, *) : *;
function K(*, *) : *;
function I(*) : *;
function f3(integer, integer, integer) : integer;
function main() : *;
function apply_1(*, *) : * {
tmp[0] = arg[0];
tmp[0] = argsnum tmp[0];
tmp[1] = 1;
tmp[0] = eq tmp[1] tmp[0];
br tmp[0] {
true: {
tmp[0] = arg[1];
tmp[1] = arg[0];
tcall tmp[1] (tmp[0]);
};
false: {
prealloc 7, live: (arg[0], arg[1]);
tmp[0] = arg[1];
tmp[1] = arg[0];
tmp[0] = cextend tmp[1] (tmp[0]);
ret tmp[0];
};
};
}
function apply_2(*, *, *) : * {
tmp[1] = arg[0];
tmp[1] = argsnum tmp[1];
n = tmp[1];
tmp[1] = n;
tmp[2] = 2;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[2];
tmp[2] = arg[1];
tmp[3] = arg[0];
tcall tmp[3] (tmp[2], tmp[1]);
};
false: {
tmp[1] = n;
tmp[2] = 1;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[2];
tmp[2] = arg[1];
tmp[3] = arg[0];
tmp[2] = call tmp[3] (tmp[2]), live: (tmp[0], tmp[1], arg[0], arg[1], arg[2]);
tcall apply_1 (tmp[2], tmp[1]);
};
false: {
prealloc 7, live: (tmp[0], arg[0], arg[1], arg[2]);
tmp[1] = arg[2];
tmp[2] = arg[1];
tmp[3] = arg[0];
tmp[1] = cextend tmp[3] (tmp[2], tmp[1]);
ret tmp[1];
};
};
};
};
}
function apply_3(*, *, *, *) : * {
tmp[1] = arg[0];
tmp[1] = argsnum tmp[1];
n = tmp[1];
tmp[1] = n;
tmp[2] = 3;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[3];
tmp[2] = arg[2];
tmp[3] = arg[1];
tmp[4] = arg[0];
tcall tmp[4] (tmp[3], tmp[2], tmp[1]);
};
false: {
tmp[1] = n;
tmp[2] = 2;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[3];
tmp[2] = arg[2];
tmp[3] = arg[1];
tmp[4] = arg[0];
tmp[2] = call tmp[4] (tmp[3], tmp[2]), live: (tmp[0], tmp[1], arg[0], arg[1], arg[2], arg[3]);
tcall apply_1 (tmp[2], tmp[1]);
};
false: {
tmp[1] = n;
tmp[2] = 1;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[3];
tmp[2] = arg[2];
tmp[3] = arg[1];
tmp[4] = arg[0];
tmp[3] = call tmp[4] (tmp[3]), live: (tmp[0], tmp[1], tmp[2], arg[0], arg[1], arg[2], arg[3]);
tcall apply_2 (tmp[3], tmp[2], tmp[1]);
};
false: {
prealloc 7, live: (tmp[0], arg[0], arg[1], arg[2], arg[3]);
tmp[1] = arg[3];
tmp[2] = arg[2];
tmp[3] = arg[1];
tmp[4] = arg[0];
tmp[1] = cextend tmp[4] (tmp[3], tmp[2], tmp[1]);
ret tmp[1];
};
};
};
};
};
};
}
function apply_4(*, *, *, *, *) : * {
tmp[1] = arg[0];
tmp[1] = argsnum tmp[1];
n = tmp[1];
tmp[1] = n;
tmp[2] = 4;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[4];
tmp[2] = arg[3];
tmp[3] = arg[2];
tmp[4] = arg[1];
tmp[5] = arg[0];
tcall tmp[5] (tmp[4], tmp[3], tmp[2], tmp[1]);
};
false: {
tmp[1] = n;
tmp[2] = 3;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[4];
tmp[2] = arg[3];
tmp[3] = arg[2];
tmp[4] = arg[1];
tmp[5] = arg[0];
tmp[2] = call tmp[5] (tmp[4], tmp[3], tmp[2]), live: (tmp[0], tmp[1], arg[0], arg[1], arg[2], arg[3], arg[4]);
tcall apply_1 (tmp[2], tmp[1]);
};
false: {
tmp[1] = n;
tmp[2] = 2;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[4];
tmp[2] = arg[3];
tmp[3] = arg[2];
tmp[4] = arg[1];
tmp[5] = arg[0];
tmp[3] = call tmp[5] (tmp[4], tmp[3]), live: (tmp[0], tmp[1], tmp[2], arg[0], arg[1], arg[2], arg[3], arg[4]);
tcall apply_2 (tmp[3], tmp[2], tmp[1]);
};
false: {
tmp[1] = n;
tmp[2] = 1;
tmp[1] = eq tmp[2] tmp[1];
br tmp[1] {
true: {
tmp[1] = arg[4];
tmp[2] = arg[3];
tmp[3] = arg[2];
tmp[4] = arg[1];
tmp[5] = arg[0];
tmp[4] = call tmp[5] (tmp[4]), live: (tmp[0], tmp[1], tmp[2], tmp[3], arg[0], arg[1], arg[2], arg[3], arg[4]);
tcall apply_3 (tmp[4], tmp[3], tmp[2], tmp[1]);
};
false: {
prealloc 7, live: (tmp[0], arg[0], arg[1], arg[2], arg[3], arg[4]);
tmp[1] = arg[4];
tmp[2] = arg[3];
tmp[3] = arg[2];
tmp[4] = arg[1];
tmp[5] = arg[0];
tmp[1] = cextend tmp[5] (tmp[4], tmp[3], tmp[2], tmp[1]);
ret tmp[1];
};
};
};
};
};
};
};
};
}
function S(*, *, *) : * {
tmp[0] = arg[2];
tmp[1] = arg[1];
tmp[0] = call apply_1 (tmp[1], tmp[0]), live: (arg[0], arg[1], arg[2]);
tmp[1] = arg[2];
tmp[2] = arg[0];
tcall apply_2 (tmp[2], tmp[1], tmp[0]);
}
function K(*, *) : * {
tmp[0] = arg[0];
ret tmp[0];
}
function I(*) : * {
prealloc 4, live: (arg[0]);
tmp[0] = arg[0];
tmp[1] = calloc K ();
tmp[2] = calloc K ();
tcall S (tmp[2], tmp[1], tmp[0]);
}
function f3(integer, integer, integer) : integer {
tmp[0] = arg[2];
tmp[1] = arg[1];
tmp[2] = arg[0];
tmp[1] = add tmp[2] tmp[1];
tmp[0] = mul tmp[1] tmp[0];
ret tmp[0];
}
function main() : * {
prealloc 14;
tmp[0] = 7;
tmp[1] = 1;
tmp[2] = calloc I ();
tmp[3] = calloc I ();
tmp[4] = calloc I ();
tmp[5] = calloc I ();
tmp[6] = calloc I ();
tmp[7] = calloc I ();
tmp[8] = calloc I ();
tmp[4] = call apply_4 (tmp[8], tmp[7], tmp[6], tmp[5], tmp[4]), live: (tmp[0], tmp[1], tmp[2], tmp[3]);
tmp[1] = call apply_3 (tmp[4], tmp[3], tmp[2], tmp[1]), live: (tmp[0]);
prealloc 6, live: (tmp[0], tmp[1]);
tmp[2] = 2;
tmp[3] = calloc I ();
tmp[4] = calloc I ();
tmp[5] = calloc I ();
tmp[2] = call apply_3 (tmp[5], tmp[4], tmp[3], tmp[2]), live: (tmp[0], tmp[1]);
prealloc 6, live: (tmp[0], tmp[1], tmp[2]);
tmp[3] = 3;
tmp[4] = calloc I ();
tmp[5] = calloc I ();
tmp[6] = calloc I ();
tmp[3] = call apply_3 (tmp[6], tmp[5], tmp[4], tmp[3]), live: (tmp[0], tmp[1], tmp[2]);
prealloc 2, live: (tmp[0], tmp[1], tmp[2], tmp[3]);
tmp[4] = calloc f3 ();
tmp[1] = call apply_3 (tmp[4], tmp[3], tmp[2], tmp[1]), live: (tmp[0]);
prealloc 2, live: (tmp[0], tmp[1]);
tmp[2] = calloc K ();
tcall apply_2 (tmp[2], tmp[1], tmp[0]);
}