neo_solidity/codegen/impl/node_gen.rs
1impl CodeGenerator {
2 #[allow(clippy::only_used_in_recursion)]
3 fn generate_node(
4 &mut self,
5 node: &AstNode,
6 bytecode: &mut Vec<u8>,
7 functions: &mut Vec<FunctionMeta>,
8 events: &mut Vec<String>,
9 estimated_gas: &mut u64,
10 ) -> Result<(), CompilerError> {
11 match &node.node_type {
12 AstNodeType::Object { statements } | AstNodeType::Block { statements } => {
13 for stmt in statements {
14 self.generate_node(stmt, bytecode, functions, events, estimated_gas)?;
15 }
16 }
17 AstNodeType::Function {
18 name,
19 params,
20 returns,
21 body,
22 } => {
23 // Record the bytecode offset before emitting this function's entry.
24 let raw_offset = bytecode.len();
25 functions.push(FunctionMeta {
26 name: name.clone(),
27 params: params.clone(),
28 returns: returns.clone(),
29 raw_offset,
30 });
31
32 // Function entry
33 bytecode.push(0x0C); // PUSHDATA1
34 bytecode.push(name.len() as u8);
35 bytecode.extend_from_slice(name.as_bytes());
36
37 // Generate function body
38 self.generate_node(body, bytecode, functions, events, estimated_gas)?;
39
40 *estimated_gas += 512; // NeoVM CALL opcode cost
41 }
42 AstNodeType::Assignment { targets, value } => {
43 // Generate value expression first
44 self.generate_node(value, bytecode, functions, events, estimated_gas)?;
45
46 // Store to each target variable using index-based STLOC
47 for target in targets {
48 // Get or create variable index
49 let var_index = match self.get_variable_index(target) {
50 Some(idx) => idx,
51 None => self.register_variable(target),
52 };
53
54 // Emit STLOC with variable index
55 emit_stloc(bytecode, var_index);
56 }
57
58 *estimated_gas += targets.len() as u64 * 8;
59 }
60 AstNodeType::FunctionCall { name, arguments } => {
61 // Generate arguments
62 for arg in arguments {
63 self.generate_node(arg, bytecode, functions, events, estimated_gas)?;
64 }
65
66 // Generate function call based on built-in type
67 match name.as_str() {
68 "add" => {
69 bytecode.push(0x9E); // ADD
70 *estimated_gas += 8; // NeoVM arithmetic cost
71 }
72 "sub" => {
73 bytecode.push(0x9F); // SUB
74 *estimated_gas += 8;
75 }
76 "mul" => {
77 bytecode.push(0xA0); // MUL
78 *estimated_gas += 8;
79 }
80 "div" => {
81 bytecode.push(0xA1); // DIV
82 *estimated_gas += 8;
83 }
84 "mod" => {
85 bytecode.push(0xA2); // MOD
86 *estimated_gas += 8;
87 }
88 "and" => {
89 bytecode.push(0xA3); // AND
90 *estimated_gas += 8;
91 }
92 "or" => {
93 bytecode.push(0xA4); // OR
94 *estimated_gas += 8;
95 }
96 "xor" => {
97 bytecode.push(0xA5); // XOR
98 *estimated_gas += 8;
99 }
100 "shl" => {
101 bytecode.push(0xA8); // SHL
102 *estimated_gas += 8;
103 }
104 "shr" => {
105 bytecode.push(0xA9); // SHR
106 *estimated_gas += 8;
107 }
108 "keccak256" => {
109 // Keccak256 is a CryptoLib native method, route via System.Contract.Call
110 bytecode.push(0x0C); // PUSHDATA1
111 bytecode.push("keccak256".len() as u8);
112 bytecode.extend_from_slice("keccak256".as_bytes());
113 bytecode.push(0x41); // SYSCALL
114 bytecode.extend_from_slice(&interop_id_bytes("System.Contract.Call"));
115 *estimated_gas += 700000; // NeoVM contract call cost for crypto
116 }
117 "sha256" => {
118 bytecode.push(0x0C); // PUSHDATA1
119 bytecode.push("sha256".len() as u8);
120 bytecode.extend_from_slice("sha256".as_bytes());
121 bytecode.push(0x41); // SYSCALL
122 bytecode.extend_from_slice(&interop_id_bytes("System.Contract.Call"));
123 *estimated_gas += 700000;
124 }
125 "sload" => {
126 bytecode.push(0x41); // SYSCALL
127 bytecode.extend_from_slice(&interop_id_bytes("System.Storage.Get"));
128 *estimated_gas += 100; // NeoVM StorageGet cost
129 }
130 "sstore" => {
131 bytecode.push(0x41); // SYSCALL
132 bytecode.extend_from_slice(&interop_id_bytes("System.Storage.Put"));
133 *estimated_gas += 1000; // NeoVM StoragePut cost
134 }
135 _ => {
136 // Generic function call
137 bytecode.push(0x0C); // PUSHDATA1
138 bytecode.push(name.len() as u8);
139 bytecode.extend_from_slice(name.as_bytes());
140 bytecode.push(0x41); // SYSCALL
141 bytecode.extend_from_slice(&interop_id_bytes("System.Contract.Call"));
142 *estimated_gas += 512; // NeoVM contract call cost
143 }
144 }
145 }
146 AstNodeType::Literal { value } => {
147 if let Ok(num) = value.parse::<i64>() {
148 bytecode.extend_from_slice(&crate::codegen_helpers::encode_small_int(num));
149 } else {
150 // String or hex literal
151 let data = if let Some(stripped) = value.strip_prefix("0x") {
152 hex::decode(stripped).unwrap_or_else(|_| value.as_bytes().to_vec())
153 } else {
154 value.as_bytes().to_vec()
155 };
156 bytecode.push(0x0C); // PUSHDATA1
157 bytecode.push(data.len() as u8);
158 bytecode.extend_from_slice(&data);
159 }
160 *estimated_gas += 1; // NeoVM PUSH cost
161 }
162 AstNodeType::Identifier { name } => {
163 // Load variable by index from variable table
164 let var_index = self.get_variable_index(name).unwrap_or(0); // Default to 0 if not found
165
166 // Emit LDLOC with variable index
167 emit_ldloc(bytecode, var_index);
168
169 *estimated_gas += 1;
170 }
171 AstNodeType::If {
172 condition,
173 then_branch,
174 else_branch,
175 } => {
176 // Generate condition
177 self.generate_node(condition, bytecode, functions, events, estimated_gas)?;
178
179 // JMPIFNOT to else/end
180 bytecode.push(0x26); // JMPIFNOT
181 let else_jump_pos = bytecode.len();
182 bytecode.push(0x00); // Jump offset (patched below)
183
184 // Generate then branch
185 self.generate_node(then_branch, bytecode, functions, events, estimated_gas)?;
186
187 if else_branch.is_some() {
188 // JMP to end
189 bytecode.push(0x22); // JMP
190 let end_jump_pos = bytecode.len();
191 bytecode.push(0x00); // Jump offset (patched below)
192
193 // Update else jump offset
194 bytecode[else_jump_pos] = (bytecode.len() - else_jump_pos + 1) as u8;
195
196 // Generate else branch
197 if let Some(else_stmt) = else_branch {
198 self.generate_node(else_stmt, bytecode, functions, events, estimated_gas)?;
199 }
200
201 // Update end jump offset
202 bytecode[end_jump_pos] = (bytecode.len() - end_jump_pos + 1) as u8;
203 } else {
204 // Update else jump offset to end
205 bytecode[else_jump_pos] = (bytecode.len() - else_jump_pos + 1) as u8;
206 }
207
208 *estimated_gas += 8; // NeoVM JMP cost
209 }
210 AstNodeType::For {
211 init,
212 condition,
213 update,
214 body,
215 } => {
216 // Generate initialization
217 if let Some(init_node) = init {
218 self.generate_node(init_node, bytecode, functions, events, estimated_gas)?;
219 }
220
221 let loop_start = bytecode.len();
222
223 // Push loop context for break/continue
224 let loop_end = bytecode.len() + 100; // Placeholder, will be updated
225 self.loop_stack.push(LoopContext {
226 break_target: loop_end,
227 continue_target: loop_start,
228 });
229
230 // Generate condition (Box<AstNode>, not Option)
231 self.generate_node(condition, bytecode, functions, events, estimated_gas)?;
232
233 // JMPIFNOT to end
234 bytecode.push(0x26); // JMPIFNOT
235 let end_jump_pos = bytecode.len();
236 bytecode.push(0x00); // Jump offset (patched below)
237
238 // Generate body (Box<AstNode>, not Option)
239 self.generate_node(body, bytecode, functions, events, estimated_gas)?;
240
241 // Generate update
242 if let Some(update_node) = update {
243 self.generate_node(update_node, bytecode, functions, events, estimated_gas)?;
244 }
245
246 // Pop loop context
247 self.loop_stack.pop();
248
249 // Jump back to condition
250 bytecode.push(0x22); // JMP
251 bytecode.push((loop_start as i32 - bytecode.len() as i32 - 1) as u8);
252
253 // Update end jump offset
254 bytecode[end_jump_pos] = (bytecode.len() - end_jump_pos + 1) as u8;
255
256 *estimated_gas += 100; // Loop overhead
257 }
258 AstNodeType::Leave => {
259 bytecode.push(0x40); // RET
260 *estimated_gas += 1;
261 }
262 AstNodeType::Break => {
263 // Resolve break target from loop stack
264 if let Some(loop_ctx) = self.loop_stack.last() {
265 bytecode.push(0x22); // JMP
266 let offset = loop_ctx.break_target as i32 - bytecode.len() as i32 - 1;
267 bytecode.push(offset as u8);
268 *estimated_gas += 8; // NeoVM JMP cost
269 } else {
270 return Err(CompilerError::CodegenError(
271 "break statement outside of loop".to_string(),
272 ));
273 }
274 }
275 AstNodeType::Continue => {
276 // Resolve continue target from loop stack
277 if let Some(loop_ctx) = self.loop_stack.last() {
278 bytecode.push(0x22); // JMP
279 let offset = loop_ctx.continue_target as i32 - bytecode.len() as i32 - 1;
280 bytecode.push(offset as u8);
281 *estimated_gas += 8; // NeoVM JMP cost
282 } else {
283 return Err(CompilerError::CodegenError(
284 "continue statement outside of loop".to_string(),
285 ));
286 }
287 }
288 _ => {
289 // Handle other node types
290 *estimated_gas += 1;
291 }
292 }
293
294 Ok(())
295 }
296}
297
298/// Emit LDLOC instruction with proper index encoding
299/// NeoVM uses 0x10-0x13 for indices 0-3, or 0x14 + index for larger indices
300fn emit_ldloc(bytecode: &mut Vec<u8>, index: usize) {
301 match index {
302 0 => bytecode.push(0x10), // LDLOC0
303 1 => bytecode.push(0x11), // LDLOC1
304 2 => bytecode.push(0x12), // LDLOC2
305 3 => bytecode.push(0x13), // LDLOC3
306 _ => {
307 bytecode.push(0x14); // LDLOC with index
308 bytecode.push(index as u8);
309 }
310 }
311}
312
313/// Emit STLOC instruction with proper index encoding
314/// NeoVM uses 0x0C-0x0F for indices 0-3, or 0x14 + index for larger indices
315fn emit_stloc(bytecode: &mut Vec<u8>, index: usize) {
316 match index {
317 0 => bytecode.push(0x0C), // STLOC0
318 1 => bytecode.push(0x0D), // STLOC1
319 2 => bytecode.push(0x0E), // STLOC2
320 3 => bytecode.push(0x0F), // STLOC3
321 _ => {
322 bytecode.push(0x14); // STLOC with index
323 bytecode.push(index as u8);
324 }
325 }
326}