Segments¶
Rationale¶
As you may recall, the memory of a Cairo program has to be continuous. However, some parts of the program may be individually continuous but vary in length in ways that are only computed at runtime – in fact, their size can only be known after the program terminates.
For this purpose, during the run of the Cairo VM, it’s useful to treat the memory as a list of continuous segments, which are concatenated to form one continuous chunk at the end of the run, when their final sizes can be calculated.
Relocatable values¶
The absolute address of every memory cell within a segment can only be determined at the end of a
VM run. Because these addresses can be stored in memory cells themselves, the VM needs a way to
refer to them. This is achieved using relocatable values, represented as <segment>:<offset>
,
where <segment>
is the segment number, assigned arbitrarily at the start of the run, and
<offset>
is the offset of the memory cell within the segment.
Note that because segment numbers are assigned arbitrarily, the number is not guaranteed to represent the same segment across different programs or even in different runs of the same program.
Uses¶
The program and execution segments¶
Cairo programs are themselves kept in memory, in what is called the program segment. This
segment is of fixed length and contains the numeric representation of the Cairo program.
The program counter pc
starts at the beginning of the program segment.
In addition to this, any Cairo program requires an execution segment. This is where the
registers ap
and fp
start, and where data generated during the run of a Cairo program
(variables, return addresses for function calls, etc.) is stored.
The length of the execution segment is variable, as it depends, for example, on the program input.
Builtin segments¶
As you’ll see in the Builtins and implicit arguments section, every builtin receives its own continuous area in memory. This memory is located in its own segment, which is variable in length.
Example¶
Compile the following code and run it with
--layout=small --print_memory --print_info --print_segments
(and without --relocate_prints
):
%builtins output
func main(output_ptr: felt*) -> (output_ptr: felt*) {
[ap] = output_ptr, ap++;
%{
print('ap =', ap)
print('[ap - 1] =', memory[ap - 1])
print()
%}
assert [output_ptr] = 12;
return (output_ptr=output_ptr + 1);
}
Segments appear in the output in a few places. First, the hint output:
ap = 1:4
[ap - 1] = 2:0
The hint prints the value of ap
and then the value of the memory cell at ap - 1
, to which
output_ptr
was assigned in the line above. Observe that the value of the ap
register is
the relocatable value 1:4
. Usually, segment 1
is the execution segment (recall that ap
starts in the execution segment). On the other hand, the value it points to, the output builtin
pointer, is located in its own segment 2
.
Segments appear again in the memory output:
Addr Value
-----------
⋮
0:0 5191102247248822272
0:1 5189976364521848832
0:2 12
0:3 4612389708016484351
0:4 5198983563776458752
0:5 1
0:6 2345108766317314046
⋮
1:0 2:0
1:1 3:0
1:2 4:0
1:3 2:0
1:4 12
1:5 2:1
⋮
2:0 12
Program output:
12
Number of steps: 5 (originally, 5)
Used memory cells: 14
Register values after execution:
pc = 4:0
ap = 1:6
fp = 3:0
The memory is divided into three segments:
Segment
0
: the program segment. This segment contains the compiled bytecode of the program.Segment
1
: the execution segment. This segment contains the values saved in memory during the run of the program. Observe that most of these represent pointers and are thus relocatable values themselves. The constant12
, which appears twice, is the only exception.Segment
2
: the output builtin segment. This segment contains the only value written to the output,12
.
The final values of the registers are also relocatable. ap
remains in the execution segment,
while the return values of fp
and pc
are given their own segments for technical reasons.
Finally, the segment relocation table describes the real addresses of the beginning of the segments after relocation:
Segment relocation table:
0 1
1 8
2 14
3 15
4 15
Segments 3-4
are the empty segments used for the return values of fp
an pc
.
Observe that each segment’s beginning is mapped to the sum of the lengths of the previous
segments. This keeps the entire memory continuous.
Exercise¶
Run the same program again, this time with the flag --relocate_prints
, which will print
the same values after relocation.
Convince yourself that the relocated memory and register values indeed correspond to the relocatable values, relocated according to the segment relocation table.
Why are the values printed from the hint (the top two lines) still relocatable? Is it possible to print their relocated value from the same hint?