cairo_lang_casm/
assembler.rs

1#[cfg(not(feature = "std"))]
2use alloc::vec::Vec;
3
4use num_bigint::{BigInt, ToBigInt};
5
6use crate::hints::Hint;
7use crate::instructions::{Instruction, InstructionBody};
8use crate::operand::{DerefOrImmediate, Operation, Register, ResOperand};
9
10#[cfg(test)]
11#[path = "assembler_test.rs"]
12mod test;
13
14/// Cairo instruction structure flags.
15#[derive(Debug, Eq, PartialEq)]
16pub enum Op1Addr {
17    Imm,
18    AP,
19    FP,
20    Op0,
21}
22#[derive(Debug, Eq, PartialEq)]
23pub enum Res {
24    Op1,
25    Add,
26    Mul,
27    Unconstrained,
28}
29#[derive(Debug, Eq, PartialEq)]
30pub enum PcUpdate {
31    Regular,
32    Jump,
33    JumpRel,
34    Jnz,
35}
36
37#[derive(Debug, Eq, PartialEq)]
38pub enum ApUpdate {
39    Regular,
40    Add,
41    Add1,
42    Add2,
43}
44
45#[derive(Debug, Eq, PartialEq)]
46pub enum FpUpdate {
47    Regular,
48    ApPlus2,
49    Dst,
50}
51
52#[derive(Debug, Eq, PartialEq)]
53pub enum Opcode {
54    Nop,
55    AssertEq,
56    Call,
57    Ret,
58}
59
60/// The low level representation of a cairo instruction.
61#[derive(Debug, Eq, PartialEq)]
62pub struct InstructionRepr {
63    pub off0: i16,
64    pub off1: i16,
65    pub off2: i16,
66    pub imm: Option<BigInt>,
67    pub dst_register: Register,
68    pub op0_register: Register,
69    pub op1_addr: Op1Addr,
70    pub res: Res,
71    pub pc_update: PcUpdate,
72    pub ap_update: ApUpdate,
73    pub fp_update: FpUpdate,
74    pub opcode: Opcode,
75}
76
77#[cfg(feature = "serde")]
78use cairo_lang_utils::bigint::{deserialize_big_ints, serialize_big_ints};
79
80/// An assembled representation of a cairo program.
81#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
82#[derive(Debug, Clone)]
83pub struct AssembledCairoProgram {
84    /// The bytecode of the program.
85    #[cfg_attr(
86        feature = "serde",
87        serde(serialize_with = "serialize_big_ints", deserialize_with = "deserialize_big_ints")
88    )]
89    pub bytecode: Vec<BigInt>,
90    /// The list of hints, and the instruction index they refer to.
91    pub hints: Vec<(usize, Vec<Hint>)>,
92}
93
94impl Instruction {
95    pub fn assemble(&self) -> InstructionRepr {
96        match &self.body {
97            InstructionBody::AddAp(insn) => {
98                assert!(!self.inc_ap, "An add_ap instruction cannot have an ap++.");
99                let res = insn.operand.to_res_description();
100                InstructionRepr {
101                    off0: -1,
102                    off1: res.off1,
103                    off2: res.off2,
104                    imm: res.imm,
105                    dst_register: Register::FP,
106                    op0_register: res.op0_register,
107                    op1_addr: res.op1_addr,
108                    res: res.res,
109                    pc_update: PcUpdate::Regular,
110                    ap_update: ApUpdate::Add,
111                    fp_update: FpUpdate::Regular,
112                    opcode: Opcode::Nop,
113                }
114            }
115            InstructionBody::AssertEq(insn) => {
116                let res = insn.b.to_res_description();
117                InstructionRepr {
118                    off0: insn.a.offset,
119                    off1: res.off1,
120                    off2: res.off2,
121                    imm: res.imm,
122                    dst_register: insn.a.register,
123                    op0_register: res.op0_register,
124                    op1_addr: res.op1_addr,
125                    res: res.res,
126                    pc_update: PcUpdate::Regular,
127                    ap_update: if self.inc_ap { ApUpdate::Add1 } else { ApUpdate::Regular },
128                    fp_update: FpUpdate::Regular,
129                    opcode: Opcode::AssertEq,
130                }
131            }
132            InstructionBody::Call(insn) => {
133                assert!(!self.inc_ap, "A call instruction cannot have an ap++.");
134                let res = insn.target.to_res_description();
135                InstructionRepr {
136                    off0: 0,
137                    off1: 1,
138                    off2: res.off2,
139                    imm: res.imm,
140                    dst_register: Register::AP,
141                    op0_register: Register::AP,
142                    op1_addr: res.op1_addr,
143                    res: Res::Op1,
144                    pc_update: if insn.relative { PcUpdate::JumpRel } else { PcUpdate::Jump },
145                    ap_update: ApUpdate::Add2,
146                    fp_update: FpUpdate::ApPlus2,
147                    opcode: Opcode::Call,
148                }
149            }
150            InstructionBody::Jump(insn) => {
151                let res = insn.target.to_res_description();
152                InstructionRepr {
153                    off0: -1,
154                    off1: res.off1,
155                    off2: res.off2,
156                    imm: res.imm,
157                    dst_register: Register::FP,
158                    op0_register: Register::FP,
159                    op1_addr: res.op1_addr,
160                    res: Res::Op1,
161                    pc_update: if insn.relative { PcUpdate::JumpRel } else { PcUpdate::Jump },
162                    ap_update: if self.inc_ap { ApUpdate::Add1 } else { ApUpdate::Regular },
163                    fp_update: FpUpdate::Regular,
164                    opcode: Opcode::Nop,
165                }
166            }
167            InstructionBody::Jnz(insn) => {
168                let res = insn.jump_offset.to_res_description();
169                InstructionRepr {
170                    off0: insn.condition.offset,
171                    off1: -1,
172                    off2: res.off2,
173                    imm: res.imm,
174                    dst_register: insn.condition.register,
175                    op0_register: Register::FP,
176                    op1_addr: res.op1_addr,
177                    res: Res::Unconstrained,
178                    pc_update: PcUpdate::Jnz,
179                    ap_update: if self.inc_ap { ApUpdate::Add1 } else { ApUpdate::Regular },
180                    fp_update: FpUpdate::Regular,
181                    opcode: Opcode::Nop,
182                }
183            }
184            InstructionBody::Ret(_) => {
185                assert!(!self.inc_ap);
186                InstructionRepr {
187                    off0: -2,
188                    off1: -1,
189                    off2: -1,
190                    imm: None,
191                    dst_register: Register::FP,
192                    op0_register: Register::FP,
193                    op1_addr: Op1Addr::FP,
194                    res: Res::Op1,
195                    pc_update: PcUpdate::Jump,
196                    ap_update: ApUpdate::Regular,
197                    fp_update: FpUpdate::Dst,
198                    opcode: Opcode::Ret,
199                }
200            }
201        }
202    }
203}
204
205impl Register {
206    fn to_op1_addr(self) -> Op1Addr {
207        match self {
208            Register::AP => Op1Addr::AP,
209            Register::FP => Op1Addr::FP,
210        }
211    }
212}
213
214impl Operation {
215    fn to_res(&self) -> Res {
216        match self {
217            Operation::Add => Res::Add,
218            Operation::Mul => Res::Mul,
219        }
220    }
221}
222
223impl DerefOrImmediate {
224    fn to_res_operand(&self) -> ResOperand {
225        match self {
226            DerefOrImmediate::Deref(operand) => ResOperand::Deref(*operand),
227            DerefOrImmediate::Immediate(operand) => ResOperand::Immediate(operand.clone()),
228        }
229    }
230    fn to_res_description(&self) -> ResDescription {
231        self.to_res_operand().to_res_description()
232    }
233}
234
235/// The part of the instruction describing the res operand.
236struct ResDescription {
237    off1: i16,
238    off2: i16,
239    imm: Option<BigInt>,
240    op0_register: Register,
241    op1_addr: Op1Addr,
242    res: Res,
243}
244
245impl ResOperand {
246    fn to_res_description(&self) -> ResDescription {
247        match self {
248            ResOperand::Deref(operand) => ResDescription {
249                off1: -1,
250                off2: operand.offset,
251                imm: None,
252                op0_register: Register::FP,
253                op1_addr: operand.register.to_op1_addr(),
254                res: Res::Op1,
255            },
256            ResOperand::DoubleDeref(operand, offset) => ResDescription {
257                off1: operand.offset,
258                off2: *offset,
259                imm: None,
260                op0_register: operand.register,
261                op1_addr: Op1Addr::Op0,
262                res: Res::Op1,
263            },
264            ResOperand::Immediate(operand) => ResDescription {
265                off1: -1,
266                off2: 1,
267                // TODO(alon): Change immediate to always work with bigint.
268                imm: operand.value.to_bigint(),
269                op0_register: Register::FP,
270                op1_addr: Op1Addr::Imm,
271                res: Res::Op1,
272            },
273            ResOperand::BinOp(operand) => {
274                let a_res = ResOperand::Deref(operand.a).to_res_description();
275                let b_res = operand.b.to_res_description();
276                ResDescription {
277                    off1: a_res.off2,
278                    off2: b_res.off2,
279                    imm: b_res.imm,
280                    op0_register: operand.a.register,
281                    op1_addr: match operand.b {
282                        DerefOrImmediate::Immediate(_) => Op1Addr::Imm,
283                        DerefOrImmediate::Deref(b) => b.register.to_op1_addr(),
284                    },
285                    res: operand.op.to_res(),
286                }
287            }
288        }
289    }
290}