Skip to main content

kv_asm/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenTree;
3use quote::quote;
4
5// Mnemonic inventory for tests: keep in sync with `make_instruction_map` in
6// [anza/sbpf](https://github.com/anza-xyz/sbpf) (`sbpf/src/assembler.rs` in this workspace).
7
8const ASM_OPERAND_KEYWORDS: &[&str] = &[
9    "in",
10    "out",
11    "inout",
12    "lateout",
13    "inlateout",
14    "const",
15    "sym",
16    "options",
17    "clobber_abi",
18];
19
20/// Inline sBPF assembly macro — like `asm!()` but without quotes around instructions.
21///
22/// # Example
23///
24/// ```ignore
25/// use kv_asm::kv_asm;
26///
27/// let ptr = data.as_ptr();
28/// let len = data.len();
29///
30/// kv_asm!(
31///     syscall sol_log_
32///     in("r1") ptr,
33///     in("r2") len,
34///     lateout("r0") _,
35/// );
36/// ```
37///
38/// Expands to:
39/// ```ignore
40/// unsafe {
41///     core::arch::asm!(
42///         "syscall sol_log_",
43///         in("r1") ptr,
44///         in("r2") len,
45///         lateout("r0") _,
46///     )
47/// }
48/// ```
49///
50/// # Without operands
51///
52/// ```ignore
53/// kv_asm!(
54///     mov r0, 0
55///     exit
56/// );
57/// ```
58#[proc_macro]
59pub fn kv_asm(input: TokenStream) -> TokenStream {
60    let input2: proc_macro2::TokenStream = input.into();
61    let tokens: Vec<TokenTree> = input2.into_iter().collect();
62
63    if tokens.is_empty() {
64        return quote! {
65            unsafe {
66                core::arch::asm!("", options(raw))
67            }
68        }
69        .into();
70    }
71
72    let (asm_tokens, operand_tokens) = split_asm_and_operands(&tokens);
73
74    let lines = group_tokens_by_line(&asm_tokens);
75    let asm_strings: Vec<String> = lines
76        .into_iter()
77        .map(|line_tokens| format_instruction(&line_tokens))
78        .filter(|s: &String| !s.is_empty())
79        .collect();
80
81    let operands: proc_macro2::TokenStream = operand_tokens.into_iter().cloned().collect();
82
83    let template = asm_strings.join("\n");
84    let template_lit = proc_macro2::Literal::string(&template);
85
86    let output = if operands.is_empty() {
87        quote! {
88            unsafe {
89                core::arch::asm!(
90                    #template_lit,
91                    options(raw)
92                )
93            }
94        }
95    } else {
96        quote! {
97            unsafe {
98                core::arch::asm!(
99                    #template_lit,
100                    #operands
101                    options(raw)
102                )
103            }
104        }
105    };
106
107    output.into()
108}
109
110/// Like [`kv_asm!`] but expands to [`core::arch::global_asm!`] for module-scope assembly.
111///
112/// Use this to define entire functions (or other top-level symbols) in pure asm:
113///
114/// ```ignore
115/// use kv_asm::kv_global_asm;
116///
117/// kv_global_asm!(
118///     .globl entrypoint
119///     .type entrypoint, @function
120///     entrypoint:
121///     ldxdw r1, [r2 - 8]   // load len from before the data ptr
122///     call process         // process(len, ptr)
123///     exit                 // return r0 to the runtime
124/// );
125/// ```
126///
127/// The token stream is parsed and formatted exactly the same way as [`kv_asm!`], so labels,
128/// memory operands and assembler directives all share one implementation. `sym`/`const`
129/// operands are passed through to `global_asm!` if present.
130#[proc_macro]
131pub fn kv_global_asm(input: TokenStream) -> TokenStream {
132    let input2: proc_macro2::TokenStream = input.into();
133    let tokens: Vec<TokenTree> = input2.into_iter().collect();
134
135    if tokens.is_empty() {
136        return quote! { core::arch::global_asm!(""); }.into();
137    }
138
139    let (asm_tokens, operand_tokens) = split_asm_and_operands(&tokens);
140
141    let lines = group_tokens_by_line(&asm_tokens);
142    let asm_strings: Vec<String> = lines
143        .into_iter()
144        .map(|line_tokens| format_instruction(&line_tokens))
145        .filter(|s: &String| !s.is_empty())
146        .collect();
147
148    let operands: proc_macro2::TokenStream = operand_tokens.into_iter().cloned().collect();
149
150    let template = asm_strings.join("\n");
151    let template_lit = proc_macro2::Literal::string(&template);
152
153    let output = if operands.is_empty() {
154        quote! {
155            core::arch::global_asm!(
156                #template_lit,
157                options(raw)
158            );
159        }
160    } else {
161        quote! {
162            core::arch::global_asm!(
163                #template_lit,
164                #operands
165            );
166        }
167    };
168
169    output.into()
170}
171
172/// Same as `kv_asm!()` but outputs an array of string literals for testing.
173///
174/// # Example
175///
176/// ```ignore
177/// let lines: &[&str] = kv_asm_array!(
178///     mov r0, 0
179///     add r1, r2
180/// );
181/// assert_eq!(lines[0], "mov r0, 0");
182/// ```
183#[proc_macro]
184pub fn kv_asm_array(input: TokenStream) -> TokenStream {
185    let input2: proc_macro2::TokenStream = input.into();
186    let tokens: Vec<TokenTree> = input2.into_iter().collect();
187
188    if tokens.is_empty() {
189        return quote! { &[] as &[&str] }.into();
190    }
191
192    let (asm_tokens, _) = split_asm_and_operands(&tokens);
193
194    let lines = group_tokens_by_line(&asm_tokens);
195    let asm_strings: Vec<String> = lines
196        .into_iter()
197        .map(|line_tokens| format_instruction(&line_tokens))
198        .filter(|s: &String| !s.is_empty())
199        .collect();
200
201    quote! {
202        &[#(#asm_strings),*]
203    }
204    .into()
205}
206
207fn is_asm_operand_keyword(token: &TokenTree) -> bool {
208    if let TokenTree::Ident(ident) = token {
209        let s = ident.to_string();
210        ASM_OPERAND_KEYWORDS.contains(&s.as_str())
211    } else {
212        false
213    }
214}
215
216/// BPF pseudo-C (`r3 = le16 r3`) uses the same `ident =` shape as Rust `asm!` operands.
217/// Treat only non-register idents as the start of the operand list.
218fn is_bpf_plain_reg_ident(ident: &proc_macro2::Ident) -> bool {
219    let s = ident_to_asm(ident);
220    let b = s.as_bytes();
221    if b.len() < 2 {
222        return false;
223    }
224    if b[0] != b'r' && b[0] != b'w' {
225        return false;
226    }
227    b[1..].iter().all(|c| c.is_ascii_digit())
228}
229
230fn is_named_operand_start(tokens: &[TokenTree], start_idx: usize) -> bool {
231    if start_idx + 1 >= tokens.len() {
232        return false;
233    }
234
235    if let TokenTree::Ident(ident) = &tokens[start_idx] {
236        if is_bpf_plain_reg_ident(ident) {
237            return false;
238        }
239        if let TokenTree::Punct(punct) = &tokens[start_idx + 1] {
240            return punct.as_char() == '=';
241        }
242    }
243    false
244}
245
246fn split_asm_and_operands(tokens: &[TokenTree]) -> (Vec<&TokenTree>, Vec<&TokenTree>) {
247    let mut asm_tokens = Vec::new();
248    let mut operand_tokens = Vec::new();
249    let mut in_operands = false;
250    let mut prev_line_num: Option<usize> = None;
251    let mut line_start_idx: Option<usize> = None;
252
253    for (i, token) in tokens.iter().enumerate() {
254        if in_operands {
255            operand_tokens.push(token);
256            continue;
257        }
258
259        let line_num = token.span().start().line;
260        let is_new_line = prev_line_num.map_or(true, |prev| line_num != prev);
261
262        if is_new_line {
263            line_start_idx = Some(i);
264        }
265
266        let at_line_start = line_start_idx == Some(i);
267
268        if at_line_start && is_asm_operand_keyword(token) {
269            in_operands = true;
270            operand_tokens.push(token);
271        } else if at_line_start && is_named_operand_start(tokens, i) {
272            in_operands = true;
273            operand_tokens.push(token);
274        } else {
275            asm_tokens.push(token);
276        }
277
278        prev_line_num = Some(line_num);
279    }
280
281    (asm_tokens, operand_tokens)
282}
283
284fn group_tokens_by_line<'a>(tokens: &[&'a TokenTree]) -> Vec<Vec<&'a TokenTree>> {
285    if tokens.is_empty() {
286        return vec![];
287    }
288
289    let mut lines: Vec<Vec<&'a TokenTree>> = vec![];
290    let mut current_line: Vec<&'a TokenTree> = vec![];
291    let mut prev_line_num: Option<usize> = None;
292
293    for &token in tokens {
294        let span = token.span();
295        let line_num = span.start().line;
296
297        if let Some(prev) = prev_line_num {
298            if line_num != prev && !current_line.is_empty() {
299                lines.push(current_line);
300                current_line = vec![];
301            }
302        }
303
304        current_line.push(token);
305        prev_line_num = Some(line_num);
306    }
307
308    if !current_line.is_empty() {
309        lines.push(current_line);
310    }
311
312    lines
313}
314
315fn format_instruction(tokens: &[&TokenTree]) -> String {
316    let mut parts: Vec<String> = vec![];
317    let mut i = 0;
318
319    while i < tokens.len() {
320        let token = tokens[i];
321        match token {
322            TokenTree::Ident(ident) => {
323                parts.push(ident_to_asm(ident));
324            }
325            TokenTree::Literal(lit) => {
326                parts.push(lit.to_string());
327            }
328            TokenTree::Punct(punct) => {
329                let ch = punct.as_char();
330                match ch {
331                    ',' => {
332                        parts.push(",".to_string());
333                    }
334                    '+' | '-' => {
335                        if i + 1 < tokens.len() {
336                            if let TokenTree::Literal(next_lit) = tokens[i + 1] {
337                                parts.push(format!("{}{}", ch, next_lit));
338                                i += 1;
339                            } else {
340                                parts.push(ch.to_string());
341                            }
342                        } else {
343                            parts.push(ch.to_string());
344                        }
345                    }
346                    '%' => {
347                        if i + 1 < tokens.len() {
348                            if let TokenTree::Punct(next) = tokens[i + 1] {
349                                if next.as_char() == '=' {
350                                    parts.push("%=".to_string());
351                                    i += 1;
352                                } else {
353                                    parts.push('%'.to_string());
354                                }
355                            } else {
356                                parts.push('%'.to_string());
357                            }
358                        } else {
359                            parts.push('%'.to_string());
360                        }
361                    }
362                    // Glue assembler-directive prefixes to the following ident: `.globl`,
363                    // `.text`, `.type`, `@function`, `@object`, etc.
364                    '.' | '@' => {
365                        if i + 1 < tokens.len() {
366                            if let TokenTree::Ident(next_ident) = tokens[i + 1] {
367                                parts.push(format!("{}{}", ch, ident_to_asm(next_ident)));
368                                i += 1;
369                            } else {
370                                parts.push(ch.to_string());
371                            }
372                        } else {
373                            parts.push(ch.to_string());
374                        }
375                    }
376                    _ => {
377                        parts.push(ch.to_string());
378                    }
379                }
380            }
381            TokenTree::Group(group) => {
382                let delimiter = group.delimiter();
383                let inner: Vec<TokenTree> = group.stream().into_iter().collect();
384                let inner_refs: Vec<&TokenTree> = inner.iter().collect();
385                let inner_str = format_memory_operand(&inner_refs);
386
387                match delimiter {
388                    proc_macro2::Delimiter::Bracket => {
389                        parts.push(format!("[{}]", inner_str));
390                    }
391                    proc_macro2::Delimiter::Brace => {
392                        parts.push(format!("{{{}}}", inner_str));
393                    }
394                    proc_macro2::Delimiter::Parenthesis => {
395                        parts.push(format!("({})", inner_str));
396                    }
397                    proc_macro2::Delimiter::None => {
398                        parts.push(inner_str);
399                    }
400                }
401            }
402        }
403        i += 1;
404    }
405
406    join_asm_parts(&parts)
407}
408
409fn format_memory_operand(tokens: &[&TokenTree]) -> String {
410    let mut result = String::new();
411    let mut i = 0;
412
413    while i < tokens.len() {
414        let token = tokens[i];
415        match token {
416            TokenTree::Ident(ident) => {
417                result.push_str(&ident_to_asm(ident));
418            }
419            TokenTree::Literal(lit) => {
420                result.push_str(&lit.to_string());
421            }
422            TokenTree::Punct(punct) => {
423                let ch = punct.as_char();
424                if ch == '+' || ch == '-' {
425                    if i + 1 < tokens.len() {
426                        if let TokenTree::Literal(next_lit) = tokens[i + 1] {
427                            result.push(ch);
428                            result.push_str(&next_lit.to_string());
429                            i += 1;
430                        } else if let TokenTree::Ident(next_ident) = tokens[i + 1] {
431                            result.push(ch);
432                            result.push_str(&next_ident.to_string());
433                            i += 1;
434                        } else {
435                            result.push(ch);
436                        }
437                    } else {
438                        result.push(ch);
439                    }
440                } else {
441                    result.push(ch);
442                }
443            }
444            TokenTree::Group(group) => {
445                let inner: Vec<TokenTree> = group.stream().into_iter().collect();
446                let inner_refs: Vec<&TokenTree> = inner.iter().collect();
447                result.push_str(&format_memory_operand(&inner_refs));
448            }
449        }
450        i += 1;
451    }
452
453    result
454}
455
456fn ident_to_asm(ident: &proc_macro2::Ident) -> String {
457    let s = ident.to_string();
458    s.strip_prefix("r#").map(str::to_string).unwrap_or(s)
459}
460
461fn join_asm_parts(parts: &[String]) -> String {
462    if parts.is_empty() {
463        return String::new();
464    }
465
466    let mut result = String::new();
467    let mut prev_needs_space = false;
468
469    for part in parts {
470        if part == "," {
471            result.push_str(", ");
472            prev_needs_space = false;
473        } else if part.starts_with('[') || part.starts_with('{') || part.starts_with('(') {
474            if prev_needs_space {
475                result.push(' ');
476            }
477            result.push_str(part);
478            prev_needs_space = false;
479        } else if part.starts_with('+') || part.starts_with('-') {
480            if prev_needs_space {
481                result.push(' ');
482            }
483            result.push_str(part);
484            prev_needs_space = true;
485        } else if part == ":" {
486            // BPF labels are `name:` (no space before `:`). If more tokens follow on the same
487            // line (`name: insn`), the space after `:` comes from `prev_needs_space` on the next part.
488            result.push(':');
489            prev_needs_space = true;
490        } else {
491            if prev_needs_space {
492                result.push(' ');
493            }
494            result.push_str(part);
495            prev_needs_space = true;
496        }
497    }
498
499    result
500}
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505    use proc_macro2::TokenStream;
506    use quote::quote;
507
508    fn test_format(input: TokenStream) -> Vec<String> {
509        let tokens: Vec<TokenTree> = input.into_iter().collect();
510        let (asm_tokens, _) = split_asm_and_operands(&tokens);
511        let lines = group_tokens_by_line(&asm_tokens);
512        lines
513            .into_iter()
514            .map(|line_tokens| format_instruction(&line_tokens))
515            .filter(|s| !s.is_empty())
516            .collect()
517    }
518
519    #[test]
520    fn test_simple_exit() {
521        let result = test_format(quote! { exit });
522        assert_eq!(result, vec!["exit"]);
523    }
524
525    #[test]
526    fn test_mov_reg_imm() {
527        let result = test_format(quote! { mov r0, 0 });
528        assert_eq!(result, vec!["mov r0, 0"]);
529    }
530
531    #[test]
532    fn test_add_reg_reg() {
533        let result = test_format(quote! { add r1, r2 });
534        assert_eq!(result, vec!["add r1, r2"]);
535    }
536
537    #[test]
538    fn test_memory_load() {
539        let result = test_format(quote! { ldxb r2, [r1 + 12] });
540        assert_eq!(result, vec!["ldxb r2, [r1+12]"]);
541    }
542
543    #[test]
544    fn test_memory_negative_offset() {
545        let result = test_format(quote! { ldxh r4, [r1 - 8] });
546        assert_eq!(result, vec!["ldxh r4, [r1-8]"]);
547    }
548
549    #[test]
550    fn test_jump_offset() {
551        let result = test_format(quote! { ja +5 });
552        assert_eq!(result, vec!["ja +5"]);
553    }
554
555    #[test]
556    fn test_jump_negative() {
557        let result = test_format(quote! { ja -3 });
558        assert_eq!(result, vec!["ja -3"]);
559    }
560
561    #[test]
562    fn test_conditional_jump() {
563        let result = test_format(quote! { jne r3, 0x8, +37 });
564        assert_eq!(result, vec!["jne r3, 0x8, +37"]);
565    }
566
567    #[test]
568    fn test_hex_immediate() {
569        let result = test_format(quote! { mov r0, 0x1234 });
570        assert_eq!(result, vec!["mov r0, 0x1234"]);
571    }
572
573    #[test]
574    fn test_label_definition_no_space_before_colon() {
575        use proc_macro2::TokenStream;
576
577        let lines: &[TokenStream] = &[
578            quote!(call cal_a),
579            quote!(ja past_cal_a),
580            quote!(cal_a:),
581            quote!(mov64 r0, 0),
582            quote!(exit),
583            quote!(past_cal_a:),
584            quote!(mov64 r1, 1),
585        ];
586        let result: Vec<String> = lines
587            .iter()
588            .flat_map(|ts| test_format(ts.clone()))
589            .collect();
590
591        assert_eq!(
592            result,
593            vec![
594                "call cal_a",
595                "ja past_cal_a",
596                "cal_a:",
597                "mov64 r0, 0",
598                "exit",
599                "past_cal_a:",
600                "mov64 r1, 1",
601            ]
602        );
603    }
604
605    #[test]
606    fn test_label_and_insn_same_line_has_space_after_colon() {
607        let result = test_format(quote!(cal_a: mov64 r0, 0));
608        assert_eq!(result, vec!["cal_a: mov64 r0, 0"]);
609    }
610
611    #[test]
612    fn test_directives_glue_dot_and_at_to_following_ident() {
613        use proc_macro2::TokenStream;
614        let lines: &[TokenStream] = &[
615            quote!(.globl entrypoint),
616            quote!(.type entrypoint, @function),
617            quote!(.section .text),
618            quote!(entrypoint:),
619            quote!(ldxdw r1, [r2 - 8]),
620            quote!(call process),
621            quote!(exit),
622        ];
623        let out: Vec<String> = lines
624            .iter()
625            .flat_map(|ts| test_format(ts.clone()))
626            .collect();
627        assert_eq!(
628            out,
629            vec![
630                ".globl entrypoint",
631                ".type entrypoint, @function",
632                ".section .text",
633                "entrypoint:",
634                "ldxdw r1, [r2-8]",
635                "call process",
636                "exit",
637            ]
638        );
639    }
640
641    /// Every instruction name registered in `make_instruction_map` (anza/sbpf
642    /// `assembler.rs`), with minimal operands so the formatter still emits a valid line.
643    /// `mod` is spelled `r#mod` because `mod` is a Rust keyword.
644    #[test]
645    fn test_sbpf_make_instruction_map_mnemonics() {
646        use proc_macro2::TokenStream;
647
648        // One `quote!` per line so each token carries a distinct `Span` line (a single
649        // `quote! { ... }` block would collapse to one line for `group_tokens_by_line`).
650        let lines: &[TokenStream] = &[
651            quote!(lddw r1, 0x1122334455667788_u64),
652            quote!(ja +1),
653            quote!(syscall sol_log_),
654            quote!(call callee),
655            quote!(callx r5),
656            quote!(exit),
657            quote!(neg r3),
658            quote!(neg32 r3),
659            quote!(neg64 r3),
660            quote!(add r3, r4),
661            quote!(add32 r3, r4),
662            quote!(add64 r3, r4),
663            quote!(sub r3, r4),
664            quote!(sub32 r3, r4),
665            quote!(sub64 r3, r4),
666            quote!(mul r3, r4),
667            quote!(mul32 r3, r4),
668            quote!(mul64 r3, r4),
669            quote!(div r3, r4),
670            quote!(div32 r3, r4),
671            quote!(div64 r3, r4),
672            quote!(or r3, r4),
673            quote!(or32 r3, r4),
674            quote!(or64 r3, r4),
675            quote!(and r3, r4),
676            quote!(and32 r3, r4),
677            quote!(and64 r3, r4),
678            quote!(lsh r3, r4),
679            quote!(lsh32 r3, r4),
680            quote!(lsh64 r3, r4),
681            quote!(rsh r3, r4),
682            quote!(rsh32 r3, r4),
683            quote!(rsh64 r3, r4),
684            quote!(r#mod r3, r4),
685            quote!(mod32 r3, r4),
686            quote!(mod64 r3, r4),
687            quote!(xor r3, r4),
688            quote!(xor32 r3, r4),
689            quote!(xor64 r3, r4),
690            quote!(mov r3, r4),
691            quote!(mov32 r3, r4),
692            quote!(mov64 r3, r4),
693            quote!(arsh r3, r4),
694            quote!(arsh32 r3, r4),
695            quote!(arsh64 r3, r4),
696            quote!(hor r3, r4),
697            quote!(hor32 r3, r4),
698            quote!(hor64 r3, r4),
699            quote!(lmul r3, r4),
700            quote!(lmul32 r3, r4),
701            quote!(lmul64 r3, r4),
702            quote!(uhmul r3, r4),
703            quote!(uhmul64 r3, r4),
704            quote!(shmul r3, r4),
705            quote!(shmul64 r3, r4),
706            quote!(udiv r3, r4),
707            quote!(udiv32 r3, r4),
708            quote!(udiv64 r3, r4),
709            quote!(urem r3, r4),
710            quote!(urem32 r3, r4),
711            quote!(urem64 r3, r4),
712            quote!(sdiv r3, r4),
713            quote!(sdiv32 r3, r4),
714            quote!(sdiv64 r3, r4),
715            quote!(srem r3, r4),
716            quote!(srem32 r3, r4),
717            quote!(srem64 r3, r4),
718            quote!(ldxb r2, [r1 + 0]),
719            quote!(ldxh r2, [r1 + 0]),
720            quote!(ldxw r2, [r1 + 0]),
721            quote!(ldxdw r2, [r1 + 0]),
722            quote!(stb [r10 - 8], 1),
723            quote!(sth [r10 - 8], 1),
724            quote!(stw [r10 - 8], 1),
725            quote!(stdw [r10 - 8], 1),
726            quote!(stxb [r10 - 8], r1),
727            quote!(stxh [r10 - 8], r1),
728            quote!(stxw [r10 - 8], r1),
729            quote!(stxdw [r10 - 8], r1),
730            quote!(jeq r1, r2, +1),
731            quote!(jeq32 r1, r2, +1),
732            quote!(jeq64 r1, r2, +1),
733            quote!(jgt r1, r2, +1),
734            quote!(jgt32 r1, r2, +1),
735            quote!(jgt64 r1, r2, +1),
736            quote!(jge r1, r2, +1),
737            quote!(jge32 r1, r2, +1),
738            quote!(jge64 r1, r2, +1),
739            quote!(jlt r1, r2, +1),
740            quote!(jlt32 r1, r2, +1),
741            quote!(jlt64 r1, r2, +1),
742            quote!(jle r1, r2, +1),
743            quote!(jle32 r1, r2, +1),
744            quote!(jle64 r1, r2, +1),
745            quote!(jset r1, r2, +1),
746            quote!(jset32 r1, r2, +1),
747            quote!(jset64 r1, r2, +1),
748            quote!(jne r1, r2, +1),
749            quote!(jne32 r1, r2, +1),
750            quote!(jne64 r1, r2, +1),
751            quote!(jsgt r1, r2, +1),
752            quote!(jsgt32 r1, r2, +1),
753            quote!(jsgt64 r1, r2, +1),
754            quote!(jsge r1, r2, +1),
755            quote!(jsge32 r1, r2, +1),
756            quote!(jsge64 r1, r2, +1),
757            quote!(jslt r1, r2, +1),
758            quote!(jslt32 r1, r2, +1),
759            quote!(jslt64 r1, r2, +1),
760            quote!(jsle r1, r2, +1),
761            quote!(jsle32 r1, r2, +1),
762            quote!(jsle64 r1, r2, +1),
763            quote!(be16 r2),
764            quote!(be32 r2),
765            quote!(be64 r2),
766            quote!(le16 r2),
767            quote!(le32 r2),
768            quote!(le64 r2),
769        ];
770
771        let out: Vec<String> = lines
772            .iter()
773            .flat_map(|ts| test_format(ts.clone()))
774            .collect();
775
776        assert_eq!(
777            out,
778            vec![
779                "lddw r1, 0x1122334455667788_u64",
780                "ja +1",
781                "syscall sol_log_",
782                "call callee",
783                "callx r5",
784                "exit",
785                "neg r3",
786                "neg32 r3",
787                "neg64 r3",
788                "add r3, r4",
789                "add32 r3, r4",
790                "add64 r3, r4",
791                "sub r3, r4",
792                "sub32 r3, r4",
793                "sub64 r3, r4",
794                "mul r3, r4",
795                "mul32 r3, r4",
796                "mul64 r3, r4",
797                "div r3, r4",
798                "div32 r3, r4",
799                "div64 r3, r4",
800                "or r3, r4",
801                "or32 r3, r4",
802                "or64 r3, r4",
803                "and r3, r4",
804                "and32 r3, r4",
805                "and64 r3, r4",
806                "lsh r3, r4",
807                "lsh32 r3, r4",
808                "lsh64 r3, r4",
809                "rsh r3, r4",
810                "rsh32 r3, r4",
811                "rsh64 r3, r4",
812                "mod r3, r4",
813                "mod32 r3, r4",
814                "mod64 r3, r4",
815                "xor r3, r4",
816                "xor32 r3, r4",
817                "xor64 r3, r4",
818                "mov r3, r4",
819                "mov32 r3, r4",
820                "mov64 r3, r4",
821                "arsh r3, r4",
822                "arsh32 r3, r4",
823                "arsh64 r3, r4",
824                "hor r3, r4",
825                "hor32 r3, r4",
826                "hor64 r3, r4",
827                "lmul r3, r4",
828                "lmul32 r3, r4",
829                "lmul64 r3, r4",
830                "uhmul r3, r4",
831                "uhmul64 r3, r4",
832                "shmul r3, r4",
833                "shmul64 r3, r4",
834                "udiv r3, r4",
835                "udiv32 r3, r4",
836                "udiv64 r3, r4",
837                "urem r3, r4",
838                "urem32 r3, r4",
839                "urem64 r3, r4",
840                "sdiv r3, r4",
841                "sdiv32 r3, r4",
842                "sdiv64 r3, r4",
843                "srem r3, r4",
844                "srem32 r3, r4",
845                "srem64 r3, r4",
846                "ldxb r2, [r1+0]",
847                "ldxh r2, [r1+0]",
848                "ldxw r2, [r1+0]",
849                "ldxdw r2, [r1+0]",
850                "stb [r10-8], 1",
851                "sth [r10-8], 1",
852                "stw [r10-8], 1",
853                "stdw [r10-8], 1",
854                "stxb [r10-8], r1",
855                "stxh [r10-8], r1",
856                "stxw [r10-8], r1",
857                "stxdw [r10-8], r1",
858                "jeq r1, r2, +1",
859                "jeq32 r1, r2, +1",
860                "jeq64 r1, r2, +1",
861                "jgt r1, r2, +1",
862                "jgt32 r1, r2, +1",
863                "jgt64 r1, r2, +1",
864                "jge r1, r2, +1",
865                "jge32 r1, r2, +1",
866                "jge64 r1, r2, +1",
867                "jlt r1, r2, +1",
868                "jlt32 r1, r2, +1",
869                "jlt64 r1, r2, +1",
870                "jle r1, r2, +1",
871                "jle32 r1, r2, +1",
872                "jle64 r1, r2, +1",
873                "jset r1, r2, +1",
874                "jset32 r1, r2, +1",
875                "jset64 r1, r2, +1",
876                "jne r1, r2, +1",
877                "jne32 r1, r2, +1",
878                "jne64 r1, r2, +1",
879                "jsgt r1, r2, +1",
880                "jsgt32 r1, r2, +1",
881                "jsgt64 r1, r2, +1",
882                "jsge r1, r2, +1",
883                "jsge32 r1, r2, +1",
884                "jsge64 r1, r2, +1",
885                "jslt r1, r2, +1",
886                "jslt32 r1, r2, +1",
887                "jslt64 r1, r2, +1",
888                "jsle r1, r2, +1",
889                "jsle32 r1, r2, +1",
890                "jsle64 r1, r2, +1",
891                "be16 r2",
892                "be32 r2",
893                "be64 r2",
894                "le16 r2",
895                "le32 r2",
896                "le64 r2",
897            ]
898        );
899    }
900}