polkavm_common/
assembler.rs

1use crate::program::{Instruction, InstructionSetKind, RawReg, Reg};
2use crate::utils::{parse_imm, parse_immediate, parse_reg, parse_slice, ParsedImmediate};
3use alloc::borrow::ToOwned;
4use alloc::collections::BTreeMap;
5use alloc::format;
6use alloc::string::String;
7use alloc::vec::Vec;
8
9fn split<'a>(text: &'a str, separator: &str) -> Option<(&'a str, &'a str)> {
10    let index = text.find(separator)?;
11    Some((text[..index].trim(), text[index + separator.len()..].trim()))
12}
13
14fn parse_reg_or_imm(text: &str) -> Option<RegImm> {
15    if let Some(value) = parse_imm(text) {
16        Some(RegImm::Imm(value))
17    } else {
18        parse_reg(text).map(RegImm::Reg)
19    }
20}
21
22fn parse_absolute_memory_access(text: &str) -> Option<i32> {
23    let text = text.trim().strip_prefix('[')?.strip_suffix(']')?;
24    parse_imm(text)
25}
26
27fn parse_indirect_memory_access(text: &str) -> Option<(Reg, i32)> {
28    let text = text.trim().strip_prefix('[')?.strip_suffix(']')?;
29    if let Some(index) = text.find('+') {
30        let reg = parse_reg(text[..index].trim())?;
31        let offset = parse_imm(&text[index + 1..])?;
32        Some((reg, offset))
33    } else {
34        parse_reg(text).map(|reg| (reg, 0))
35    }
36}
37
38/// Parses the long form of load_imm_and_jump_indirect:
39/// `tmp = {base}, {dst} = {value}, jump [tmp + {offset}]`, where `dest == base` is allowed
40fn parse_load_imm_and_jump_indirect_with_tmp(line: &str) -> Option<(Reg, Reg, i32, i32)> {
41    let line = line.trim().strip_prefix("tmp")?;
42    if !line.starts_with('=') && line.trim_start() == line {
43        return None;
44    }
45    let line = line.trim().strip_prefix('=')?;
46
47    let index = line.find(',')?;
48    let base = parse_reg(line[..index].trim())?;
49    let line = line[index + 1..].trim();
50
51    let index = line.find('=')?;
52    let dst = parse_reg(line[..index].trim())?;
53    let line = line[index + 1..].trim();
54
55    let index = line.find(',')?;
56    let value = parse_imm(line[..index].trim())?;
57    let line = line[index + 1..].trim().strip_prefix("jump")?;
58    let text = line.trim().strip_prefix('[')?.strip_suffix(']')?;
59
60    if let Some(index) = text.find('+') {
61        if text[..index].trim() != "tmp" {
62            return None;
63        }
64        let offset = parse_imm(&text[index + 1..])?;
65        Some((dst, base, value, offset))
66    } else {
67        if text.trim() != "tmp" {
68            return None;
69        }
70        Some((dst, base, value, 0))
71    }
72}
73
74#[derive(Copy, Clone)]
75pub enum OpMarker {
76    I32,
77    NONE,
78}
79
80#[derive(Copy, Clone)]
81pub enum LoadKind {
82    I8,
83    I16,
84    I32,
85    U8,
86    U16,
87    U32,
88    U64,
89}
90
91#[derive(Copy, Clone)]
92pub enum StoreKind {
93    U8,
94    U16,
95    U32,
96    U64,
97}
98
99#[derive(Copy, Clone)]
100enum ConditionKind {
101    Eq,
102    NotEq,
103    LessSigned,
104    LessUnsigned,
105    LessOrEqualSigned,
106    LessOrEqualUnsigned,
107    GreaterSigned,
108    GreaterUnsigned,
109    GreaterOrEqualSigned,
110    GreaterOrEqualUnsigned,
111}
112
113impl ConditionKind {
114    fn reverse_operands(self) -> Self {
115        match self {
116            Self::Eq => Self::Eq,
117            Self::NotEq => Self::NotEq,
118            Self::LessSigned => Self::GreaterSigned,
119            Self::LessUnsigned => Self::GreaterUnsigned,
120            Self::LessOrEqualSigned => Self::GreaterOrEqualSigned,
121            Self::LessOrEqualUnsigned => Self::GreaterOrEqualUnsigned,
122            Self::GreaterSigned => Self::LessSigned,
123            Self::GreaterUnsigned => Self::LessUnsigned,
124            Self::GreaterOrEqualSigned => Self::LessOrEqualSigned,
125            Self::GreaterOrEqualUnsigned => Self::LessOrEqualUnsigned,
126        }
127    }
128}
129
130#[derive(Copy, Clone)]
131enum RegImm {
132    Reg(Reg),
133    Imm(i32),
134}
135
136#[derive(Copy, Clone)]
137struct Condition {
138    kind: ConditionKind,
139    lhs: RegImm,
140    rhs: RegImm,
141}
142
143fn parse_condition(text: &str) -> Option<Condition> {
144    let text = text.trim();
145    let (lhs, text) = split(text, " ")?;
146    let lhs = parse_reg_or_imm(lhs)?;
147    let (kind, text) = split(text, " ")?;
148    let kind = match kind {
149        "<u" => ConditionKind::LessUnsigned,
150        "<s" => ConditionKind::LessSigned,
151        "<=u" => ConditionKind::LessOrEqualUnsigned,
152        "<=s" => ConditionKind::LessOrEqualSigned,
153        ">u" => ConditionKind::GreaterUnsigned,
154        ">s" => ConditionKind::GreaterSigned,
155        ">=u" => ConditionKind::GreaterOrEqualUnsigned,
156        ">=s" => ConditionKind::GreaterOrEqualSigned,
157        "==" => ConditionKind::Eq,
158        "!=" => ConditionKind::NotEq,
159        _ => return None,
160    };
161
162    let rhs = parse_reg_or_imm(text)?;
163    Some(Condition { kind, lhs, rhs })
164}
165
166pub fn assemble(mut isa: Option<InstructionSetKind>, code: &str) -> Result<Vec<u8>, String> {
167    enum MaybeInstruction {
168        Instruction(Instruction),
169        Jump(String),
170        Branch(String, ConditionKind, Reg, Reg),
171        BranchImm(String, ConditionKind, Reg, i32),
172        LoadLabelAddress(Reg, String),
173        LoadImmAndJump(Reg, u32, String),
174    }
175
176    impl MaybeInstruction {
177        fn starts_new_basic_block(&self) -> bool {
178            match self {
179                MaybeInstruction::Instruction(instruction) => instruction.starts_new_basic_block(),
180                MaybeInstruction::Jump(..)
181                | MaybeInstruction::Branch(..)
182                | MaybeInstruction::BranchImm(..)
183                | MaybeInstruction::LoadImmAndJump(..) => true,
184                MaybeInstruction::LoadLabelAddress(..) => false,
185            }
186        }
187    }
188
189    impl From<Instruction> for MaybeInstruction {
190        fn from(inst: Instruction) -> Self {
191            MaybeInstruction::Instruction(inst)
192        }
193    }
194
195    enum Export {
196        ByBlock(u32),
197        ByInstruction(u32),
198    }
199
200    let mut instructions: Vec<MaybeInstruction> = Vec::new();
201    let mut label_to_index = BTreeMap::new();
202    let mut at_block_start = true;
203    let mut current_basic_block = 0;
204    let mut exports = BTreeMap::new();
205    let mut ro_data = Vec::new();
206    let mut rw_data = Vec::new();
207    let mut ro_data_size = 0;
208    let mut rw_data_size = 0;
209    let mut stack_size = 0;
210
211    macro_rules! emit_and_continue {
212        ($instruction:expr) => {{
213            let instruction: MaybeInstruction = $instruction.into();
214            at_block_start = instruction.starts_new_basic_block();
215            if at_block_start {
216                current_basic_block += 1;
217            }
218
219            instructions.push(instruction);
220            continue;
221        }};
222    }
223
224    for (nth_line, line) in code.lines().enumerate() {
225        let nth_line = nth_line + 1; // Line counter for error messages starts as 1.
226        let line = line.trim();
227        let original_line = line;
228
229        if line.is_empty() || line.starts_with("//") {
230            continue;
231        }
232
233        if let Some(line) = line.strip_prefix("%ro_data_size = ") {
234            let line = line.trim();
235            let Ok(size) = line.parse::<u32>() else {
236                return Err(format!("cannot parse line {nth_line}"));
237            };
238            ro_data_size = size;
239            continue;
240        }
241
242        if let Some(line) = line.strip_prefix("%rw_data_size = ") {
243            let line = line.trim();
244            let Ok(size) = line.parse::<u32>() else {
245                return Err(format!("cannot parse line {nth_line}"));
246            };
247            rw_data_size = size;
248            continue;
249        }
250
251        if let Some(line) = line.strip_prefix("%stack_size = ") {
252            let line = line.trim();
253            let Ok(size) = line.parse::<u32>() else {
254                return Err(format!("cannot parse line {nth_line}"));
255            };
256            stack_size = size;
257            continue;
258        }
259
260        if let Some(line) = line.strip_prefix("%ro_data = ") {
261            let Some(value) = parse_slice(line) else {
262                return Err(format!("cannot parse line {nth_line}"));
263            };
264
265            ro_data = value;
266            continue;
267        }
268
269        if let Some(line) = line.strip_prefix("%rw_data = ") {
270            let Some(value) = parse_slice(line) else {
271                return Err(format!("cannot parse line {nth_line}"));
272            };
273
274            rw_data = value;
275            continue;
276        }
277
278        if let Some(line) = line.strip_prefix("%isa = ") {
279            isa = Some(match line.trim() {
280                "revive_v1" => InstructionSetKind::ReviveV1,
281                "jam_v1" => InstructionSetKind::JamV1,
282                "latest32" => InstructionSetKind::Latest32,
283                "latest64" => InstructionSetKind::Latest64,
284                _ => return Err(format!("cannot parse line {nth_line}")),
285            });
286            continue;
287        }
288
289        if let Some((is_export, mut line)) = line
290            .strip_prefix("pub @")
291            .map(|line| (true, line))
292            .or_else(|| line.strip_prefix('@').map(|line| (false, line)))
293        {
294            let mut no_fallthrough = false;
295            if let Some(line_no_fallthrough) = line.strip_suffix("%no_fallthrough") {
296                no_fallthrough = true;
297                line = line_no_fallthrough.trim();
298            }
299
300            if let Some(label) = line.strip_suffix(':') {
301                if !at_block_start && !no_fallthrough {
302                    instructions.push(Instruction::fallthrough.into());
303                    at_block_start = true;
304                    current_basic_block += 1;
305                }
306
307                if label_to_index.insert(label, current_basic_block).is_some() {
308                    return Err(format!("duplicate label \"{label}\" on line {nth_line}"));
309                }
310
311                if is_export {
312                    if at_block_start {
313                        exports.insert(label, Export::ByBlock(current_basic_block));
314                    } else {
315                        exports.insert(label, Export::ByInstruction(instructions.len() as u32));
316                    }
317                }
318
319                continue;
320            }
321        }
322
323        if line == "trap" {
324            emit_and_continue!(Instruction::trap);
325        }
326
327        if line == "fallthrough" {
328            emit_and_continue!(Instruction::fallthrough);
329        }
330
331        if line == "unlikely" {
332            emit_and_continue!(Instruction::unlikely);
333        }
334
335        if line == "ret" {
336            emit_and_continue!(Instruction::jump_indirect(Reg::RA.into(), 0));
337        }
338
339        if line == "nop" {
340            emit_and_continue!(Instruction::move_reg(Reg::RA.into(), Reg::RA.into()));
341        }
342
343        if let Some(line) = line.strip_prefix("ecalli ") {
344            let line = line.trim();
345            if let Ok(index) = line.parse::<u32>() {
346                emit_and_continue!(Instruction::ecalli(index));
347            }
348        }
349
350        if let Some(line) = line.strip_prefix("jump ") {
351            let line = line.trim();
352            if let Some(line) = line.strip_prefix('@') {
353                if let Some(index) = line.find(' ') {
354                    let label = &line[..index];
355                    let line = &line[index + 1..].trim();
356                    let Some(line) = line.strip_prefix("if ") else {
357                        return Err(format!("cannot parse line {nth_line}: \"{original_line}\""));
358                    };
359
360                    let line = line.trim();
361                    let Some(condition) = parse_condition(line) else {
362                        return Err(format!("cannot parse line {nth_line}: invalid condition"));
363                    };
364
365                    let (kind, lhs, rhs) = match (condition.lhs, condition.rhs) {
366                        (RegImm::Reg(lhs), RegImm::Reg(rhs)) => {
367                            emit_and_continue!(MaybeInstruction::Branch(label.to_owned(), condition.kind, lhs, rhs));
368                        }
369                        (RegImm::Reg(lhs), RegImm::Imm(rhs)) => (condition.kind, lhs, rhs),
370                        (RegImm::Imm(lhs), RegImm::Reg(rhs)) => (condition.kind.reverse_operands(), rhs, lhs),
371                        (RegImm::Imm(_), RegImm::Imm(_)) => {
372                            return Err(format!("cannot parse line {nth_line}: both arguments cannot be immediates"));
373                        }
374                    };
375
376                    emit_and_continue!(MaybeInstruction::BranchImm(label.to_owned(), kind, lhs, rhs));
377                }
378
379                emit_and_continue!(MaybeInstruction::Jump(line.to_owned()));
380            }
381
382            if let Some((base, offset)) = parse_indirect_memory_access(line) {
383                emit_and_continue!(Instruction::jump_indirect(base.into(), offset as u32));
384            }
385        }
386
387        if let Some((dst, base, value, offset)) = parse_load_imm_and_jump_indirect_with_tmp(line) {
388            emit_and_continue!(Instruction::load_imm_and_jump_indirect(
389                dst.into(),
390                base.into(),
391                value as u32,
392                offset as u32
393            ));
394        }
395
396        if let Some(index) = line.find('=') {
397            let lhs = line[..index].trim();
398            let rhs = line[index + 1..].trim();
399
400            let (op_marker, lhs) = if let Some(lhs) = lhs.strip_prefix("i32 ") {
401                (OpMarker::I32, lhs)
402            } else {
403                (OpMarker::NONE, lhs)
404            };
405
406            if let Some(dst) = parse_reg(lhs) {
407                if let Some(index) = rhs.find(',') {
408                    if let Some(value) = parse_immediate(&rhs[..index]).and_then(|value| value.try_into().ok()) {
409                        if let Some(line) = rhs[index + 1..].trim().strip_prefix("jump") {
410                            if let Some(label) = line.trim().strip_prefix('@') {
411                                emit_and_continue!(MaybeInstruction::LoadImmAndJump(dst, value, label.to_owned()));
412                            }
413                            if let Some((base, offset)) = parse_indirect_memory_access(line) {
414                                let instruction = Instruction::load_imm_and_jump_indirect(dst.into(), base.into(), value, offset as u32);
415
416                                if dst == base {
417                                    return Err(format!("cannot parse line {nth_line}, expected: \"{instruction}\""));
418                                }
419
420                                emit_and_continue!(instruction);
421                            }
422                        }
423                    }
424                }
425
426                if let Some(index) = rhs.find("if ") {
427                    if let Some(src) = parse_reg_or_imm(&rhs[..index]) {
428                        if let Some(condition) = parse_condition(&rhs[index + 3..]) {
429                            if let (RegImm::Reg(cond), RegImm::Imm(0)) = (condition.lhs, condition.rhs) {
430                                let inst = match (src, condition.kind) {
431                                    (RegImm::Reg(src), ConditionKind::Eq) => {
432                                        Some(Instruction::cmov_if_zero(dst.into(), src.into(), cond.into()))
433                                    }
434                                    (RegImm::Reg(src), ConditionKind::NotEq) => {
435                                        Some(Instruction::cmov_if_zero(dst.into(), src.into(), cond.into()))
436                                    }
437                                    (RegImm::Imm(src), ConditionKind::Eq) => {
438                                        Some(Instruction::cmov_if_zero_imm(dst.into(), cond.into(), src as u32))
439                                    }
440                                    (RegImm::Imm(src), ConditionKind::NotEq) => {
441                                        Some(Instruction::cmov_if_zero_imm(dst.into(), cond.into(), src as u32))
442                                    }
443                                    _ => None,
444                                };
445
446                                if let Some(inst) = inst {
447                                    emit_and_continue!(inst);
448                                }
449                            }
450                        }
451                    }
452                }
453
454                if let Some((name, rhs)) = split(rhs, " ") {
455                    if let Some(src) = parse_reg(rhs) {
456                        type F = fn(RawReg, RawReg) -> Instruction;
457                        let ctor = match (name, op_marker) {
458                            ("cpop", OpMarker::I32) => Some(Instruction::count_set_bits_32 as F),
459                            ("cpop", OpMarker::NONE) => Some(Instruction::count_set_bits_64 as F),
460                            ("clz", OpMarker::I32) => Some(Instruction::count_leading_zero_bits_32 as F),
461                            ("clz", OpMarker::NONE) => Some(Instruction::count_leading_zero_bits_64 as F),
462                            ("ctz", OpMarker::I32) => Some(Instruction::count_trailing_zero_bits_32 as F),
463                            ("ctz", OpMarker::NONE) => Some(Instruction::count_trailing_zero_bits_64 as F),
464                            ("sext8", _) => Some(Instruction::sign_extend_8 as F),
465                            ("sext16", _) => Some(Instruction::sign_extend_16 as F),
466                            ("zext16", _) => Some(Instruction::zero_extend_16 as F),
467                            ("reverse", _) => Some(Instruction::reverse_byte as F),
468                            _ => None,
469                        };
470
471                        if let Some(ctor) = ctor {
472                            emit_and_continue!(ctor(dst.into(), src.into()));
473                        }
474                    }
475                }
476
477                if let Some(src) = parse_reg(rhs) {
478                    emit_and_continue!(Instruction::move_reg(dst.into(), src.into()));
479                }
480
481                if let Some(instr) = parse_immediate(rhs) {
482                    match instr {
483                        ParsedImmediate::U32(value) => {
484                            emit_and_continue!(Instruction::load_imm(dst.into(), value));
485                        }
486                        ParsedImmediate::U64(value) => {
487                            emit_and_continue!(Instruction::load_imm64(dst.into(), value));
488                        }
489                    }
490                }
491
492                if let Some(label) = rhs.strip_prefix('@') {
493                    emit_and_continue!(MaybeInstruction::LoadLabelAddress(dst, label.to_owned()));
494                }
495
496                if let Some(rhs) = rhs.strip_prefix("~(") {
497                    if let Some(rhs) = rhs.strip_suffix(')') {
498                        if let Some((src1, src2)) = split(rhs.trim(), "^") {
499                            if let Some(src1) = parse_reg(src1) {
500                                if let Some(src2) = parse_reg(src2) {
501                                    let dst = dst.into();
502                                    let src1 = src1.into();
503                                    let src2 = src2.into();
504                                    emit_and_continue!(Instruction::xnor(dst, src1, src2));
505                                }
506                            }
507                        }
508                    }
509                }
510
511                enum Op {
512                    Add,
513                    Sub,
514                    And,
515                    Xor,
516                    Or,
517                    Mul,
518                    DivUnsigned,
519                    DivSigned,
520                    RemUnsigned,
521                    RemSigned,
522                    LessUnsigned,
523                    LessSigned,
524                    GreaterUnsigned,
525                    GreaterSigned,
526                    ShiftLeft,
527                    ShiftRight,
528                    ShiftArithmeticRight,
529                    RotateLeft,
530                    RotateRight,
531                    AndInverted,
532                    OrInverted,
533                }
534
535                #[allow(clippy::manual_map)]
536                let operation = if let Some(index) = rhs.find('+') {
537                    Some((index, 1, Op::Add))
538                } else if let Some(index) = rhs.find("& ~") {
539                    Some((index, 3, Op::AndInverted))
540                } else if let Some(index) = rhs.find('&') {
541                    Some((index, 1, Op::And))
542                } else if let Some(index) = rhs.find("| ~") {
543                    Some((index, 3, Op::OrInverted))
544                } else if let Some(index) = rhs.find('|') {
545                    Some((index, 1, Op::Or))
546                } else if let Some(index) = rhs.find('^') {
547                    Some((index, 1, Op::Xor))
548                } else if let Some(index) = rhs.find('*') {
549                    Some((index, 1, Op::Mul))
550                } else if let Some(index) = rhs.find("/u") {
551                    Some((index, 2, Op::DivUnsigned))
552                } else if let Some(index) = rhs.find("/s") {
553                    Some((index, 2, Op::DivSigned))
554                } else if let Some(index) = rhs.find("%u") {
555                    Some((index, 2, Op::RemUnsigned))
556                } else if let Some(index) = rhs.find("%s") {
557                    Some((index, 2, Op::RemSigned))
558                } else if let Some(index) = rhs.find(">>a") {
559                    Some((index, 3, Op::ShiftArithmeticRight))
560                } else if let Some(index) = rhs.find(">>r") {
561                    Some((index, 3, Op::RotateRight))
562                } else if let Some(index) = rhs.find("<<r") {
563                    Some((index, 3, Op::RotateLeft))
564                } else if let Some(index) = rhs.find("<<") {
565                    Some((index, 2, Op::ShiftLeft))
566                } else if let Some(index) = rhs.find(">>") {
567                    Some((index, 2, Op::ShiftRight))
568                } else if let Some(index) = rhs.find("<u") {
569                    Some((index, 2, Op::LessUnsigned))
570                } else if let Some(index) = rhs.find("<s") {
571                    Some((index, 2, Op::LessSigned))
572                } else if let Some(index) = rhs.find(">u") {
573                    Some((index, 2, Op::GreaterUnsigned))
574                } else if let Some(index) = rhs.find(">s") {
575                    Some((index, 2, Op::GreaterSigned))
576                } else if let Some(index) = rhs.find('-') {
577                    // Needs to be last.
578                    Some((index, 1, Op::Sub))
579                } else {
580                    None
581                };
582
583                if let Some((index, op_len, op)) = operation {
584                    let src1 = rhs[..index].trim();
585                    let src2 = rhs[index + op_len..].trim();
586
587                    if let Some(src1) = parse_reg(src1) {
588                        if let Some(src2) = parse_reg(src2) {
589                            let dst = dst.into();
590                            let src1 = src1.into();
591                            let src2 = src2.into();
592                            match op_marker {
593                                OpMarker::I32 => {
594                                    emit_and_continue!(match op {
595                                        Op::Add => Instruction::add_32(dst, src1, src2),
596                                        Op::Sub => Instruction::sub_32(dst, src1, src2),
597                                        Op::And => {
598                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
599                                        }
600                                        Op::Xor => {
601                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
602                                        }
603                                        Op::Or => {
604                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
605                                        }
606                                        Op::Mul => Instruction::mul_32(dst, src1, src2),
607                                        Op::DivUnsigned => Instruction::div_unsigned_32(dst, src1, src2),
608                                        Op::DivSigned => Instruction::div_signed_32(dst, src1, src2),
609                                        Op::RemUnsigned => Instruction::rem_unsigned_32(dst, src1, src2),
610                                        Op::RemSigned => Instruction::rem_signed_32(dst, src1, src2),
611                                        Op::LessUnsigned => {
612                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
613                                        }
614                                        Op::LessSigned => {
615                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
616                                        }
617                                        Op::GreaterUnsigned => {
618                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
619                                        }
620                                        Op::GreaterSigned => {
621                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
622                                        }
623                                        Op::ShiftLeft => Instruction::shift_logical_left_32(dst, src1, src2),
624                                        Op::ShiftRight => Instruction::shift_logical_right_32(dst, src1, src2),
625                                        Op::ShiftArithmeticRight => Instruction::shift_arithmetic_right_32(dst, src1, src2),
626                                        Op::RotateLeft => Instruction::rotate_left_32(dst, src1, src2),
627                                        Op::RotateRight => Instruction::rotate_right_32(dst, src1, src2),
628                                        Op::AndInverted => {
629                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
630                                        }
631                                        Op::OrInverted => {
632                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
633                                        }
634                                    });
635                                }
636                                OpMarker::NONE => {
637                                    emit_and_continue!(match op {
638                                        Op::Add => Instruction::add_64(dst, src1, src2),
639                                        Op::Sub => Instruction::sub_64(dst, src1, src2),
640                                        Op::And => Instruction::and(dst, src1, src2),
641                                        Op::Xor => Instruction::xor(dst, src1, src2),
642                                        Op::Or => Instruction::or(dst, src1, src2),
643                                        Op::Mul => Instruction::mul_64(dst, src1, src2),
644                                        Op::DivUnsigned => Instruction::div_unsigned_64(dst, src1, src2),
645                                        Op::DivSigned => Instruction::div_signed_64(dst, src1, src2),
646                                        Op::RemUnsigned => Instruction::rem_unsigned_64(dst, src1, src2),
647                                        Op::RemSigned => Instruction::rem_signed_64(dst, src1, src2),
648                                        Op::LessUnsigned => Instruction::set_less_than_unsigned(dst, src1, src2),
649                                        Op::LessSigned => Instruction::set_less_than_signed(dst, src1, src2),
650                                        Op::GreaterUnsigned => Instruction::set_less_than_unsigned(dst, src2, src1),
651                                        Op::GreaterSigned => Instruction::set_less_than_signed(dst, src2, src1),
652                                        Op::ShiftLeft => Instruction::shift_logical_left_64(dst, src1, src2),
653                                        Op::ShiftRight => Instruction::shift_logical_right_64(dst, src1, src2),
654                                        Op::ShiftArithmeticRight => Instruction::shift_arithmetic_right_64(dst, src1, src2),
655                                        Op::RotateLeft => Instruction::rotate_left_64(dst, src1, src2),
656                                        Op::RotateRight => Instruction::rotate_right_64(dst, src1, src2),
657                                        Op::AndInverted => Instruction::and_inverted(dst, src1, src2),
658                                        Op::OrInverted => Instruction::or_inverted(dst, src1, src2),
659                                    });
660                                }
661                            }
662                        } else if let Some(src2) = parse_immediate(src2).and_then(|value| value.try_into().ok()) {
663                            let dst = dst.into();
664                            let src1 = src1.into();
665                            match op_marker {
666                                OpMarker::I32 => {
667                                    emit_and_continue!(match op {
668                                        Op::Add => Instruction::add_imm_32(dst, src1, src2),
669                                        Op::Sub => Instruction::add_imm_32(dst, src1, (-(src2 as i32)) as u32),
670                                        Op::And => {
671                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
672                                        }
673                                        Op::Xor => {
674                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
675                                        }
676                                        Op::Or => {
677                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
678                                        }
679                                        Op::Mul => Instruction::mul_imm_32(dst, src1, src2),
680                                        Op::DivUnsigned | Op::DivSigned => {
681                                            return Err(format!(
682                                                "cannot parse line {nth_line}: i32 and division is not supported for immediates"
683                                            ));
684                                        }
685                                        Op::RemUnsigned | Op::RemSigned => {
686                                            return Err(format!(
687                                                "cannot parse line {nth_line}: i32 and modulo is not supported for immediates"
688                                            ));
689                                        }
690                                        Op::LessUnsigned => {
691                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
692                                        }
693                                        Op::LessSigned => {
694                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
695                                        }
696                                        Op::GreaterUnsigned => {
697                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
698                                        }
699                                        Op::GreaterSigned => {
700                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
701                                        }
702                                        Op::ShiftLeft => Instruction::shift_logical_left_imm_32(dst, src1, src2),
703                                        Op::ShiftRight => Instruction::shift_logical_right_imm_32(dst, src1, src2),
704                                        Op::ShiftArithmeticRight => Instruction::shift_arithmetic_right_imm_32(dst, src1, src2),
705                                        Op::RotateLeft => {
706                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
707                                        }
708                                        Op::RotateRight => Instruction::rotate_right_imm_32(dst, src1, src2),
709                                        Op::AndInverted => {
710                                            return Err(format!(
711                                                "cannot parse line {nth_line}: i32 and and_inverted not supported for immediates"
712                                            ));
713                                        }
714                                        Op::OrInverted => {
715                                            return Err(format!(
716                                                "cannot parse line {nth_line}: i32 and or_inverted not supported for immediates"
717                                            ));
718                                        }
719                                    });
720                                }
721                                OpMarker::NONE => {
722                                    emit_and_continue!(match op {
723                                        Op::Add => Instruction::add_imm_64(dst, src1, src2),
724                                        Op::Sub => Instruction::add_imm_64(dst, src1, (-(src2 as i32)) as u32),
725                                        Op::And => Instruction::and_imm(dst, src1, src2),
726                                        Op::Xor => Instruction::xor_imm(dst, src1, src2),
727                                        Op::Or => Instruction::or_imm(dst, src1, src2),
728                                        Op::Mul => Instruction::mul_imm_64(dst, src1, src2),
729                                        Op::DivUnsigned | Op::DivSigned => {
730                                            return Err(format!("cannot parse line {nth_line}: division is not supported for immediates"));
731                                        }
732                                        Op::RemUnsigned | Op::RemSigned => {
733                                            return Err(format!("cannot parse line {nth_line}: modulo is not supported for immediates"));
734                                        }
735                                        Op::LessUnsigned => Instruction::set_less_than_unsigned_imm(dst, src1, src2),
736                                        Op::LessSigned => Instruction::set_less_than_signed_imm(dst, src1, src2),
737                                        Op::GreaterUnsigned => Instruction::set_greater_than_unsigned_imm(dst, src1, src2),
738                                        Op::GreaterSigned => Instruction::set_greater_than_signed_imm(dst, src1, src2),
739                                        Op::ShiftLeft => Instruction::shift_logical_left_imm_64(dst, src1, src2),
740                                        Op::ShiftRight => Instruction::shift_logical_right_imm_64(dst, src1, src2),
741                                        Op::ShiftArithmeticRight => Instruction::shift_arithmetic_right_imm_64(dst, src1, src2),
742                                        Op::RotateLeft => {
743                                            return Err(format!("cannot parse line {nth_line}: rotate_left not supported for immediates"));
744                                        }
745                                        Op::RotateRight => Instruction::rotate_right_imm_64(dst, src1, src2),
746                                        Op::AndInverted => {
747                                            return Err(format!("cannot parse line {nth_line}: and_inverted not supported for immediates"));
748                                        }
749                                        Op::OrInverted => {
750                                            return Err(format!("cannot parse line {nth_line}: or_inverted not supported for immediates"));
751                                        }
752                                    });
753                                }
754                            }
755                        }
756                    } else if let Some(src1) = parse_immediate(src1).and_then(|value| value.try_into().ok()) {
757                        if let Some(src2) = parse_reg(src2) {
758                            let dst = dst.into();
759                            let src2 = src2.into();
760                            match op_marker {
761                                OpMarker::I32 => {
762                                    emit_and_continue!(match op {
763                                        Op::Add => Instruction::add_imm_32(dst, src2, src1),
764                                        Op::Sub => Instruction::negate_and_add_imm_32(dst, src2, src1),
765                                        Op::And => {
766                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
767                                        }
768                                        Op::Xor => {
769                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
770                                        }
771                                        Op::Or => {
772                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
773                                        }
774                                        Op::Mul => Instruction::mul_imm_32(dst, src2, src1),
775                                        Op::DivUnsigned | Op::DivSigned => {
776                                            return Err(format!(
777                                                "cannot parse line {nth_line}: i32 and division is not supported for immediates"
778                                            ));
779                                        }
780                                        Op::RemUnsigned | Op::RemSigned => {
781                                            return Err(format!(
782                                                "cannot parse line {nth_line}: i32 and modulo is not supported for immediates"
783                                            ));
784                                        }
785                                        Op::LessUnsigned => {
786                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
787                                        }
788                                        Op::LessSigned => {
789                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
790                                        }
791                                        Op::GreaterUnsigned => {
792                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
793                                        }
794                                        Op::GreaterSigned => {
795                                            return Err(format!("cannot parse line {nth_line}: i32 not supported for operation"));
796                                        }
797                                        Op::ShiftLeft => Instruction::shift_logical_left_imm_alt_32(dst, src2, src1),
798                                        Op::ShiftRight => Instruction::shift_logical_right_imm_alt_32(dst, src2, src1),
799                                        Op::ShiftArithmeticRight => Instruction::shift_arithmetic_right_imm_alt_32(dst, src2, src1),
800                                        Op::RotateLeft => {
801                                            return Err(format!(
802                                                "cannot parse line {nth_line}: i32 and rotate_left is not supported for immediates"
803                                            ));
804                                        }
805                                        Op::RotateRight => Instruction::rotate_right_imm_alt_32(dst, src2, src1),
806                                        Op::AndInverted => {
807                                            return Err(format!(
808                                                "cannot parse line {nth_line}: i32 and and_inverted not supported for operation"
809                                            ));
810                                        }
811                                        Op::OrInverted => {
812                                            return Err(format!(
813                                                "cannot parse line {nth_line}: i32 and or_inverted not supported for operation"
814                                            ));
815                                        }
816                                    });
817                                }
818                                OpMarker::NONE => {
819                                    emit_and_continue!(match op {
820                                        Op::Add => Instruction::add_imm_64(dst, src2, src1),
821                                        Op::Sub => Instruction::negate_and_add_imm_64(dst, src2, src1),
822                                        Op::And => Instruction::and_imm(dst, src2, src1),
823                                        Op::Xor => Instruction::xor_imm(dst, src2, src1),
824                                        Op::Or => Instruction::or_imm(dst, src2, src1),
825                                        Op::Mul => Instruction::mul_imm_64(dst, src2, src1),
826                                        Op::DivUnsigned | Op::DivSigned => {
827                                            return Err(format!("cannot parse line {nth_line}: division is not supported for immediates"));
828                                        }
829                                        Op::RemUnsigned | Op::RemSigned => {
830                                            return Err(format!("cannot parse line {nth_line}: modulo is not supported for immediates"));
831                                        }
832                                        Op::LessUnsigned => Instruction::set_greater_than_unsigned_imm(dst, src2, src1),
833                                        Op::LessSigned => Instruction::set_greater_than_signed_imm(dst, src2, src1),
834                                        Op::GreaterUnsigned => Instruction::set_less_than_unsigned_imm(dst, src2, src1),
835                                        Op::GreaterSigned => Instruction::set_less_than_signed_imm(dst, src2, src1),
836                                        Op::ShiftLeft => Instruction::shift_logical_left_imm_alt_64(dst, src2, src1),
837                                        Op::ShiftRight => Instruction::shift_logical_right_imm_alt_64(dst, src2, src1),
838                                        Op::ShiftArithmeticRight => Instruction::shift_arithmetic_right_imm_alt_64(dst, src2, src1),
839                                        Op::RotateLeft => {
840                                            return Err(format!("cannot parse line {nth_line}: i64 not supported for operation"));
841                                        }
842                                        Op::RotateRight => Instruction::rotate_right_imm_alt_64(dst, src2, src1),
843                                        Op::AndInverted => {
844                                            return Err(format!("cannot parse line {nth_line}: and_inverted not supported for immediates"));
845                                        }
846                                        Op::OrInverted => {
847                                            return Err(format!("cannot parse line {nth_line}: or_inverted not supported for immediates"));
848                                        }
849                                    });
850                                }
851                            }
852                        }
853                    }
854                }
855
856                if let Some(rhs) = rhs.strip_suffix(')') {
857                    let rhs = rhs.trim();
858                    if let Some((name, rhs)) = split(rhs, "(") {
859                        type F = fn(RawReg, RawReg, RawReg) -> Instruction;
860                        let ctor = match name {
861                            "maxs" => Some(Instruction::maximum as F),
862                            "maxu" => Some(Instruction::maximum_unsigned as F),
863                            "mins" => Some(Instruction::minimum as F),
864                            "minu" => Some(Instruction::minimum_unsigned as F),
865                            _ => None,
866                        };
867
868                        if let Some(ctor) = ctor {
869                            if let Some((src1, src2)) = split(rhs, ",") {
870                                if let Some(src1) = parse_reg(src1) {
871                                    if let Some(src2) = parse_reg(src2) {
872                                        emit_and_continue!(ctor(dst.into(), src1.into(), src2.into()));
873                                    }
874                                }
875                            }
876                        }
877                    }
878                }
879
880                #[allow(clippy::manual_map)]
881                let load_kind = if let Some(rhs) = rhs.strip_prefix("u8") {
882                    Some((LoadKind::U8, rhs))
883                } else if let Some(rhs) = rhs.strip_prefix("u16") {
884                    Some((LoadKind::U16, rhs))
885                } else if let Some(rhs) = rhs.strip_prefix("u32") {
886                    Some((LoadKind::U32, rhs))
887                } else if let Some(rhs) = rhs.strip_prefix("u64") {
888                    Some((LoadKind::U64, rhs))
889                } else if let Some(rhs) = rhs.strip_prefix("i8") {
890                    Some((LoadKind::I8, rhs))
891                } else if let Some(rhs) = rhs.strip_prefix("i16") {
892                    Some((LoadKind::I16, rhs))
893                } else if let Some(rhs) = rhs.strip_prefix("i32") {
894                    Some((LoadKind::I32, rhs))
895                } else {
896                    None
897                };
898
899                if let Some((kind, rhs)) = load_kind {
900                    if let Some((base, offset)) = parse_indirect_memory_access(rhs) {
901                        let dst = dst.into();
902                        let base = base.into();
903                        let offset = offset as u32;
904                        emit_and_continue!(match kind {
905                            LoadKind::I8 => Instruction::load_indirect_i8(dst, base, offset),
906                            LoadKind::I16 => Instruction::load_indirect_i16(dst, base, offset),
907                            LoadKind::I32 => Instruction::load_indirect_i32(dst, base, offset),
908                            LoadKind::U8 => Instruction::load_indirect_u8(dst, base, offset),
909                            LoadKind::U16 => Instruction::load_indirect_u16(dst, base, offset),
910                            LoadKind::U32 => Instruction::load_indirect_u32(dst, base, offset),
911                            LoadKind::U64 => Instruction::load_indirect_u64(dst, base, offset),
912                        });
913                    } else if let Some(offset) = parse_absolute_memory_access(rhs) {
914                        let dst = dst.into();
915                        let offset = offset as u32;
916                        emit_and_continue!(match kind {
917                            LoadKind::I8 => Instruction::load_i8(dst, offset),
918                            LoadKind::I16 => Instruction::load_i16(dst, offset),
919                            LoadKind::I32 => Instruction::load_i32(dst, offset),
920                            LoadKind::U8 => Instruction::load_u8(dst, offset),
921                            LoadKind::U16 => Instruction::load_u16(dst, offset),
922                            LoadKind::U32 => Instruction::load_u32(dst, offset),
923                            LoadKind::U64 => Instruction::load_u64(dst, offset),
924                        });
925                    }
926                }
927            }
928
929            #[allow(clippy::manual_map)]
930            let store_kind = if let Some(lhs) = lhs.strip_prefix("u8") {
931                Some((StoreKind::U8, lhs))
932            } else if let Some(lhs) = lhs.strip_prefix("u16") {
933                Some((StoreKind::U16, lhs))
934            } else if let Some(lhs) = lhs.strip_prefix("u32") {
935                Some((StoreKind::U32, lhs))
936            } else if let Some(lhs) = lhs.strip_prefix("u64") {
937                Some((StoreKind::U64, lhs))
938            } else {
939                None
940            };
941
942            if let Some((kind, lhs)) = store_kind {
943                if let Some(offset) = parse_absolute_memory_access(lhs) {
944                    let offset = offset as u32;
945                    if let Some(rhs) = parse_reg(rhs) {
946                        let rhs = rhs.into();
947                        emit_and_continue!(match kind {
948                            StoreKind::U8 => Instruction::store_u8(rhs, offset),
949                            StoreKind::U16 => Instruction::store_u16(rhs, offset),
950                            StoreKind::U32 => Instruction::store_u32(rhs, offset),
951                            StoreKind::U64 => Instruction::store_u64(rhs, offset),
952                        });
953                    } else if let Some(rhs) = parse_immediate(rhs).and_then(|value| value.try_into().ok()) {
954                        emit_and_continue!(match kind {
955                            StoreKind::U8 => match u8::try_from(rhs) {
956                                Ok(_) => Instruction::store_imm_u8(offset, rhs),
957                                Err(_) => return Err(format!("cannot parse line {nth_line}: immediate larger than u8")),
958                            },
959                            StoreKind::U16 => match u16::try_from(rhs) {
960                                Ok(_) => Instruction::store_imm_u16(offset, rhs),
961                                Err(_) => return Err(format!("cannot parse line {nth_line}: immediate larger than u16")),
962                            },
963                            StoreKind::U32 => Instruction::store_imm_u32(offset, rhs),
964                            StoreKind::U64 => Instruction::store_imm_u64(offset, rhs),
965                        });
966                    }
967                } else if let Some((base, offset)) = parse_indirect_memory_access(lhs) {
968                    let base = base.into();
969                    let offset = offset as u32;
970                    if let Some(rhs) = parse_reg(rhs) {
971                        let rhs = rhs.into();
972                        emit_and_continue!(match kind {
973                            StoreKind::U8 => Instruction::store_indirect_u8(rhs, base, offset),
974                            StoreKind::U16 => Instruction::store_indirect_u16(rhs, base, offset),
975                            StoreKind::U32 => Instruction::store_indirect_u32(rhs, base, offset),
976                            StoreKind::U64 => Instruction::store_indirect_u64(rhs, base, offset),
977                        });
978                    } else if let Some(rhs) = parse_immediate(rhs).and_then(|value| value.try_into().ok()) {
979                        emit_and_continue!(match kind {
980                            StoreKind::U8 => match u8::try_from(rhs) {
981                                Ok(_) => Instruction::store_imm_indirect_u8(base, offset, rhs),
982                                Err(_) => return Err(format!("cannot parse line {nth_line}: immediate larger than u8")),
983                            },
984                            StoreKind::U16 => match u16::try_from(rhs) {
985                                Ok(_) => Instruction::store_imm_indirect_u16(base, offset, rhs),
986                                Err(_) => return Err(format!("cannot parse line {nth_line}: immediate larger than u16")),
987                            },
988                            StoreKind::U32 => Instruction::store_imm_indirect_u32(base, offset, rhs),
989                            StoreKind::U64 => Instruction::store_imm_indirect_u64(base, offset, rhs),
990                        });
991                    }
992                }
993            }
994        }
995
996        return Err(format!("cannot parse line {nth_line}: \"{original_line}\""));
997    }
998
999    let mut code = Vec::new();
1000    let mut jump_table = Vec::new();
1001    for instruction in instructions {
1002        match instruction {
1003            MaybeInstruction::Instruction(instruction) => {
1004                code.push(instruction);
1005            }
1006            MaybeInstruction::LoadLabelAddress(dst, label) => {
1007                let Some(&target_index) = label_to_index.get(&*label) else {
1008                    return Err(format!("label is not defined: \"{label}\""));
1009                };
1010
1011                jump_table.push(target_index);
1012                code.push(Instruction::load_imm(
1013                    dst.into(),
1014                    (jump_table.len() as u32) * crate::abi::VM_CODE_ADDRESS_ALIGNMENT,
1015                ));
1016            }
1017            MaybeInstruction::LoadImmAndJump(dst, value, label) => {
1018                let Some(&target_index) = label_to_index.get(&*label) else {
1019                    return Err(format!("label is not defined: \"{label}\""));
1020                };
1021
1022                code.push(Instruction::load_imm_and_jump(dst.into(), value, target_index));
1023            }
1024            MaybeInstruction::Jump(label) => {
1025                let Some(&target_index) = label_to_index.get(&*label) else {
1026                    return Err(format!("label is not defined: \"{label}\""));
1027                };
1028                code.push(Instruction::jump(target_index));
1029            }
1030            MaybeInstruction::Branch(label, kind, lhs, rhs) => {
1031                let Some(&target_index) = label_to_index.get(&*label) else {
1032                    return Err(format!("label is not defined: \"{label}\""));
1033                };
1034
1035                let lhs = lhs.into();
1036                let rhs = rhs.into();
1037                let instruction = match kind {
1038                    ConditionKind::Eq => Instruction::branch_eq(lhs, rhs, target_index),
1039                    ConditionKind::NotEq => Instruction::branch_not_eq(lhs, rhs, target_index),
1040                    ConditionKind::LessSigned => Instruction::branch_less_signed(lhs, rhs, target_index),
1041                    ConditionKind::LessUnsigned => Instruction::branch_less_unsigned(lhs, rhs, target_index),
1042                    ConditionKind::GreaterOrEqualSigned => Instruction::branch_greater_or_equal_signed(lhs, rhs, target_index),
1043                    ConditionKind::GreaterOrEqualUnsigned => Instruction::branch_greater_or_equal_unsigned(lhs, rhs, target_index),
1044
1045                    ConditionKind::LessOrEqualSigned => Instruction::branch_greater_or_equal_signed(rhs, lhs, target_index),
1046                    ConditionKind::LessOrEqualUnsigned => Instruction::branch_greater_or_equal_unsigned(rhs, lhs, target_index),
1047                    ConditionKind::GreaterSigned => Instruction::branch_less_signed(rhs, lhs, target_index),
1048                    ConditionKind::GreaterUnsigned => Instruction::branch_less_unsigned(rhs, lhs, target_index),
1049                };
1050                code.push(instruction);
1051            }
1052            MaybeInstruction::BranchImm(label, kind, lhs, rhs) => {
1053                let Some(&target_index) = label_to_index.get(&*label) else {
1054                    return Err(format!("label is not defined: \"{label}\""));
1055                };
1056
1057                let lhs = lhs.into();
1058                let rhs = rhs as u32;
1059                let instruction = match kind {
1060                    ConditionKind::Eq => Instruction::branch_eq_imm(lhs, rhs, target_index),
1061                    ConditionKind::NotEq => Instruction::branch_not_eq_imm(lhs, rhs, target_index),
1062                    ConditionKind::LessSigned => Instruction::branch_less_signed_imm(lhs, rhs, target_index),
1063                    ConditionKind::LessUnsigned => Instruction::branch_less_unsigned_imm(lhs, rhs, target_index),
1064                    ConditionKind::GreaterOrEqualSigned => Instruction::branch_greater_or_equal_signed_imm(lhs, rhs, target_index),
1065                    ConditionKind::GreaterOrEqualUnsigned => Instruction::branch_greater_or_equal_unsigned_imm(lhs, rhs, target_index),
1066                    ConditionKind::LessOrEqualSigned => Instruction::branch_less_or_equal_signed_imm(lhs, rhs, target_index),
1067                    ConditionKind::LessOrEqualUnsigned => Instruction::branch_less_or_equal_unsigned_imm(lhs, rhs, target_index),
1068                    ConditionKind::GreaterSigned => Instruction::branch_greater_signed_imm(lhs, rhs, target_index),
1069                    ConditionKind::GreaterUnsigned => Instruction::branch_greater_unsigned_imm(lhs, rhs, target_index),
1070                };
1071                code.push(instruction);
1072            }
1073        };
1074    }
1075
1076    let Some(isa) = isa else {
1077        return Err("no ISA was declared in the program".into());
1078    };
1079
1080    let mut builder = crate::writer::ProgramBlobBuilder::new(isa);
1081    builder.set_ro_data(ro_data);
1082    builder.set_ro_data_size(ro_data_size);
1083    builder.set_rw_data(rw_data);
1084    builder.set_rw_data_size(rw_data_size);
1085    builder.set_stack_size(stack_size);
1086    builder.set_code(&code, &jump_table);
1087    for (label, export) in exports {
1088        match export {
1089            Export::ByBlock(target_index) => builder.add_export_by_basic_block(target_index, label.as_bytes()),
1090            Export::ByInstruction(target_index) => builder.add_export_by_instruction(target_index, label.as_bytes()),
1091        }
1092    }
1093
1094    builder.to_vec()
1095}
1096
1097#[cfg(test)]
1098#[track_caller]
1099fn assert_assembler(input: &str, expected_output: &str) {
1100    use crate::program::InstructionFormat;
1101    use alloc::string::ToString;
1102
1103    let expected_output_clean: Vec<_> = expected_output.trim().split('\n').map(|line| line.trim()).collect();
1104    let expected_output_clean = expected_output_clean.join("\n");
1105
1106    let blob = assemble(Some(InstructionSetKind::Latest64), input).expect("failed to assemble");
1107    let program = crate::program::ProgramBlob::parse(blob.into()).unwrap();
1108    let output: Vec<_> = program
1109        .instructions()
1110        .take_while(|inst| (inst.offset.0 as usize) < program.code().len())
1111        .map(|inst| inst.kind.display(&InstructionFormat::default()).to_string())
1112        .collect();
1113    let output = output.join("\n");
1114    assert_eq!(output, expected_output_clean);
1115}
1116
1117#[test]
1118fn test_assembler_basics() {
1119    assert_assembler(
1120        "
1121        // This is a comment.
1122        a0 = a1 + a2
1123        a3 = a4 + a5
1124        // This is another comment.
1125    ",
1126        "
1127        a0 = a1 + a2
1128        a3 = a4 + a5
1129    ",
1130    );
1131
1132    assert_assembler(
1133        "
1134        jump @label
1135        a0 = 1
1136        @label:
1137        a0 = 2
1138    ",
1139        "
1140        jump 6
1141        a0 = 0x1
1142        fallthrough
1143        a0 = 0x2
1144    ",
1145    );
1146}