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
38fn 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; 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 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}