I'm trying to solve this bonus question from the "How Cairo Works" tutorial. I ran the following function, opened the Cairo tracer and saw that the memory is full with powers of 2. Why is that?
func main():
[fp + 1] = 2; ap++
[fp] = 5201798304953761792; ap++
jmp rel -1
end
Here are some leading questions that can help you reach the answer. Answers to the questions after a break:
jmp rel -1
instruction jump to?jmp rel -1
is encoded in the memory at addresses 5-6. When it is executed, we havepc = 5
, thus after the jump we will execute the instruction atpc = 4
, which is0x48307fff7fff8000
.[ap] = [ap - 1] + [ap - 1]; ap++
(to check, you can manually decode the flags and offsets [Edit: see below], or simply write a cairo program with this instruction and see what it compiles to). After it is executed,pc
is increased by 1, so we again executejmp rel -1
, and so on in an infinite loop. It should be clear why this fills the memory with powers of 2 (the first 2, at address 10, was written by the[fp + 1] = 2; ap++
instruction).[fp] = 5201798304953761792; ap++
has an immediate argument (the right hand side, 5201798304953761792). Instructions with immediate arguments are encoded as two field elements in the memory, the first encoding the general instruction (e.g.[fp] = imm; ap++
), and the second being the immediate value itself. This immediate value is thus written in address 4, and indeed 5201798304953761792 is the same as0x48307fff7fff8000
. Similarly, the2
at address 2 is the immediate argument of the instruction[fp + 1] = 2
, and the-1
at address 6 is the immediate ofjmp rel -1
.To summarize, this strange behavior is due to the relative jump moving to an address of an immediate value and parsing it as a standalone instruction. Normally this wouldn't occur, since
pc
is incremented by 2 after executing an instruction with an immediate value, and by 1 when executing an instruction without one, so it always continues to the next compiled instruction. The unlabeled jump was necessary here to reach this unexpected program counter.How can one manually decode the flags and offsets of
0x48307fff7fff8000
? Consulting the Cairo whitepaper (mostly pages 50-59), we see that the lower three 16-bit words encode offsets offdst = 0, offop0 = offop1 = -1 (the values0x8000
,0x7fff
,0x7fff
are offset by 215, or alternatively can be considered as signed integers, as detailed on page 51). The flag word is0x4830
, which has 4 flags set to 1 and the rest are 0: the set flags, from least to most, are f4, f5, f11 and f14, which correspond to the flagsOP1_AP
,RES_ADD
,AP_ADD1
andOPCODE_ASSERT_EQ
(according to page 58). Let us explore the meaning of these flags (derived from the constraints listed in pages 58-59):OP1_AP
flag means operand 1 is taken relative toap
, with offset offop1, i.e.op1 = [ap - 1]
. Operand 0 anddst
are also relative toap
by default (when the relevant flags are not set), and including the above offsets we see thatop0 = [ap - 1]
,dst = [ap]
.RES_ADD
flag means the operation betweenop0
andop1
is addition, i.e. the constraintres = [ap - 1] + [ap - 1]
is enforced.OPCODE_ASSERT_EQ
flag means this is as equality assertion command, which meansres
will be equaled todst
by enforcingdst - res = 0
, which we now see is equivalent to[ap] = [ap - 1] + [ap - 1]
.AP_ADD1
flag simply meansap
is advanced by 1, which corresponds to theap++
part of the command.Taken all together, we get the command
[ap] = [ap - 1] + [ap - 1]; ap++
as claimed.