cairo_lang_sierra_to_casm/
relocations.rs

1use cairo_lang_casm::instructions::{
2    AssertEqInstruction, CallInstruction, Instruction, InstructionBody, JnzInstruction,
3    JumpInstruction,
4};
5use cairo_lang_casm::operand::{BinOpOperand, DerefOrImmediate, ResOperand};
6use cairo_lang_sierra::ids::ConcreteTypeId;
7use cairo_lang_sierra::program::StatementIdx;
8use cairo_lang_sierra_gas::objects::ConstCost;
9
10use crate::compiler::ConstsInfo;
11
12pub type CodeOffset = usize;
13
14#[derive(Debug, Eq, PartialEq)]
15pub enum Relocation {
16    /// Adds program_offset(StatementIdx) and subtracts the program offset of the casm instruction
17    /// that is being relocated.
18    RelativeStatementId(StatementIdx),
19    /// Adds the offset of the constant segment with this id.
20    SegmentStart(u32),
21    /// Adds the offset of the constant value in the const segments.
22    ConstStart(u32, ConcreteTypeId),
23    /// Adds the offset of the circuit with the given type.
24    CircuitStart(ConcreteTypeId),
25    /// Adds the offset between the current statement index and the end of the program code
26    /// segment (which includes the const segment at its end).
27    EndOfProgram,
28}
29
30impl Relocation {
31    pub fn apply(
32        &self,
33        instruction_offset: CodeOffset,
34        statement_offsets: &[CodeOffset],
35        consts_info: &ConstsInfo,
36        instruction: &mut Instruction,
37    ) {
38        let target_pc = match self {
39            Relocation::RelativeStatementId(statement_idx) => statement_offsets[statement_idx.0],
40            Relocation::SegmentStart(segment_index) => {
41                let segment = consts_info.segments.get(segment_index).expect("Segment not found.");
42                *statement_offsets.last().unwrap() + segment.segment_offset
43            }
44            Relocation::ConstStart(segment_index, ty) => {
45                let segment = consts_info.segments.get(segment_index).expect("Segment not found.");
46                *statement_offsets.last().unwrap()
47                    + segment.segment_offset
48                    + segment.const_offset.get(ty).expect("Const type not found in const segments.")
49            }
50            Relocation::EndOfProgram => {
51                *statement_offsets.last().unwrap() + consts_info.total_segments_size
52            }
53            Relocation::CircuitStart(circ_ty) => {
54                let segment_index =
55                    consts_info.circuit_segments.get(circ_ty).expect("Circuit not found");
56
57                let segment = consts_info.segments.get(segment_index).expect("Segment not found.");
58
59                *statement_offsets.last().unwrap()
60                    + segment.segment_offset
61                    + segment
62                        .const_offset
63                        .get(circ_ty)
64                        .expect("Const type not found in const segments.")
65            }
66        };
67        match instruction {
68            Instruction {
69                body:
70                    InstructionBody::Call(CallInstruction {
71                        target: DerefOrImmediate::Immediate(value),
72                        relative: true,
73                    }),
74                inc_ap: false,
75                ..
76            }
77            | Instruction {
78                body:
79                    InstructionBody::Jnz(JnzInstruction {
80                        jump_offset: DerefOrImmediate::Immediate(value),
81                        condition: _,
82                    }),
83                ..
84            }
85            | Instruction {
86                body:
87                    InstructionBody::Jump(JumpInstruction {
88                        target: DerefOrImmediate::Immediate(value),
89                        relative: true,
90                    }),
91                ..
92            }
93            | Instruction {
94                body:
95                    InstructionBody::AssertEq(AssertEqInstruction {
96                        b:
97                            ResOperand::BinOp(BinOpOperand {
98                                b: DerefOrImmediate::Immediate(value),
99                                ..
100                            }),
101                        ..
102                    }),
103                ..
104            } => {
105                value.value += target_pc as i128 - instruction_offset as i128;
106            }
107            _ => panic!("Bad relocation."),
108        }
109    }
110}
111
112#[derive(Debug, Eq, PartialEq)]
113pub struct RelocationEntry {
114    /// The index of the casm instruction that needs to be relocated.
115    pub instruction_idx: CodeOffset,
116    /// The relocation that needs to be applied.
117    pub relocation: Relocation,
118}
119
120/// Applies 'relocations' to 'instructions'.
121///
122/// This is currently O(instruction.len()) rather than O(relocations.len()),
123/// But another pass is required anyhow to generate the bytecode and the relocations
124/// can be applied during that pass.
125pub fn relocate_instructions(
126    relocations: &[RelocationEntry],
127    statement_offsets: &[usize],
128    consts_info: &ConstsInfo,
129    instructions: &mut [Instruction],
130) {
131    let mut program_offset = 0;
132    let mut relocations_iter = relocations.iter();
133    let mut relocation_entry = relocations_iter.next();
134    for (instruction_idx, instruction) in instructions.iter_mut().enumerate() {
135        if let Some(RelocationEntry { instruction_idx: relocation_idx, relocation }) =
136            relocation_entry
137        {
138            if *relocation_idx == instruction_idx {
139                relocation.apply(program_offset, statement_offsets, consts_info, instruction);
140                relocation_entry = relocations_iter.next();
141            } else {
142                assert!(
143                    *relocation_idx > instruction_idx,
144                    "Relocation for already handled instruction #{relocation_idx} - currently \
145                     handling #{instruction_idx}."
146                );
147            }
148        }
149
150        program_offset += instruction.body.op_size();
151    }
152    assert!(
153        relocation_entry.is_none(),
154        "No relocations should be left when done with all instructions."
155    );
156}
157
158/// Represents a list of instructions with their relocations.
159#[derive(Default)]
160pub struct InstructionsWithRelocations {
161    pub instructions: Vec<Instruction>,
162    pub relocations: Vec<RelocationEntry>,
163    /// The gas cost of executing the instructions.
164    pub cost: ConstCost,
165}