1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2#[repr(u8)]
3pub enum Opcode {
4 Nop = 0,
5 End = 1,
6 Return = 2,
7
8 LoadConst = 10,
9 LoadInt = 11,
10 LoadInt8 = 12,
11 LoadTrue = 13,
12 LoadFalse = 14,
13 LoadNull = 15,
14 LoadUndefined = 16,
15 LoadTdz = 17,
16 CheckTdz = 18,
17 CheckRef = 19,
18 CheckObjectCoercible = 91,
19
20 Add = 20,
21 Sub = 21,
22 SubImm8 = 34,
23 AddNum = 35,
24 SubNum = 36,
25 MulNum = 37,
26 DivNum = 38,
27 AddImm8 = 39,
28 Mul = 22,
29 Div = 23,
30 Mod = 24,
31 Pow = 25,
32 Neg = 26,
33 BitAnd = 27,
34 BitOr = 28,
35 BitXor = 29,
36 BitNot = 30,
37 Shl = 31,
38 Shr = 32,
39 UShr = 33,
40
41 Lt = 50,
42 Lte = 51,
43 LteImm8 = 60,
44 Gt = 52,
45 Gte = 53,
46 Eq = 54,
47 Neq = 55,
48 StrictEq = 56,
49 StrictNeq = 57,
50 InstanceOf = 58,
51 NewRegExp = 59,
52
53 Not = 70,
54 TypeOf = 71,
55
56 Jump = 80,
57 JumpIf = 81,
58 JumpIfNot = 82,
59 JumpIfNullish = 176,
60 Throw = 83,
61 Try = 84,
62 Catch = 85,
63 Finally = 86,
64 Jump8 = 87,
65 JumpIf8 = 88,
66 JumpIfNot8 = 89,
67
68 Move = 90,
69
70 GetLocal = 100,
71 SetLocal = 101,
72 GetGlobal = 102,
73 SetGlobal = 103,
74 GetUpvalue = 104,
75 SetUpvalue = 105,
76 IncLocal = 106,
77 DecLocal = 107,
78
79 NewObject = 120,
80 NewArray = 121,
81 GetField = 122,
82 SetField = 123,
83 GetProp = 124,
84 SetProp = 125,
85 Call = 126,
86 CallMethod = 127,
87 NewFunction = 128,
88 CallNew = 129,
89 DeleteProp = 130,
90 HasProperty = 131,
91 GatherRest = 132,
92 ObjectSpread = 133,
93 GetPropertyNames = 134,
94 ArrayExtend = 135,
95 SetProto = 136,
96 GetSuper = 137,
97 ArrayPush = 138,
98 CallSpread = 139,
99 CallMethodSpread = 140,
100 CallNewSpread = 141,
101 GetPrivate = 142,
102 SetPrivate = 143,
103 HasPrivate = 144,
104 Yield = 145,
105 NewGeneratorFunction = 146,
106 NewAsyncFunction = 147,
107 Await = 148,
108 NewAsyncGeneratorFunction = 149,
109 GetIterator = 150,
110 IteratorNext = 151,
111 GetArguments = 152,
112 GetCurrentFunction = 153,
113 CallCurrent = 154,
114 CallCurrent1 = 155,
115 GetNamedProp = 156,
116 SetNamedProp = 157,
117 Call0 = 174,
118 Call1 = 175,
119 Call2 = 177,
120 Call3 = 178,
121 CallNamedMethod = 179,
122
123 MathSin = 180,
124 MathCos = 181,
125 MathSqrt = 182,
126 MathAbs = 183,
127 MathFloor = 184,
128 MathCeil = 185,
129 MathRound = 186,
130 MathPow = 187,
131 MathMin = 188,
132 MathMax = 189,
133
134 DefineAccessor = 190,
135
136 DefinePrivateAccessor = 191,
137
138 SetMethodProp = 192,
139
140 Pos = 193,
141 DefineGlobal = 194,
142 DeleteGlobal = 195,
143 ThrowReferenceError = 196,
144 SetGlobalVar = 197,
145 InitGlobalVar = 198,
146
147 LtJumpIfNot = 158,
148 LtJumpIf = 159,
149 LteJumpIfNot = 160,
150 LteJumpIf = 161,
151 GtJumpIfNot = 162,
152 GtJumpIf = 163,
153 GteJumpIfNot = 164,
154 GteJumpIf = 165,
155 EqJumpIfNot = 166,
156 EqJumpIf = 167,
157 NeqJumpIfNot = 168,
158 NeqJumpIf = 169,
159 StrictEqJumpIfNot = 170,
160 StrictEqJumpIf = 171,
161 StrictNeqJumpIfNot = 172,
162 StrictNeqJumpIf = 173,
163}
164
165impl Opcode {
166 #[inline(always)]
167 pub fn from_u8(v: u8) -> Option<Self> {
168 match v {
169 0 => Some(Opcode::Nop),
170 1 => Some(Opcode::End),
171 2 => Some(Opcode::Return),
172 10 => Some(Opcode::LoadConst),
173 11 => Some(Opcode::LoadInt),
174 12 => Some(Opcode::LoadInt8),
175 13 => Some(Opcode::LoadTrue),
176 14 => Some(Opcode::LoadFalse),
177 15 => Some(Opcode::LoadNull),
178 16 => Some(Opcode::LoadUndefined),
179 17 => Some(Opcode::LoadTdz),
180 18 => Some(Opcode::CheckTdz),
181 19 => Some(Opcode::CheckRef),
182 91 => Some(Opcode::CheckObjectCoercible),
183 20 => Some(Opcode::Add),
184 21 => Some(Opcode::Sub),
185 34 => Some(Opcode::SubImm8),
186 35 => Some(Opcode::AddNum),
187 36 => Some(Opcode::SubNum),
188 37 => Some(Opcode::MulNum),
189 38 => Some(Opcode::DivNum),
190 39 => Some(Opcode::AddImm8),
191 22 => Some(Opcode::Mul),
192 23 => Some(Opcode::Div),
193 24 => Some(Opcode::Mod),
194 25 => Some(Opcode::Pow),
195 26 => Some(Opcode::Neg),
196 27 => Some(Opcode::BitAnd),
197 28 => Some(Opcode::BitOr),
198 29 => Some(Opcode::BitXor),
199 30 => Some(Opcode::BitNot),
200 31 => Some(Opcode::Shl),
201 32 => Some(Opcode::Shr),
202 33 => Some(Opcode::UShr),
203 50 => Some(Opcode::Lt),
204 51 => Some(Opcode::Lte),
205 60 => Some(Opcode::LteImm8),
206 52 => Some(Opcode::Gt),
207 53 => Some(Opcode::Gte),
208 54 => Some(Opcode::Eq),
209 55 => Some(Opcode::Neq),
210 56 => Some(Opcode::StrictEq),
211 57 => Some(Opcode::StrictNeq),
212 58 => Some(Opcode::InstanceOf),
213 59 => Some(Opcode::NewRegExp),
214 70 => Some(Opcode::Not),
215 71 => Some(Opcode::TypeOf),
216 80 => Some(Opcode::Jump),
217 81 => Some(Opcode::JumpIf),
218 82 => Some(Opcode::JumpIfNot),
219 176 => Some(Opcode::JumpIfNullish),
220 83 => Some(Opcode::Throw),
221 84 => Some(Opcode::Try),
222 85 => Some(Opcode::Catch),
223 86 => Some(Opcode::Finally),
224 87 => Some(Opcode::Jump8),
225 88 => Some(Opcode::JumpIf8),
226 89 => Some(Opcode::JumpIfNot8),
227 90 => Some(Opcode::Move),
228 100 => Some(Opcode::GetLocal),
229 101 => Some(Opcode::SetLocal),
230 102 => Some(Opcode::GetGlobal),
231 103 => Some(Opcode::SetGlobal),
232 104 => Some(Opcode::GetUpvalue),
233 105 => Some(Opcode::SetUpvalue),
234 106 => Some(Opcode::IncLocal),
235 107 => Some(Opcode::DecLocal),
236 120 => Some(Opcode::NewObject),
237 121 => Some(Opcode::NewArray),
238 122 => Some(Opcode::GetField),
239 123 => Some(Opcode::SetField),
240 124 => Some(Opcode::GetProp),
241 125 => Some(Opcode::SetProp),
242 126 => Some(Opcode::Call),
243 127 => Some(Opcode::CallMethod),
244 128 => Some(Opcode::NewFunction),
245 129 => Some(Opcode::CallNew),
246 130 => Some(Opcode::DeleteProp),
247 131 => Some(Opcode::HasProperty),
248 132 => Some(Opcode::GatherRest),
249 133 => Some(Opcode::ObjectSpread),
250 134 => Some(Opcode::GetPropertyNames),
251 135 => Some(Opcode::ArrayExtend),
252 136 => Some(Opcode::SetProto),
253 137 => Some(Opcode::GetSuper),
254 138 => Some(Opcode::ArrayPush),
255 139 => Some(Opcode::CallSpread),
256 140 => Some(Opcode::CallMethodSpread),
257 141 => Some(Opcode::CallNewSpread),
258 142 => Some(Opcode::GetPrivate),
259 143 => Some(Opcode::SetPrivate),
260 144 => Some(Opcode::HasPrivate),
261 145 => Some(Opcode::Yield),
262 146 => Some(Opcode::NewGeneratorFunction),
263 147 => Some(Opcode::NewAsyncFunction),
264 148 => Some(Opcode::Await),
265 149 => Some(Opcode::NewAsyncGeneratorFunction),
266 150 => Some(Opcode::GetIterator),
267 151 => Some(Opcode::IteratorNext),
268 152 => Some(Opcode::GetArguments),
269 153 => Some(Opcode::GetCurrentFunction),
270 154 => Some(Opcode::CallCurrent),
271 155 => Some(Opcode::CallCurrent1),
272 156 => Some(Opcode::GetNamedProp),
273 157 => Some(Opcode::SetNamedProp),
274 174 => Some(Opcode::Call0),
275 175 => Some(Opcode::Call1),
276 177 => Some(Opcode::Call2),
277 178 => Some(Opcode::Call3),
278 179 => Some(Opcode::CallNamedMethod),
279 180 => Some(Opcode::MathSin),
280 181 => Some(Opcode::MathCos),
281 182 => Some(Opcode::MathSqrt),
282 183 => Some(Opcode::MathAbs),
283 184 => Some(Opcode::MathFloor),
284 185 => Some(Opcode::MathCeil),
285 186 => Some(Opcode::MathRound),
286 187 => Some(Opcode::MathPow),
287 188 => Some(Opcode::MathMin),
288 189 => Some(Opcode::MathMax),
289 190 => Some(Opcode::DefineAccessor),
290 191 => Some(Opcode::DefinePrivateAccessor),
291 192 => Some(Opcode::SetMethodProp),
292 193 => Some(Opcode::Pos),
293 194 => Some(Opcode::DefineGlobal),
294 195 => Some(Opcode::DeleteGlobal),
295 196 => Some(Opcode::ThrowReferenceError),
296 197 => Some(Opcode::SetGlobalVar),
297 198 => Some(Opcode::InitGlobalVar),
298 158 => Some(Opcode::LtJumpIfNot),
299 159 => Some(Opcode::LtJumpIf),
300 160 => Some(Opcode::LteJumpIfNot),
301 161 => Some(Opcode::LteJumpIf),
302 162 => Some(Opcode::GtJumpIfNot),
303 163 => Some(Opcode::GtJumpIf),
304 164 => Some(Opcode::GteJumpIfNot),
305 165 => Some(Opcode::GteJumpIf),
306 166 => Some(Opcode::EqJumpIfNot),
307 167 => Some(Opcode::EqJumpIf),
308 168 => Some(Opcode::NeqJumpIfNot),
309 169 => Some(Opcode::NeqJumpIf),
310 170 => Some(Opcode::StrictEqJumpIfNot),
311 171 => Some(Opcode::StrictEqJumpIf),
312 172 => Some(Opcode::StrictNeqJumpIfNot),
313 173 => Some(Opcode::StrictNeqJumpIf),
314 _ => None,
315 }
316 }
317
318 #[inline(always)]
319 pub fn from_u8_unchecked(v: u8) -> Self {
320 Self::from_u8(v).unwrap_or(Opcode::Nop)
321 }
322
323 pub fn instruction_size(op: Opcode) -> usize {
324 match op {
325 Opcode::Nop | Opcode::End | Opcode::Catch | Opcode::Finally => 1,
326 Opcode::Return => 3,
327 Opcode::LoadTrue
328 | Opcode::LoadFalse
329 | Opcode::LoadNull
330 | Opcode::LoadUndefined
331 | Opcode::LoadTdz
332 | Opcode::CheckTdz
333 | Opcode::IncLocal
334 | Opcode::DecLocal
335 | Opcode::Throw
336 | Opcode::GatherRest
337 | Opcode::GetSuper
338 | Opcode::Yield
339 | Opcode::NewObject
340 | Opcode::GetArguments
341 | Opcode::GetCurrentFunction
342 | Opcode::CheckObjectCoercible => 3,
343 Opcode::Neg | Opcode::BitNot | Opcode::Not | Opcode::TypeOf | Opcode::Move => 5,
344 Opcode::Add
345 | Opcode::AddNum
346 | Opcode::SubNum
347 | Opcode::MulNum
348 | Opcode::DivNum
349 | Opcode::Sub
350 | Opcode::Mul
351 | Opcode::Div
352 | Opcode::Mod
353 | Opcode::Pow
354 | Opcode::BitAnd
355 | Opcode::BitOr
356 | Opcode::BitXor
357 | Opcode::Shl
358 | Opcode::Shr
359 | Opcode::UShr
360 | Opcode::Lt
361 | Opcode::Lte
362 | Opcode::Gt
363 | Opcode::Gte
364 | Opcode::Eq
365 | Opcode::Neq
366 | Opcode::StrictEq
367 | Opcode::StrictNeq
368 | Opcode::InstanceOf => 7,
369 Opcode::SubImm8 | Opcode::AddImm8 | Opcode::LteImm8 => 6,
370 Opcode::NewRegExp => 11,
371 Opcode::LoadConst | Opcode::LoadInt => 7,
372 Opcode::LoadInt8 => 4,
373 Opcode::Jump => 5,
374 Opcode::JumpIf | Opcode::JumpIfNot | Opcode::JumpIfNullish => 7,
375 Opcode::Jump8 => 2,
376 Opcode::JumpIf8 | Opcode::JumpIfNot8 => 4,
377 Opcode::Try => 9,
378 Opcode::GetLocal
379 | Opcode::SetLocal
380 | Opcode::GetGlobal
381 | Opcode::SetGlobal
382 | Opcode::CheckRef => 7,
383 Opcode::GetUpvalue | Opcode::SetUpvalue => 5,
384 Opcode::NewArray => 5,
385 Opcode::GetField | Opcode::GetProp => 7,
386 Opcode::SetField | Opcode::SetProp => 7,
387 Opcode::GetNamedProp | Opcode::SetNamedProp => 9,
388
389 Opcode::LtJumpIfNot
390 | Opcode::LtJumpIf
391 | Opcode::LteJumpIfNot
392 | Opcode::LteJumpIf
393 | Opcode::GtJumpIfNot
394 | Opcode::GtJumpIf
395 | Opcode::GteJumpIfNot
396 | Opcode::GteJumpIf
397 | Opcode::EqJumpIfNot
398 | Opcode::EqJumpIf
399 | Opcode::NeqJumpIfNot
400 | Opcode::NeqJumpIf
401 | Opcode::StrictEqJumpIfNot
402 | Opcode::StrictEqJumpIf
403 | Opcode::StrictNeqJumpIfNot
404 | Opcode::StrictNeqJumpIf => 9,
405 Opcode::Call | Opcode::CallNew => 7,
406 Opcode::Call0 => 5,
407 Opcode::Call1 => 7,
408 Opcode::Call2 => 9,
409 Opcode::Call3 => 11,
410 Opcode::CallCurrent => 5,
411 Opcode::CallCurrent1 => 5,
412 Opcode::CallMethod => 9,
413 Opcode::CallNamedMethod => 11,
414 Opcode::MathSin
415 | Opcode::MathCos
416 | Opcode::MathSqrt
417 | Opcode::MathAbs
418 | Opcode::MathFloor
419 | Opcode::MathCeil
420 | Opcode::MathRound => 5,
421 Opcode::MathPow | Opcode::MathMin | Opcode::MathMax => 7,
422 Opcode::DefineAccessor => 9,
423 Opcode::DefinePrivateAccessor => 9,
424 Opcode::SetMethodProp => 7,
425 Opcode::Pos => 5,
426 Opcode::DefineGlobal => 7,
427 Opcode::DeleteGlobal => 7,
428 Opcode::ThrowReferenceError => 5,
429 Opcode::SetGlobalVar => 7,
430 Opcode::InitGlobalVar => 7,
431 Opcode::NewFunction => 1,
432 Opcode::DeleteProp => 7,
433 Opcode::HasProperty => 7,
434 Opcode::ObjectSpread => 5,
435 Opcode::GetPropertyNames => 5,
436 Opcode::ArrayExtend => 5,
437 Opcode::SetProto => 5,
438 Opcode::ArrayPush => 5,
439 Opcode::CallSpread => 7,
440 Opcode::CallMethodSpread => 9,
441 Opcode::CallNewSpread => 7,
442 Opcode::GetPrivate => 7,
443 Opcode::SetPrivate => 7,
444 Opcode::HasPrivate => 7,
445 Opcode::NewGeneratorFunction => 1,
446 Opcode::NewAsyncFunction => 1,
447 Opcode::NewAsyncGeneratorFunction => 1,
448 Opcode::Await => 3,
449 Opcode::GetIterator => 5,
450 Opcode::IteratorNext => 7,
451 }
452 }
453}
454
455#[derive(Debug, Clone)]
456pub struct Bytecode {
457 pub code: Vec<u8>,
458 pub constants: Vec<crate::value::JSValue>,
459 pub locals_count: u32,
460 pub param_count: u16,
461 pub line_number_table: Option<crate::compiler::location::LineNumberTable>,
462 pub ic_table: crate::compiler::InlineCacheTable,
463
464 pub shared_ic_table_ptr: *mut crate::compiler::InlineCacheTable,
465
466 pub shared_code_ptr: *const u8,
467 pub shared_code_len: usize,
468 pub shared_const_ptr: *const crate::value::JSValue,
469 pub shared_const_len: usize,
470 pub uses_arguments: bool,
471 pub is_strict: bool,
472 pub var_name_to_slot: std::rc::Rc<Vec<(u32, u16)>>,
473 pub nested_bytecodes: std::collections::HashMap<u32, std::sync::Arc<NestedBytecode>>,
474 pub is_simple_constructor: bool,
475 pub simple_constructor_props: Vec<(crate::runtime::atom::Atom, u16, u16)>,
476 pub cached_constructor_atoms: Vec<crate::runtime::atom::Atom>,
477 pub cached_constructor_final_shape: Option<std::ptr::NonNull<crate::object::shape::Shape>>,
478}
479
480unsafe impl Send for Bytecode {}
481unsafe impl Sync for Bytecode {}
482
483pub struct NestedBytecode {
484 pub code: Vec<u8>,
485 pub constants: Vec<crate::value::JSValue>,
486 pub locals_count: u32,
487 pub param_count: u16,
488 pub uses_arguments: bool,
489 pub is_strict: bool,
490 pub var_name_to_slot: std::rc::Rc<Vec<(u32, u16)>>,
491 pub line_number_table: Option<crate::compiler::location::LineNumberTable>,
492
493 pub parent_bytecode_span: u32,
494
495 pub upvalue_count: u32,
496
497 pub upvalue_descs: Vec<(u32, u32)>,
498
499 pub func_name_atom: u32,
500
501 pub ic_table: std::cell::UnsafeCell<crate::compiler::InlineCacheTable>,
502}
503
504unsafe impl Sync for NestedBytecode {}
505unsafe impl Send for NestedBytecode {}
506
507impl std::fmt::Debug for NestedBytecode {
508 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
509 f.debug_struct("NestedBytecode")
510 .field("code_len", &self.code.len())
511 .field("locals_count", &self.locals_count)
512 .field("param_count", &self.param_count)
513 .finish()
514 }
515}
516
517impl Bytecode {
518 pub fn new() -> Self {
519 Self {
520 code: Vec::new(),
521 constants: Vec::new(),
522 locals_count: 0,
523 param_count: 0,
524 uses_arguments: false,
525 is_strict: false,
526 var_name_to_slot: std::rc::Rc::new(Vec::new()),
527 line_number_table: None,
528 ic_table: crate::compiler::InlineCacheTable::new(),
529 shared_ic_table_ptr: std::ptr::null_mut(),
530 shared_code_ptr: std::ptr::null(),
531 shared_code_len: 0,
532 shared_const_ptr: std::ptr::null(),
533 shared_const_len: 0,
534 nested_bytecodes: std::collections::HashMap::new(),
535 is_simple_constructor: false,
536 simple_constructor_props: Vec::new(),
537 cached_constructor_final_shape: None,
538 cached_constructor_atoms: Vec::new(),
539 }
540 }
541
542 #[inline(always)]
543 pub fn effective_ic_table_ptr(&self) -> *mut crate::compiler::InlineCacheTable {
544 if !self.shared_ic_table_ptr.is_null() {
545 self.shared_ic_table_ptr
546 } else {
547 &self.ic_table as *const crate::compiler::InlineCacheTable
548 as *mut crate::compiler::InlineCacheTable
549 }
550 }
551
552 #[inline(always)]
553 pub fn effective_code_ptr(&self) -> *const u8 {
554 if !self.shared_code_ptr.is_null() {
555 self.shared_code_ptr
556 } else {
557 self.code.as_ptr()
558 }
559 }
560
561 #[inline(always)]
562 pub fn effective_code_len(&self) -> usize {
563 if !self.shared_code_ptr.is_null() {
564 self.shared_code_len
565 } else {
566 self.code.len()
567 }
568 }
569
570 #[inline(always)]
571 pub fn effective_const_ptr(&self) -> *const crate::value::JSValue {
572 if !self.shared_const_ptr.is_null() {
573 self.shared_const_ptr
574 } else {
575 self.constants.as_ptr()
576 }
577 }
578
579 #[inline(always)]
580 pub fn effective_const_len(&self) -> usize {
581 if !self.shared_const_ptr.is_null() {
582 self.shared_const_len
583 } else {
584 self.constants.len()
585 }
586 }
587
588 pub fn disassemble(&self) -> String {
589 use std::fmt::Write;
590 let mut out = String::new();
591 writeln!(out, "=== Register Bytecode ===").unwrap();
592 writeln!(
593 out,
594 "locals_count: {}, param_count: {}",
595 self.locals_count, self.param_count
596 )
597 .unwrap();
598 let mut pc = 0usize;
599 while pc < self.code.len() {
600 let op_val = self.code[pc];
601 let op = Opcode::from_u8_unchecked(op_val);
602 let size = Opcode::instruction_size(op);
603 let remaining = self.code.len().saturating_sub(pc + 1);
604
605 let read_u8 = |offset: usize| -> u8 {
606 if offset <= remaining {
607 self.code.get(pc + offset).copied().unwrap_or(0)
608 } else {
609 0
610 }
611 };
612 let read_i32 = |offset: usize| -> i32 {
613 let bytes = [
614 read_u8(offset),
615 read_u8(offset + 1),
616 read_u8(offset + 2),
617 read_u8(offset + 3),
618 ];
619 i32::from_le_bytes(bytes)
620 };
621 let read_u32 = |offset: usize| -> u32 { read_i32(offset) as u32 };
622 let read_u16 = |offset: usize| -> u16 {
623 u16::from_le_bytes([read_u8(offset), read_u8(offset + 1)])
624 };
625 let read_i8 = |offset: usize| -> i8 { read_u8(offset) as i8 };
626
627 let mut operands = String::new();
628 match op {
629 Opcode::Nop | Opcode::End | Opcode::Catch | Opcode::Finally => {}
630 Opcode::Return
631 | Opcode::LoadTrue
632 | Opcode::LoadFalse
633 | Opcode::LoadNull
634 | Opcode::LoadUndefined
635 | Opcode::IncLocal
636 | Opcode::DecLocal => {
637 let a = read_u16(1);
638 write!(operands, " r{}", a).unwrap();
639 }
640 Opcode::LoadConst => {
641 let dst = read_u16(1);
642 let idx = read_u32(3);
643 write!(operands, " r{}, const[{}]", dst, idx).unwrap();
644 }
645 Opcode::LoadInt => {
646 let dst = read_u16(1);
647 let val = read_i32(3);
648 write!(operands, " r{}, {}", dst, val).unwrap();
649 }
650 Opcode::LoadInt8 => {
651 let dst = read_u16(1);
652 let val = read_u8(3) as i8;
653 write!(operands, " r{}, {}", dst, val).unwrap();
654 }
655 Opcode::Neg
656 | Opcode::BitNot
657 | Opcode::Not
658 | Opcode::TypeOf
659 | Opcode::Throw
660 | Opcode::GatherRest
661 | Opcode::GetSuper
662 | Opcode::Yield
663 | Opcode::NewObject
664 | Opcode::GetArguments
665 | Opcode::GetCurrentFunction => {
666 let a = read_u16(1);
667 write!(operands, " r{}", a).unwrap();
668 }
669 Opcode::Add
670 | Opcode::AddNum
671 | Opcode::SubNum
672 | Opcode::MulNum
673 | Opcode::DivNum
674 | Opcode::Sub
675 | Opcode::Mul
676 | Opcode::Div
677 | Opcode::Mod
678 | Opcode::Pow
679 | Opcode::BitAnd
680 | Opcode::BitOr
681 | Opcode::BitXor
682 | Opcode::Shl
683 | Opcode::Shr
684 | Opcode::UShr
685 | Opcode::Lt
686 | Opcode::Lte
687 | Opcode::Gt
688 | Opcode::Gte
689 | Opcode::Eq
690 | Opcode::Neq
691 | Opcode::StrictEq
692 | Opcode::StrictNeq => {
693 let dst = read_u16(1);
694 let src1 = read_u16(3);
695 let src2 = read_u16(5);
696 write!(operands, " r{}, r{}, r{}", dst, src1, src2).unwrap();
697 }
698 Opcode::InstanceOf => {
699 let dst = read_u16(1);
700 let obj = read_u16(3);
701 let ctor = read_u16(5);
702 write!(operands, " r{}, r{}, r{}", dst, obj, ctor).unwrap();
703 }
704 Opcode::NewRegExp => {
705 let dst = read_u16(1);
706 let pattern_idx = read_u32(3);
707 let flags_idx = read_u32(7);
708 write!(
709 operands,
710 " r{}, pattern={}, flags={}",
711 dst, pattern_idx, flags_idx
712 )
713 .unwrap();
714 }
715 Opcode::SubImm8 | Opcode::AddImm8 | Opcode::LteImm8 => {
716 let dst = read_u16(1);
717 let src = read_u16(3);
718 let imm = read_i8(5);
719 write!(operands, " r{}, r{}, {}", dst, src, imm).unwrap();
720 }
721 Opcode::Jump => {
722 let off = read_i32(1);
723 let size = Opcode::instruction_size(Opcode::Jump) as i32;
724 write!(operands, " {} (pc {})", off, pc as i32 + size + off).unwrap();
725 }
726 Opcode::JumpIf | Opcode::JumpIfNot | Opcode::JumpIfNullish => {
727 let src = read_u16(1);
728 let off = read_i32(3);
729 let size = Opcode::instruction_size(Opcode::JumpIf) as i32;
730 write!(
731 operands,
732 " r{}, {} (pc {})",
733 src,
734 off,
735 pc as i32 + size + off
736 )
737 .unwrap();
738 }
739 Opcode::Jump8 => {
740 let off = read_i8(1);
741 let size = Opcode::instruction_size(Opcode::Jump8) as i32;
742 write!(operands, " {} (pc {})", off, pc as i32 + size + off as i32).unwrap();
743 }
744 Opcode::JumpIf8 | Opcode::JumpIfNot8 => {
745 let src = read_u16(1);
746 let off = read_i8(3);
747 let size = Opcode::instruction_size(Opcode::JumpIf8) as i32;
748 write!(
749 operands,
750 " r{}, {} (pc {})",
751 src,
752 off,
753 pc as i32 + size + off as i32
754 )
755 .unwrap();
756 }
757 Opcode::Try => {
758 let catch = read_i32(1);
759 let finally = read_i32(5);
760 write!(operands, " catch={} finally={}", catch, finally).unwrap();
761 }
762 Opcode::Move => {
763 let dst = read_u16(1);
764 let src = read_u16(3);
765 write!(operands, " r{}, r{}", dst, src).unwrap();
766 }
767 Opcode::GetLocal | Opcode::GetGlobal => {
768 let dst = read_u16(1);
769 let idx = read_u32(3);
770 write!(operands, " r{}, {}", dst, idx).unwrap();
771 }
772 Opcode::SetLocal | Opcode::SetGlobal => {
773 let idx = read_u32(1);
774 let src = read_u16(5);
775 write!(operands, " {}, r{}", idx, src).unwrap();
776 }
777 Opcode::GetUpvalue | Opcode::SetUpvalue => {
778 let a = read_u16(1);
779 let b = read_u16(3);
780 write!(operands, " r{}, r{}", a, b).unwrap();
781 }
782 Opcode::NewArray => {
783 let dst = read_u16(1);
784 let count = read_u16(3);
785 write!(operands, " r{}, {}", dst, count).unwrap();
786 }
787 Opcode::GetField
788 | Opcode::GetProp
789 | Opcode::DeleteProp
790 | Opcode::HasProperty
791 | Opcode::GetPrivate
792 | Opcode::HasPrivate => {
793 let dst = read_u16(1);
794 let obj = read_u16(3);
795 let key = read_u16(5);
796 write!(operands, " r{}, r{}, r{}", dst, obj, key).unwrap();
797 }
798 Opcode::GetNamedProp => {
799 let dst = read_u16(1);
800 let obj = read_u16(3);
801 let atom = read_u32(5);
802 write!(operands, " r{}, r{}, atom({})", dst, obj, atom).unwrap();
803 }
804 Opcode::SetField | Opcode::SetProp | Opcode::SetPrivate => {
805 let obj = read_u16(1);
806 let key = read_u16(3);
807 let val = read_u16(5);
808 write!(operands, " r{}, r{}, r{}", obj, key, val).unwrap();
809 }
810 Opcode::SetNamedProp => {
811 let obj = read_u16(1);
812 let val = read_u16(3);
813 let atom = read_u32(5);
814 write!(operands, " r{}, r{}, atom({})", obj, val, atom).unwrap();
815 }
816 Opcode::LtJumpIfNot
817 | Opcode::LtJumpIf
818 | Opcode::LteJumpIfNot
819 | Opcode::LteJumpIf
820 | Opcode::GtJumpIfNot
821 | Opcode::GtJumpIf
822 | Opcode::GteJumpIfNot
823 | Opcode::GteJumpIf
824 | Opcode::EqJumpIfNot
825 | Opcode::EqJumpIf
826 | Opcode::NeqJumpIfNot
827 | Opcode::NeqJumpIf
828 | Opcode::StrictEqJumpIfNot
829 | Opcode::StrictEqJumpIf
830 | Opcode::StrictNeqJumpIfNot
831 | Opcode::StrictNeqJumpIf => {
832 let a = read_u16(3);
833 let b = read_u16(5);
834 let offset = read_i32(10);
835 let target = (pc as i64 + 14 + offset as i64) as usize;
836 write!(operands, " r{}, r{} -> {}", a, b, target).unwrap();
837 }
838 Opcode::Call | Opcode::CallNew => {
839 let dst = read_u16(1);
840 let func = read_u16(3);
841 let argc = read_u16(5);
842 write!(operands, " r{}, r{}, argc={}", dst, func, argc).unwrap();
843 }
844 Opcode::Call0 => {
845 let dst = read_u16(1);
846 let func = read_u16(3);
847 write!(operands, " r{}, r{}", dst, func).unwrap();
848 }
849 Opcode::Call1 => {
850 let dst = read_u16(1);
851 let func = read_u16(3);
852 let arg = read_u16(5);
853 write!(operands, " r{}, r{}, r{}", dst, func, arg).unwrap();
854 }
855 Opcode::Call2 => {
856 let dst = read_u16(1);
857 let func = read_u16(3);
858 let arg0 = read_u16(5);
859 let arg1 = read_u16(7);
860 write!(operands, " r{}, r{}, r{}, r{}", dst, func, arg0, arg1).unwrap();
861 }
862 Opcode::Call3 => {
863 let dst = read_u16(1);
864 let func = read_u16(3);
865 let arg0 = read_u16(5);
866 let arg1 = read_u16(7);
867 let arg2 = read_u16(9);
868 write!(
869 operands,
870 " r{}, r{}, r{}, r{}, r{}",
871 dst, func, arg0, arg1, arg2
872 )
873 .unwrap();
874 }
875 Opcode::CallCurrent => {
876 let dst = read_u16(1);
877 let argc = read_u16(3);
878 write!(operands, " r{}, argc={}", dst, argc).unwrap();
879 }
880 Opcode::CallCurrent1 => {
881 let dst = read_u16(1);
882 let arg = read_u16(3);
883 write!(operands, " r{}, r{}", dst, arg).unwrap();
884 }
885 Opcode::CallMethod => {
886 let dst = read_u16(1);
887 let obj = read_u16(3);
888 let func = read_u16(5);
889 let argc = read_u16(7);
890 write!(operands, " r{}, r{}, r{}, argc={}", dst, obj, func, argc).unwrap();
891 }
892 Opcode::CallNamedMethod => {
893 let dst = read_u16(1);
894 let obj = read_u16(3);
895 let atom = read_u32(5);
896 let argc = read_u16(9);
897 write!(
898 operands,
899 " r{}, r{}, atom={}, argc={}",
900 dst, obj, atom, argc
901 )
902 .unwrap();
903 }
904 Opcode::NewFunction
905 | Opcode::NewGeneratorFunction
906 | Opcode::NewAsyncFunction
907 | Opcode::NewAsyncGeneratorFunction => {
908 write!(operands, " <embedded>").unwrap();
909 }
910 Opcode::ObjectSpread
911 | Opcode::GetPropertyNames
912 | Opcode::ArrayExtend
913 | Opcode::SetProto
914 | Opcode::ArrayPush => {
915 let a = read_u16(1);
916 let b = read_u16(3);
917 write!(operands, " r{}, r{}", a, b).unwrap();
918 }
919 Opcode::CallSpread | Opcode::CallNewSpread => {
920 let dst = read_u16(1);
921 let func = read_u16(3);
922 let arr = read_u16(5);
923 write!(operands, " r{}, r{}, r{}", dst, func, arr).unwrap();
924 }
925 Opcode::CallMethodSpread => {
926 let dst = read_u16(1);
927 let obj = read_u16(3);
928 let func = read_u16(5);
929 let arr = read_u16(7);
930 write!(operands, " r{}, r{}, r{}, r{}", dst, obj, func, arr).unwrap();
931 }
932 Opcode::MathSin
933 | Opcode::MathCos
934 | Opcode::MathSqrt
935 | Opcode::MathAbs
936 | Opcode::MathFloor
937 | Opcode::MathCeil
938 | Opcode::MathRound => {
939 let dst = read_u16(1);
940 let src = read_u16(3);
941 write!(operands, " r{}, r{}", dst, src).unwrap();
942 }
943 Opcode::MathPow | Opcode::MathMin | Opcode::MathMax => {
944 let dst = read_u16(1);
945 let a = read_u16(3);
946 let b = read_u16(5);
947 write!(operands, " r{}, r{}, r{}", dst, a, b).unwrap();
948 }
949 Opcode::DefineAccessor => {
950 let obj = read_u16(1);
951 let key = read_u16(3);
952 let getter = read_u16(5);
953 let setter = read_u16(7);
954 write!(operands, " r{}, r{}, r{}, r{}", obj, key, getter, setter).unwrap();
955 }
956 Opcode::DefinePrivateAccessor => {
957 let obj = read_u16(1);
958 let key = read_u16(3);
959 let getter = read_u16(5);
960 let setter = read_u16(7);
961 write!(operands, " r{}, r{}, r{}, r{}", obj, key, getter, setter).unwrap();
962 }
963 Opcode::SetMethodProp => {
964 let obj = read_u16(1);
965 let key = read_u16(3);
966 let val = read_u16(5);
967 write!(operands, " r{}, r{}, r{}", obj, key, val).unwrap();
968 }
969 Opcode::Pos => {
970 let dst = read_u16(1);
971 let src = read_u16(3);
972 write!(operands, " r{}, r{}", dst, src).unwrap();
973 }
974 Opcode::DefineGlobal => {
975 let idx = read_u32(1);
976 let src = read_u16(5);
977 write!(operands, " {}, r{}", idx, src).unwrap();
978 }
979 Opcode::DeleteGlobal => {
980 let dst = read_u16(1);
981 let idx = read_u32(3);
982 write!(operands, " r{}, {}", dst, idx).unwrap();
983 }
984 Opcode::ThrowReferenceError => {
985 let idx = read_u32(1);
986 write!(operands, " const[{}]", idx).unwrap();
987 }
988 Opcode::SetGlobalVar => {
989 let idx = read_u32(1);
990 let src = read_u16(5);
991 write!(operands, " {}, r{}", idx, src).unwrap();
992 }
993 Opcode::InitGlobalVar => {
994 let idx = read_u32(1);
995 let src = read_u16(5);
996 write!(operands, " {}, r{}", idx, src).unwrap();
997 }
998 Opcode::Await => {
999 let src = read_u16(1);
1000 write!(operands, " r{}", src).unwrap();
1001 }
1002 Opcode::LoadTdz | Opcode::CheckTdz => {
1003 let a = read_u16(1);
1004 write!(operands, " r{}", a).unwrap();
1005 }
1006 Opcode::CheckRef => {
1007 let a = read_u16(1);
1008 let idx = read_u32(3);
1009 write!(operands, " r{}, const[{}]", a, idx).unwrap();
1010 }
1011 Opcode::CheckObjectCoercible => {
1012 let a = read_u16(1);
1013 write!(operands, " r{}", a).unwrap();
1014 }
1015 Opcode::GetIterator => {
1016 let dst = read_u16(1);
1017 let src = read_u16(3);
1018 write!(operands, " r{}, r{}", dst, src).unwrap();
1019 }
1020 Opcode::IteratorNext => {
1021 let dst_val = read_u16(1);
1022 let dst_done = read_u16(3);
1023 let iter = read_u16(5);
1024 write!(operands, " r{}, r{}, r{}", dst_val, dst_done, iter).unwrap();
1025 }
1026 }
1027 writeln!(out, "{:04} {:?}{}", pc, op, operands).unwrap();
1028 pc += size.max(1);
1029 }
1030 out
1031 }
1032
1033 pub fn serialize(&self) -> Vec<u8> {
1034 let mut out = Vec::new();
1035
1036 out.extend_from_slice(b"SAFC");
1037
1038 out.extend_from_slice(&1u32.to_le_bytes());
1039
1040 out.extend_from_slice(&self.locals_count.to_le_bytes());
1041
1042 out.extend_from_slice(&self.param_count.to_le_bytes());
1043
1044 out.push(self.uses_arguments as u8);
1045
1046 out.extend_from_slice(&(self.code.len() as u64).to_le_bytes());
1047
1048 out.extend_from_slice(&self.code);
1049
1050 out.extend_from_slice(&(self.constants.len() as u64).to_le_bytes());
1051
1052 for c in &self.constants {
1053 out.extend_from_slice(&c.raw_bits().to_le_bytes());
1054 }
1055 out
1056 }
1057
1058 pub fn deserialize(data: &[u8]) -> Result<Self, String> {
1059 if data.len() < 8 {
1060 return Err("Invalid .jsc: too short".to_string());
1061 }
1062 if &data[0..4] != b"SAFC" {
1063 return Err("Invalid .jsc: bad magic".to_string());
1064 }
1065 let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
1066 if version != 1 {
1067 return Err(format!("Unsupported .jsc version: {}", version));
1068 }
1069 let mut offset = 8usize;
1070 let read_u32 = |o: &mut usize| -> u32 {
1071 let v = u32::from_le_bytes([data[*o], data[*o + 1], data[*o + 2], data[*o + 3]]);
1072 *o += 4;
1073 v
1074 };
1075 let read_u16 = |o: &mut usize| -> u16 {
1076 let v = u16::from_le_bytes([data[*o], data[*o + 1]]);
1077 *o += 2;
1078 v
1079 };
1080 let read_u64 = |o: &mut usize| -> u64 {
1081 let v = u64::from_le_bytes([
1082 data[*o],
1083 data[*o + 1],
1084 data[*o + 2],
1085 data[*o + 3],
1086 data[*o + 4],
1087 data[*o + 5],
1088 data[*o + 6],
1089 data[*o + 7],
1090 ]);
1091 *o += 8;
1092 v
1093 };
1094 let locals_count = read_u32(&mut offset);
1095 let param_count = read_u16(&mut offset);
1096 let uses_arguments = data[offset] != 0;
1097 offset += 1;
1098 let code_len = read_u64(&mut offset) as usize;
1099 if offset + code_len > data.len() {
1100 return Err("Invalid .jsc: code truncated".to_string());
1101 }
1102 let code = data[offset..offset + code_len].to_vec();
1103 offset += code_len;
1104 let constants_count = read_u64(&mut offset) as usize;
1105 if offset + constants_count * 8 > data.len() {
1106 return Err("Invalid .jsc: constants truncated".to_string());
1107 }
1108 let mut constants = Vec::with_capacity(constants_count);
1109 for _ in 0..constants_count {
1110 let bits = read_u64(&mut offset);
1111 constants.push(crate::value::JSValue::from_raw_bits(bits));
1112 }
1113 Ok(Self {
1114 code,
1115 constants,
1116 locals_count,
1117 param_count,
1118 uses_arguments,
1119 is_strict: false,
1120 var_name_to_slot: std::rc::Rc::new(Vec::new()),
1121 line_number_table: None,
1122 ic_table: crate::compiler::InlineCacheTable::new(),
1123 shared_ic_table_ptr: std::ptr::null_mut(),
1124 shared_code_ptr: std::ptr::null(),
1125 shared_code_len: 0,
1126 shared_const_ptr: std::ptr::null(),
1127 shared_const_len: 0,
1128 nested_bytecodes: std::collections::HashMap::new(),
1129 is_simple_constructor: false,
1130 simple_constructor_props: Vec::new(),
1131 cached_constructor_final_shape: None,
1132 cached_constructor_atoms: Vec::new(),
1133 })
1134 }
1135}
1136
1137#[cfg(test)]
1138mod tests {
1139 use super::*;
1140
1141 #[test]
1142 fn test_opcode_roundtrip() {
1143 let ops = [
1144 Opcode::Nop,
1145 Opcode::End,
1146 Opcode::Return,
1147 Opcode::LoadConst,
1148 Opcode::LoadInt,
1149 Opcode::LoadTrue,
1150 Opcode::Add,
1151 Opcode::Sub,
1152 Opcode::Mul,
1153 Opcode::Div,
1154 Opcode::Jump,
1155 Opcode::JumpIf,
1156 Opcode::JumpIfNot,
1157 Opcode::GetLocal,
1158 Opcode::SetLocal,
1159 Opcode::IncLocal,
1160 Opcode::DecLocal,
1161 Opcode::Call,
1162 Opcode::CallMethod,
1163 Opcode::NewFunction,
1164 Opcode::JumpIfNullish,
1165 Opcode::Call2,
1166 Opcode::Call3,
1167 Opcode::Try,
1168 ];
1169 for op in ops {
1170 let byte = op as u8;
1171 let recovered = Opcode::from_u8(byte).expect("roundtrip failed");
1172 assert_eq!(op, recovered, "Opcode {:?} did not round-trip", op);
1173 }
1174 }
1175
1176 #[test]
1177 fn test_disassemble_basic_program() {
1178 let bytecode = Bytecode {
1179 code: vec![
1180 Opcode::LoadInt as u8,
1181 0x00,
1182 0x00,
1183 0x0A,
1184 0x00,
1185 0x00,
1186 0x00,
1187 Opcode::LoadInt as u8,
1188 0x01,
1189 0x00,
1190 0x14,
1191 0x00,
1192 0x00,
1193 0x00,
1194 Opcode::Add as u8,
1195 0x02,
1196 0x00,
1197 0x00,
1198 0x00,
1199 0x01,
1200 0x00,
1201 Opcode::Return as u8,
1202 0x02,
1203 0x00,
1204 ],
1205 constants: vec![],
1206 locals_count: 3,
1207 param_count: 0,
1208 line_number_table: None,
1209 ic_table: crate::compiler::InlineCacheTable::new(),
1210 shared_ic_table_ptr: std::ptr::null_mut(),
1211 uses_arguments: false,
1212 is_strict: false,
1213 var_name_to_slot: std::rc::Rc::new(Vec::new()),
1214 nested_bytecodes: std::collections::HashMap::new(),
1215 is_simple_constructor: false,
1216 simple_constructor_props: Vec::new(),
1217 cached_constructor_final_shape: None,
1218 cached_constructor_atoms: Vec::new(),
1219 shared_code_ptr: std::ptr::null(),
1220 shared_code_len: 0,
1221 shared_const_ptr: std::ptr::null(),
1222 shared_const_len: 0,
1223 };
1224
1225 let output = bytecode.disassemble();
1226 assert!(output.contains("=== Register Bytecode ==="));
1227 assert!(
1228 output.contains("LoadInt r0, 10"),
1229 "Basic disasm wrong: {}",
1230 output
1231 );
1232 assert!(
1233 output.contains("LoadInt r1, 20"),
1234 "Basic disasm wrong: {}",
1235 output
1236 );
1237 assert!(
1238 output.contains("Add r2, r0, r1"),
1239 "Basic disasm wrong: {}",
1240 output
1241 );
1242 assert!(output.contains("Return"), "Basic disasm wrong: {}", output);
1243 }
1244
1245 #[test]
1246 fn test_disassemble_jump_offsets() {
1247 let bytecode = Bytecode {
1248 code: vec![
1249 Opcode::LoadFalse as u8,
1250 0x00,
1251 0x00,
1252 Opcode::JumpIfNot as u8,
1253 0x00,
1254 0x00,
1255 0x05,
1256 0x00,
1257 0x00,
1258 0x00,
1259 Opcode::LoadTrue as u8,
1260 0x01,
1261 0x00,
1262 Opcode::Jump as u8,
1263 0x00,
1264 0x00,
1265 0x00,
1266 0x00,
1267 ],
1268 constants: vec![],
1269 locals_count: 1,
1270 param_count: 0,
1271 line_number_table: None,
1272 ic_table: crate::compiler::InlineCacheTable::new(),
1273 shared_ic_table_ptr: std::ptr::null_mut(),
1274 uses_arguments: false,
1275 is_strict: false,
1276 var_name_to_slot: std::rc::Rc::new(Vec::new()),
1277 nested_bytecodes: std::collections::HashMap::new(),
1278 is_simple_constructor: false,
1279 simple_constructor_props: Vec::new(),
1280 cached_constructor_final_shape: None,
1281 cached_constructor_atoms: Vec::new(),
1282 shared_code_ptr: std::ptr::null(),
1283 shared_code_len: 0,
1284 shared_const_ptr: std::ptr::null(),
1285 shared_const_len: 0,
1286 };
1287
1288 let output = bytecode.disassemble();
1289
1290 assert!(
1291 output.contains("JumpIfNot r0, 5 (pc 15)"),
1292 "JumpIfNot disasm wrong: {}",
1293 output
1294 );
1295
1296 assert!(
1297 output.contains("Jump 0 (pc 18)"),
1298 "Jump disasm wrong: {}",
1299 output
1300 );
1301 }
1302
1303 #[test]
1304 fn test_disassemble_call_and_object_ops() {
1305 let bytecode = Bytecode {
1306 code: vec![
1307 Opcode::NewObject as u8,
1308 0x00,
1309 0x00,
1310 Opcode::Call as u8,
1311 0x01,
1312 0x00,
1313 0x02,
1314 0x00,
1315 0x00,
1316 0x00,
1317 Opcode::SetField as u8,
1318 0x00,
1319 0x00,
1320 0x01,
1321 0x00,
1322 0x02,
1323 0x00,
1324 ],
1325 constants: vec![],
1326 locals_count: 3,
1327 param_count: 0,
1328 line_number_table: None,
1329 ic_table: crate::compiler::InlineCacheTable::new(),
1330 shared_ic_table_ptr: std::ptr::null_mut(),
1331 uses_arguments: false,
1332 is_strict: false,
1333 var_name_to_slot: std::rc::Rc::new(Vec::new()),
1334 nested_bytecodes: std::collections::HashMap::new(),
1335 is_simple_constructor: false,
1336 simple_constructor_props: Vec::new(),
1337 cached_constructor_final_shape: None,
1338 cached_constructor_atoms: Vec::new(),
1339 shared_code_ptr: std::ptr::null(),
1340 shared_code_len: 0,
1341 shared_const_ptr: std::ptr::null(),
1342 shared_const_len: 0,
1343 };
1344
1345 let output = bytecode.disassemble();
1346 assert!(
1347 output.contains("NewObject r0"),
1348 "NewObject disasm wrong: {}",
1349 output
1350 );
1351 assert!(
1352 output.contains("Call r1, r2, argc=0"),
1353 "Call disasm wrong: {}",
1354 output
1355 );
1356 assert!(
1357 output.contains("SetField r0, r1, r2"),
1358 "SetField disasm wrong: {}",
1359 output
1360 );
1361 }
1362}