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/// An assembled representation of a cairo program.
78#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
79#[derive(Debug, Clone)]
80pub struct AssembledCairoProgram {
81    /// The bytecode of the program.
82    pub bytecode: Vec<BigInt>,
83    /// The list of hints, and the instruction index they refer to.
84    pub hints: Vec<(usize, Vec<Hint>)>,
85}
86
87impl Instruction {
88    pub fn assemble(&self) -> InstructionRepr {
89        match &self.body {
90            InstructionBody::AddAp(insn) => {
91                assert!(!self.inc_ap, "An add_ap instruction cannot have an ap++.");
92                let res = insn.operand.to_res_description();
93                InstructionRepr {
94                    off0: -1,
95                    off1: res.off1,
96                    off2: res.off2,
97                    imm: res.imm,
98                    dst_register: Register::FP,
99                    op0_register: res.op0_register,
100                    op1_addr: res.op1_addr,
101                    res: res.res,
102                    pc_update: PcUpdate::Regular,
103                    ap_update: ApUpdate::Add,
104                    fp_update: FpUpdate::Regular,
105                    opcode: Opcode::Nop,
106                }
107            }
108            InstructionBody::AssertEq(insn) => {
109                let res = insn.b.to_res_description();
110                InstructionRepr {
111                    off0: insn.a.offset,
112                    off1: res.off1,
113                    off2: res.off2,
114                    imm: res.imm,
115                    dst_register: insn.a.register,
116                    op0_register: res.op0_register,
117                    op1_addr: res.op1_addr,
118                    res: res.res,
119                    pc_update: PcUpdate::Regular,
120                    ap_update: if self.inc_ap { ApUpdate::Add1 } else { ApUpdate::Regular },
121                    fp_update: FpUpdate::Regular,
122                    opcode: Opcode::AssertEq,
123                }
124            }
125            InstructionBody::Call(insn) => {
126                assert!(!self.inc_ap, "A call instruction cannot have an ap++.");
127                let res = insn.target.to_res_description();
128                InstructionRepr {
129                    off0: 0,
130                    off1: 1,
131                    off2: res.off2,
132                    imm: res.imm,
133                    dst_register: Register::AP,
134                    op0_register: Register::AP,
135                    op1_addr: res.op1_addr,
136                    res: Res::Op1,
137                    pc_update: if insn.relative { PcUpdate::JumpRel } else { PcUpdate::Jump },
138                    ap_update: ApUpdate::Add2,
139                    fp_update: FpUpdate::ApPlus2,
140                    opcode: Opcode::Call,
141                }
142            }
143            InstructionBody::Jump(insn) => {
144                let res = insn.target.to_res_description();
145                InstructionRepr {
146                    off0: -1,
147                    off1: res.off1,
148                    off2: res.off2,
149                    imm: res.imm,
150                    dst_register: Register::FP,
151                    op0_register: Register::FP,
152                    op1_addr: res.op1_addr,
153                    res: Res::Op1,
154                    pc_update: if insn.relative { PcUpdate::JumpRel } else { PcUpdate::Jump },
155                    ap_update: if self.inc_ap { ApUpdate::Add1 } else { ApUpdate::Regular },
156                    fp_update: FpUpdate::Regular,
157                    opcode: Opcode::Nop,
158                }
159            }
160            InstructionBody::Jnz(insn) => {
161                let res = insn.jump_offset.to_res_description();
162                InstructionRepr {
163                    off0: insn.condition.offset,
164                    off1: -1,
165                    off2: res.off2,
166                    imm: res.imm,
167                    dst_register: insn.condition.register,
168                    op0_register: Register::FP,
169                    op1_addr: res.op1_addr,
170                    res: Res::Unconstrained,
171                    pc_update: PcUpdate::Jnz,
172                    ap_update: if self.inc_ap { ApUpdate::Add1 } else { ApUpdate::Regular },
173                    fp_update: FpUpdate::Regular,
174                    opcode: Opcode::Nop,
175                }
176            }
177            InstructionBody::Ret(_) => {
178                assert!(!self.inc_ap);
179                InstructionRepr {
180                    off0: -2,
181                    off1: -1,
182                    off2: -1,
183                    imm: None,
184                    dst_register: Register::FP,
185                    op0_register: Register::FP,
186                    op1_addr: Op1Addr::FP,
187                    res: Res::Op1,
188                    pc_update: PcUpdate::Jump,
189                    ap_update: ApUpdate::Regular,
190                    fp_update: FpUpdate::Dst,
191                    opcode: Opcode::Ret,
192                }
193            }
194        }
195    }
196}
197
198impl Register {
199    fn to_op1_addr(self) -> Op1Addr {
200        match self {
201            Register::AP => Op1Addr::AP,
202            Register::FP => Op1Addr::FP,
203        }
204    }
205}
206
207impl Operation {
208    fn to_res(&self) -> Res {
209        match self {
210            Operation::Add => Res::Add,
211            Operation::Mul => Res::Mul,
212        }
213    }
214}
215
216impl DerefOrImmediate {
217    fn to_res_operand(&self) -> ResOperand {
218        match self {
219            DerefOrImmediate::Deref(operand) => ResOperand::Deref(*operand),
220            DerefOrImmediate::Immediate(operand) => ResOperand::Immediate(operand.clone()),
221        }
222    }
223    fn to_res_description(&self) -> ResDescription {
224        self.to_res_operand().to_res_description()
225    }
226}
227
228/// The part of the instruction describing the res operand.
229struct ResDescription {
230    off1: i16,
231    off2: i16,
232    imm: Option<BigInt>,
233    op0_register: Register,
234    op1_addr: Op1Addr,
235    res: Res,
236}
237
238impl ResOperand {
239    fn to_res_description(&self) -> ResDescription {
240        match self {
241            ResOperand::Deref(operand) => ResDescription {
242                off1: -1,
243                off2: operand.offset,
244                imm: None,
245                op0_register: Register::FP,
246                op1_addr: operand.register.to_op1_addr(),
247                res: Res::Op1,
248            },
249            ResOperand::DoubleDeref(operand, offset) => ResDescription {
250                off1: operand.offset,
251                off2: *offset,
252                imm: None,
253                op0_register: operand.register,
254                op1_addr: Op1Addr::Op0,
255                res: Res::Op1,
256            },
257            ResOperand::Immediate(operand) => ResDescription {
258                off1: -1,
259                off2: 1,
260                // TODO(alon): Change immediate to always work with bigint.
261                imm: operand.value.to_bigint(),
262                op0_register: Register::FP,
263                op1_addr: Op1Addr::Imm,
264                res: Res::Op1,
265            },
266            ResOperand::BinOp(operand) => {
267                let a_res = ResOperand::Deref(operand.a).to_res_description();
268                let b_res = operand.b.to_res_description();
269                ResDescription {
270                    off1: a_res.off2,
271                    off2: b_res.off2,
272                    imm: b_res.imm,
273                    op0_register: operand.a.register,
274                    op1_addr: match operand.b {
275                        DerefOrImmediate::Immediate(_) => Op1Addr::Imm,
276                        DerefOrImmediate::Deref(b) => b.register.to_op1_addr(),
277                    },
278                    res: operand.op.to_res(),
279                }
280            }
281        }
282    }
283}