runar_compiler_rust/codegen/
emit.rs1use serde::{Deserialize, Serialize};
8
9use super::opcodes::opcode_byte;
10use super::stack::{PushValue, StackMethod, StackOp};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ConstructorSlot {
21 #[serde(rename = "paramIndex")]
22 pub param_index: usize,
23 #[serde(rename = "byteOffset")]
24 pub byte_offset: usize,
25}
26
27#[derive(Debug, Clone)]
33pub struct EmitResult {
34 pub script_hex: String,
35 pub script_asm: String,
36 pub constructor_slots: Vec<ConstructorSlot>,
37}
38
39struct EmitContext {
44 hex_parts: Vec<String>,
45 asm_parts: Vec<String>,
46 byte_length: usize,
47 constructor_slots: Vec<ConstructorSlot>,
48}
49
50impl EmitContext {
51 fn new() -> Self {
52 EmitContext {
53 hex_parts: Vec::new(),
54 asm_parts: Vec::new(),
55 byte_length: 0,
56 constructor_slots: Vec::new(),
57 }
58 }
59
60 fn append_hex(&mut self, hex: &str) {
61 self.byte_length += hex.len() / 2;
62 self.hex_parts.push(hex.to_string());
63 }
64
65 fn emit_opcode(&mut self, name: &str) -> Result<(), String> {
66 let byte = opcode_byte(name)
67 .ok_or_else(|| format!("unknown opcode: {}", name))?;
68 self.append_hex(&format!("{:02x}", byte));
69 self.asm_parts.push(name.to_string());
70 Ok(())
71 }
72
73 fn emit_push(&mut self, value: &PushValue) {
74 let (h, a) = encode_push_value(value);
75 self.append_hex(&h);
76 self.asm_parts.push(a);
77 }
78
79 fn emit_placeholder(&mut self, param_index: usize, _param_name: &str) {
80 let byte_offset = self.byte_length;
81 self.append_hex("00"); self.asm_parts.push("OP_0".to_string());
83 self.constructor_slots.push(ConstructorSlot {
84 param_index,
85 byte_offset,
86 });
87 }
88
89 fn get_hex(&self) -> String {
90 self.hex_parts.join("")
91 }
92
93 fn get_asm(&self) -> String {
94 self.asm_parts.join(" ")
95 }
96}
97
98pub fn encode_script_number(n: i128) -> Vec<u8> {
105 if n == 0 {
106 return Vec::new();
107 }
108
109 let negative = n < 0;
110 let mut abs = if negative { (-n) as u128 } else { n as u128 };
111
112 let mut bytes = Vec::new();
113 while abs > 0 {
114 bytes.push((abs & 0xff) as u8);
115 abs >>= 8;
116 }
117
118 let last_byte = *bytes.last().unwrap();
119 if last_byte & 0x80 != 0 {
120 bytes.push(if negative { 0x80 } else { 0x00 });
121 } else if negative {
122 let len = bytes.len();
123 bytes[len - 1] = last_byte | 0x80;
124 }
125
126 bytes
127}
128
129pub fn encode_push_data(data: &[u8]) -> Vec<u8> {
135 let len = data.len();
136
137 if len == 0 {
138 return vec![0x00]; }
140
141 if len == 1 {
144 let b = data[0];
145 if b >= 1 && b <= 16 {
146 return vec![0x50 + b]; }
148 if b == 0x81 {
149 return vec![0x4f]; }
151 }
152
153 if len <= 75 {
154 let mut result = vec![len as u8];
155 result.extend_from_slice(data);
156 return result;
157 }
158
159 if len <= 255 {
160 let mut result = vec![0x4c, len as u8]; result.extend_from_slice(data);
162 return result;
163 }
164
165 if len <= 65535 {
166 let mut result = vec![0x4d, (len & 0xff) as u8, ((len >> 8) & 0xff) as u8]; result.extend_from_slice(data);
168 return result;
169 }
170
171 let mut result = vec![
173 0x4e,
174 (len & 0xff) as u8,
175 ((len >> 8) & 0xff) as u8,
176 ((len >> 16) & 0xff) as u8,
177 ((len >> 24) & 0xff) as u8,
178 ];
179 result.extend_from_slice(data);
180 result
181}
182
183fn encode_push_value(value: &PushValue) -> (String, String) {
185 match value {
186 PushValue::Bool(b) => {
187 if *b {
188 ("51".to_string(), "OP_TRUE".to_string())
189 } else {
190 ("00".to_string(), "OP_FALSE".to_string())
191 }
192 }
193 PushValue::Int(n) => encode_push_int(*n),
194 PushValue::Bytes(bytes) => {
195 let encoded = encode_push_data(bytes);
196 let h = hex::encode(&encoded);
197 if bytes.is_empty() {
198 (h, "OP_0".to_string())
199 } else {
200 (h, format!("<{}>", hex::encode(bytes)))
201 }
202 }
203 }
204}
205
206pub fn encode_push_int(n: i128) -> (String, String) {
208 if n == 0 {
209 return ("00".to_string(), "OP_0".to_string());
210 }
211
212 if n == -1 {
213 return ("4f".to_string(), "OP_1NEGATE".to_string());
214 }
215
216 if n >= 1 && n <= 16 {
217 let opcode = 0x50 + n as u8;
218 return (format!("{:02x}", opcode), format!("OP_{}", n));
219 }
220
221 let num_bytes = encode_script_number(n);
222 let encoded = encode_push_data(&num_bytes);
223 (hex::encode(&encoded), format!("<{}>", hex::encode(&num_bytes)))
224}
225
226fn emit_stack_op(op: &StackOp, ctx: &mut EmitContext) -> Result<(), String> {
231 match op {
232 StackOp::Push(value) => {
233 ctx.emit_push(value);
234 Ok(())
235 }
236 StackOp::Dup => ctx.emit_opcode("OP_DUP"),
237 StackOp::Swap => ctx.emit_opcode("OP_SWAP"),
238 StackOp::Roll { .. } => ctx.emit_opcode("OP_ROLL"),
239 StackOp::Pick { .. } => ctx.emit_opcode("OP_PICK"),
240 StackOp::Drop => ctx.emit_opcode("OP_DROP"),
241 StackOp::Nip => ctx.emit_opcode("OP_NIP"),
242 StackOp::Over => ctx.emit_opcode("OP_OVER"),
243 StackOp::Rot => ctx.emit_opcode("OP_ROT"),
244 StackOp::Tuck => ctx.emit_opcode("OP_TUCK"),
245 StackOp::Opcode(code) => ctx.emit_opcode(code),
246 StackOp::If {
247 then_ops,
248 else_ops,
249 } => emit_if(then_ops, else_ops, ctx),
250 StackOp::Placeholder {
251 param_index,
252 param_name,
253 } => {
254 ctx.emit_placeholder(*param_index, param_name);
255 Ok(())
256 }
257 }
258}
259
260fn emit_if(
261 then_ops: &[StackOp],
262 else_ops: &[StackOp],
263 ctx: &mut EmitContext,
264) -> Result<(), String> {
265 ctx.emit_opcode("OP_IF")?;
266
267 for op in then_ops {
268 emit_stack_op(op, ctx)?;
269 }
270
271 if !else_ops.is_empty() {
272 ctx.emit_opcode("OP_ELSE")?;
273 for op in else_ops {
274 emit_stack_op(op, ctx)?;
275 }
276 }
277
278 ctx.emit_opcode("OP_ENDIF")
279}
280
281pub fn emit(methods: &[StackMethod]) -> Result<EmitResult, String> {
297 let mut ctx = EmitContext::new();
298
299 let public_methods: Vec<StackMethod> = methods
301 .iter()
302 .filter(|m| m.name != "constructor")
303 .cloned()
304 .collect();
305
306 if public_methods.is_empty() {
307 return Ok(EmitResult {
308 script_hex: String::new(),
309 script_asm: String::new(),
310 constructor_slots: Vec::new(),
311 });
312 }
313
314 if public_methods.len() == 1 {
315 for op in &public_methods[0].ops {
316 emit_stack_op(op, &mut ctx)?;
317 }
318 } else {
319 let refs: Vec<&StackMethod> = public_methods.iter().collect();
320 emit_method_dispatch(&refs, &mut ctx)?;
321 }
322
323 Ok(EmitResult {
324 script_hex: ctx.get_hex(),
325 script_asm: ctx.get_asm(),
326 constructor_slots: ctx.constructor_slots,
327 })
328}
329
330fn emit_method_dispatch(
331 methods: &[&StackMethod],
332 ctx: &mut EmitContext,
333) -> Result<(), String> {
334 for (i, method) in methods.iter().enumerate() {
335 let is_last = i == methods.len() - 1;
336
337 if !is_last {
338 ctx.emit_opcode("OP_DUP")?;
339 ctx.emit_push(&PushValue::Int(i as i128));
340 ctx.emit_opcode("OP_NUMEQUAL")?;
341 ctx.emit_opcode("OP_IF")?;
342 ctx.emit_opcode("OP_DROP")?;
343 } else {
344 ctx.emit_opcode("OP_DROP")?;
345 }
346
347 for op in &method.ops {
348 emit_stack_op(op, ctx)?;
349 }
350
351 if !is_last {
352 ctx.emit_opcode("OP_ELSE")?;
353 }
354 }
355
356 for _ in 0..methods.len() - 1 {
358 ctx.emit_opcode("OP_ENDIF")?;
359 }
360
361 Ok(())
362}
363
364pub fn emit_method(method: &StackMethod) -> Result<EmitResult, String> {
366 let mut ctx = EmitContext::new();
367 for op in &method.ops {
368 emit_stack_op(op, &mut ctx)?;
369 }
370 Ok(EmitResult {
371 script_hex: ctx.get_hex(),
372 script_asm: ctx.get_asm(),
373 constructor_slots: ctx.constructor_slots,
374 })
375}
376
377#[cfg(test)]
382mod tests {
383 use super::*;
384
385 #[test]
386 fn test_emit_placeholder_produces_constructor_slot() {
387 let method = StackMethod {
388 name: "unlock".to_string(),
389 ops: vec![StackOp::Placeholder {
390 param_index: 0,
391 param_name: "pubKeyHash".to_string(),
392 }],
393 max_stack_depth: 1,
394 };
395
396 let result = emit_method(&method).expect("emit should succeed");
397 assert_eq!(
398 result.constructor_slots.len(),
399 1,
400 "should produce exactly one constructor slot"
401 );
402 assert_eq!(result.constructor_slots[0].param_index, 0);
403 assert_eq!(result.constructor_slots[0].byte_offset, 0);
404 }
405
406 #[test]
407 fn test_multiple_placeholders_produce_distinct_byte_offsets() {
408 let method = StackMethod {
409 name: "test".to_string(),
410 ops: vec![
411 StackOp::Placeholder {
412 param_index: 0,
413 param_name: "a".to_string(),
414 },
415 StackOp::Placeholder {
416 param_index: 1,
417 param_name: "b".to_string(),
418 },
419 ],
420 max_stack_depth: 2,
421 };
422
423 let result = emit_method(&method).expect("emit should succeed");
424 assert_eq!(
425 result.constructor_slots.len(),
426 2,
427 "should produce two constructor slots"
428 );
429
430 assert_eq!(result.constructor_slots[0].param_index, 0);
432 assert_eq!(result.constructor_slots[0].byte_offset, 0);
433
434 assert_eq!(result.constructor_slots[1].param_index, 1);
436 assert_eq!(result.constructor_slots[1].byte_offset, 1);
437
438 assert_ne!(
440 result.constructor_slots[0].byte_offset,
441 result.constructor_slots[1].byte_offset
442 );
443 }
444
445 #[test]
446 fn test_placeholder_byte_offset_position_is_op_0() {
447 let method = StackMethod {
448 name: "test".to_string(),
449 ops: vec![
450 StackOp::Push(PushValue::Int(42)), StackOp::Placeholder {
452 param_index: 0,
453 param_name: "x".to_string(),
454 },
455 ],
456 max_stack_depth: 2,
457 };
458
459 let result = emit_method(&method).expect("emit should succeed");
460 assert_eq!(result.constructor_slots.len(), 1);
461
462 let slot = &result.constructor_slots[0];
463 let hex = &result.script_hex;
464
465 let byte_hex = &hex[slot.byte_offset * 2..slot.byte_offset * 2 + 2];
467 assert_eq!(
468 byte_hex, "00",
469 "expected OP_0 at placeholder byte offset {}, got '{}' in hex '{}'",
470 slot.byte_offset, byte_hex, hex
471 );
472 }
473
474 #[test]
475 fn test_emit_single_method_produces_hex_and_asm() {
476 use super::super::optimizer::optimize_stack_ops;
477
478 let method = StackMethod {
479 name: "check".to_string(),
480 ops: vec![
481 StackOp::Push(PushValue::Int(42)),
482 StackOp::Opcode("OP_NUMEQUAL".to_string()),
483 StackOp::Opcode("OP_VERIFY".to_string()),
484 ],
485 max_stack_depth: 1,
486 };
487
488 let optimized_method = StackMethod {
490 name: method.name.clone(),
491 ops: optimize_stack_ops(&method.ops),
492 max_stack_depth: method.max_stack_depth,
493 };
494
495 let result = emit(&[optimized_method]).expect("emit should succeed");
496 assert!(!result.script_hex.is_empty(), "hex should not be empty");
497 assert!(!result.script_asm.is_empty(), "asm should not be empty");
498 assert!(
499 result.script_asm.contains("OP_NUMEQUALVERIFY"),
500 "standalone peephole optimizer should combine OP_NUMEQUAL + OP_VERIFY into OP_NUMEQUALVERIFY, got: {}",
501 result.script_asm
502 );
503 }
504
505 #[test]
506 fn test_emit_empty_methods_produces_empty_output() {
507 let result = emit(&[]).expect("emit with no methods should succeed");
508 assert!(
509 result.script_hex.is_empty(),
510 "empty methods should produce empty hex"
511 );
512 assert!(
513 result.constructor_slots.is_empty(),
514 "empty methods should produce no constructor slots"
515 );
516 }
517
518 #[test]
519 fn test_emit_push_bool_values() {
520 let method = StackMethod {
521 name: "test".to_string(),
522 ops: vec![
523 StackOp::Push(PushValue::Bool(true)),
524 StackOp::Push(PushValue::Bool(false)),
525 ],
526 max_stack_depth: 2,
527 };
528
529 let result = emit_method(&method).expect("emit should succeed");
530 assert!(
532 result.script_hex.starts_with("51"),
533 "true should emit 0x51, got: {}",
534 result.script_hex
535 );
536 assert!(
537 result.script_hex.ends_with("00"),
538 "false should emit 0x00, got: {}",
539 result.script_hex
540 );
541 assert!(result.script_asm.contains("OP_TRUE"));
542 assert!(result.script_asm.contains("OP_FALSE"));
543 }
544}