neo_solidity/cli/bytecode/
bytecode_emit_ir.rs

1fn emit_ir_function(
2    function: &ir::Function,
3    module: &ir::Module,
4    method: &FunctionMetadata,
5    use_callt: bool,
6) -> (Vec<u8>, Vec<CallPatch>, Vec<MethodTokenPatch>) {
7    use std::{collections::HashMap, convert::TryFrom};
8
9    let mut local = Vec::new();
10    let arg_count = method.parameters.len() as u8;
11    let local_count = u8::try_from(function.local_count).unwrap_or(u8::MAX);
12    if local_count > 0 || arg_count > 0 {
13        local.push(0x57); // INITSLOT
14        local.push(local_count);
15        local.push(arg_count);
16    }
17    let mut label_offsets: HashMap<usize, u32> = HashMap::new();
18    let mut jump_patches: Vec<(usize, usize)> = Vec::new();
19    let mut call_patches: Vec<CallPatch> = Vec::new();
20    let mut token_patches: Vec<MethodTokenPatch> = Vec::new();
21
22    for block in &function.basic_blocks {
23        for instruction in &block.instructions {
24            match instruction {
25                ir::Instruction::Drop(_) => local.push(0x45),
26                ir::Instruction::LoadParameter(index) => {
27                    emit_load_parameter(&mut local, method, *index)
28                }
29                ir::Instruction::PushLiteral(literal) => {
30                    push_literal_value(&mut local, literal);
31                }
32                ir::Instruction::BinaryOp(operator) => emit_binary_op(&mut local, *operator),
33                ir::Instruction::Return | ir::Instruction::ReturnVoid => local.push(0x40),
34                ir::Instruction::ReturnDefault(value_type) => {
35                    append_default_value(&mut local, value_type);
36                    local.push(0x40);
37                }
38                ir::Instruction::LoadState(index) => emit_load_state(&mut local, module, *index),
39                ir::Instruction::StoreState(index) => emit_store_state(&mut local, module, *index),
40                ir::Instruction::LoadStorageDynamic => emit_load_storage_dynamic(&mut local),
41                ir::Instruction::LoadLocal(index) => emit_load_local(&mut local, *index),
42                ir::Instruction::StoreLocal(index) => emit_store_local(&mut local, *index),
43                ir::Instruction::LoadMappingElement {
44                    state_index,
45                    key_types,
46                } => emit_load_mapping(
47                    &mut local,
48                    module,
49                    *state_index,
50                    key_types,
51                    use_callt,
52                    &mut token_patches,
53                ),
54                ir::Instruction::StoreMappingElement {
55                    state_index,
56                    key_types,
57                } => emit_store_mapping(
58                    &mut local,
59                    module,
60                    *state_index,
61                    key_types,
62                    use_callt,
63                    &mut token_patches,
64                ),
65                ir::Instruction::LoadStructField {
66                    state_index,
67                    key_types,
68                    field_keys,
69                    field_type,
70                    ..
71                } => emit_load_struct_field(
72                    &mut local,
73                    module,
74                    *state_index,
75                    key_types,
76                    StructFieldAccess {
77                        field_keys: field_keys.as_slice(),
78                        ty: field_type,
79                    },
80                    use_callt,
81                    &mut token_patches,
82                ),
83                ir::Instruction::StoreStructField {
84                    state_index,
85                    key_types,
86                    field_keys,
87                    field_type,
88                    ..
89                } => {
90                    emit_store_struct_field(
91                        &mut local,
92                        module,
93                        *state_index,
94                        key_types,
95                        StructFieldAccess {
96                            field_keys: field_keys.as_slice(),
97                            ty: field_type,
98                        },
99                        use_callt,
100                        &mut token_patches,
101                    )
102                }
103                ir::Instruction::LoadStructArrayElement {
104                    state_index,
105                    key_types,
106                    field_keys,
107                    element_type,
108                } => emit_load_struct_array_element(
109                    &mut local,
110                    module,
111                    *state_index,
112                    key_types,
113                    StructArrayElementAccess {
114                        field_keys: field_keys.as_slice(),
115                        element_type,
116                    },
117                    use_callt,
118                    &mut token_patches,
119                ),
120                ir::Instruction::StoreStructArrayElement {
121                    state_index,
122                    key_types,
123                    field_keys,
124                    element_type,
125                } => emit_store_struct_array_element(
126                    &mut local,
127                    module,
128                    *state_index,
129                    key_types,
130                    StructArrayElementAccess {
131                        field_keys: field_keys.as_slice(),
132                        element_type,
133                    },
134                    use_callt,
135                    &mut token_patches,
136                ),
137                ir::Instruction::LoadRuntimeValue(value) => {
138                    emit_load_runtime_value(&mut local, value, use_callt, &mut token_patches)
139                }
140                ir::Instruction::GetSize => local.push(0xCA),
141                ir::Instruction::CallBuiltin { builtin, arg_count } => {
142                    emit_builtin_call(
143                        &mut local,
144                        builtin,
145                        *arg_count,
146                        use_callt,
147                        &mut token_patches,
148                    );
149                }
150                ir::Instruction::CallFunction { name, arg_count } => {
151                    // NeoVM's `INITSLOT` assigns argument slots by popping values from the
152                    // evaluation stack in order, meaning the callee sees the last-pushed value
153                    // as `arg0`. Solidity evaluates arguments left-to-right, so call sites push
154                    // `arg0, arg1, ...` onto the stack. Before calling, reverse the argument
155                    // segment so that `arg0` is on top and parameters arrive in the expected
156                    // order inside the callee.
157                    if *arg_count > 1 {
158                        push_integer_bigint(&mut local, &BigInt::from(*arg_count));
159                        local.push(0x55); // REVERSEN
160                    }
161                    // NeoVM CALL_L uses a 4-byte signed offset relative to the
162                    // beginning of the CALL_L instruction. We always use the
163                    // wide form for simplicity.
164                    local.push(0x35); // CALL_L
165                    let patch_pos = local.len();
166                    local.extend_from_slice(&[0, 0, 0, 0]);
167                    call_patches.push(CallPatch {
168                        position: patch_pos,
169                        target: name.clone(),
170                    });
171                }
172                ir::Instruction::EmitEvent {
173                    event_index,
174                    arg_count,
175                } => emit_event(&mut local, module, *event_index, *arg_count),
176                ir::Instruction::EmitEventByName { name, arg_count } => {
177                    emit_event_by_name(&mut local, name, *arg_count)
178                }
179                ir::Instruction::Convert { target } => emit_convert(&mut local, *target),
180                ir::Instruction::IsType { target } => emit_is_type(&mut local, *target),
181                ir::Instruction::NewBuffer => emit_new_buffer(&mut local),
182                ir::Instruction::NewArray { .. } => emit_new_array(&mut local),
183                ir::Instruction::ArrayGet => emit_array_get(&mut local),
184                ir::Instruction::ArraySet => emit_array_set(&mut local),
185                ir::Instruction::MemCpy => {
186                    local.push(0x89); // MEMCPY
187                }
188                ir::Instruction::ReverseItems => {
189                    local.push(0xD1); // REVERSEITEMS
190                }
191                ir::Instruction::BitwiseNot => {
192                    local.push(0x90); // INVERT
193                }
194                ir::Instruction::LogicalNot => {
195                    local.push(0xAA); // NOT (logical boolean negation)
196                }
197                ir::Instruction::Try { catch_target } => {
198                    // NeoVM TRY_L uses 4-byte signed offsets (catch, finally) relative to
199                    // the beginning of the TRY_L instruction. We always emit the wide form
200                    // and omit `finally` (offset = 0).
201                    local.push(0x3C); // TRY_L
202                    let position = local.len();
203                    local.extend_from_slice(&[0, 0, 0, 0]); // catch offset placeholder
204                    jump_patches.push((position, *catch_target));
205                    local.extend_from_slice(&[0, 0, 0, 0]); // finally offset (absent)
206                }
207                ir::Instruction::EndTry { target } => {
208                    // NeoVM ENDTRY_L uses a 4-byte signed offset relative to the beginning
209                    // of the ENDTRY_L instruction. We always emit the wide form.
210                    local.push(0x3E); // ENDTRY_L
211                    let position = local.len();
212                    local.extend_from_slice(&[0, 0, 0, 0]);
213                    jump_patches.push((position, *target));
214                }
215                ir::Instruction::Jump { target } => {
216                    // NeoVM JMP_L uses a 4-byte signed offset relative to the
217                    // beginning of the JMP_L instruction. We always use the
218                    // wide form for simplicity.
219                    local.push(0x23); // JMP_L
220                    let position = local.len();
221                    local.extend_from_slice(&[0, 0, 0, 0]);
222                    jump_patches.push((position, *target));
223                }
224                ir::Instruction::JumpIf { target } => {
225                    // IR JumpIf branches when the condition is false.
226                    // NeoVM JMPIFNOT_L uses a 4-byte signed offset relative to the
227                    // beginning of the JMPIFNOT_L instruction.
228                    local.push(0x27); // JMPIFNOT_L
229                    let position = local.len();
230                    local.extend_from_slice(&[0, 0, 0, 0]);
231                    jump_patches.push((position, *target));
232                }
233                ir::Instruction::Label(label) => {
234                    label_offsets.insert(*label, local.len() as u32);
235                }
236                ir::Instruction::AbortMsg => {
237                    local.push(0xE0); // ABORTMSG
238                }
239                ir::Instruction::Abort => {
240                    local.push(0x38); // ABORT
241                }
242                ir::Instruction::Throw => {
243                    local.push(0x3A); // THROW
244                }
245                ir::Instruction::Dup => {
246                    local.push(0x4A); // DUP
247                }
248                ir::Instruction::Swap => {
249                    local.push(0x50); // SWAP
250                }
251            }
252        }
253    }
254
255    for (position, label) in jump_patches {
256        let target_offset = label_offsets
257            .get(&label)
258            .copied()
259            .unwrap_or(local.len() as u32) as i32;
260        // `position` points at the beginning of the 4-byte operand; the opcode
261        // is immediately before it.
262        let opcode_pos = (position - 1) as i32;
263        let relative = target_offset
264            .checked_sub(opcode_pos)
265            .unwrap_or(0);
266        local[position..position + 4].copy_from_slice(&relative.to_le_bytes());
267    }
268
269    (local, call_patches, token_patches)
270}
271
272fn append_default_value(bytecode: &mut Vec<u8>, value_type: &ValueType) {
273    match value_type {
274        ValueType::Integer { .. } => bytecode.push(0x10),
275        // Solidity booleans are represented as 0/1 values on the stack. Use PUSH0 for the
276        // default `false` to match numeric/ABI semantics.
277        ValueType::Boolean => bytecode.push(0x10), // PUSH0
278        ValueType::String => push_data(bytecode, &[]),
279        ValueType::Address => push_data(bytecode, &[0u8; 20]),
280        ValueType::ByteArray { fixed_len } => {
281            if let Some(len) = fixed_len {
282                let zeros = vec![0u8; *len as usize];
283                push_data(bytecode, &zeros);
284            } else {
285                push_data(bytecode, &[]);
286            }
287        }
288        ValueType::Array(_) => bytecode.push(0xC2), // NEWARRAY0
289        ValueType::Mapping { .. } => bytecode.push(0xC8), // NEWMAP
290        ValueType::Struct { .. } => bytecode.push(0xC5), // NEWSTRUCT0
291        ValueType::Any => bytecode.push(0x0B),      // NULL
292    }
293}