1use std::collections::HashMap;
11
12use parser::lexer::token::Span;
13
14use crate::runtime_core::NULL_VALUE;
15
16const MAX_UNSCALED_OFFSET: u64 = 256;
19const MAX_ARITH_IMM: u64 = 4095;
21const MAX_ARITH_IMM_SHIFTED: u64 = 0xFF_F000;
23const MAX_SCALED_OFFSET: u64 = 32760;
26
27pub const CLOSURE_SLOT_OFFSET: u64 = 16;
29
30pub fn slot_offset(index: usize) -> u64 {
33 16 * (index as u64 + 2)
34}
35
36pub fn call_area_size(argc: usize) -> u64 {
39 let packed = 8 * (argc as u64 + 1);
40 (packed + 15) & !15
41}
42
43pub fn scratch_area_size(len: usize) -> u64 {
45 let packed = 8 * len as u64;
46 (packed + 15) & !15
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum AsmDialect {
55 LinuxElf,
57 MachO,
59}
60
61impl AsmDialect {
62 pub fn global_symbol(self, name: &str) -> String {
66 match self {
67 AsmDialect::LinuxElf => name.to_string(),
68 AsmDialect::MachO => format!("_{}", name),
69 }
70 }
71
72 pub(crate) fn local_label_prefix(self) -> &'static str {
75 match self {
76 AsmDialect::LinuxElf => ".L",
77 AsmDialect::MachO => "L",
78 }
79 }
80}
81
82#[derive(Clone, Debug)]
83struct Line {
84 text: String,
85 span: Option<(usize, usize)>,
86}
87
88#[derive(Clone, Debug)]
91pub struct Assembly {
92 pub text: String,
93 pub line_spans: Vec<Option<(usize, usize)>>,
94}
95
96pub struct FunctionFrame {
99 pub label: String,
100 pub comment: String,
102 pub num_parameters: usize,
105 pub num_definitions: usize,
107 pub epilogue_label: String,
109 pub parameter_names: Vec<String>,
111}
112
113pub struct Emitter {
114 dialect: AsmDialect,
115 main_body: Vec<Line>,
116 functions: Vec<Vec<Line>>,
117 rodata: Vec<Line>,
118 open_functions: Vec<Vec<Line>>,
121 span_stack: Vec<Option<(usize, usize)>>,
124 label_count: usize,
125 function_count: usize,
126 strings: HashMap<Vec<u8>, (String, u64)>,
127}
128
129impl Emitter {
130 pub fn new(dialect: AsmDialect) -> Emitter {
131 Emitter {
132 dialect,
133 main_body: vec![],
134 functions: vec![],
135 rodata: vec![],
136 open_functions: vec![],
137 span_stack: vec![],
138 label_count: 0,
139 function_count: 0,
140 strings: HashMap::new(),
141 }
142 }
143
144 fn current_span(&self) -> Option<(usize, usize)> {
145 self.span_stack.last().copied().flatten()
146 }
147
148 fn buffer(&mut self) -> &mut Vec<Line> {
149 self.open_functions
150 .last_mut()
151 .unwrap_or(&mut self.main_body)
152 }
153
154 fn push_line(&mut self, text: String) {
155 let span = self.current_span();
156 self.buffer().push(Line {
157 text,
158 span,
159 });
160 }
161
162 pub fn with_span<R>(&mut self, span: &Span, f: impl FnOnce(&mut Emitter) -> R) -> R {
163 self.span_stack.push(Some((span.start, span.end)));
164 let result = f(self);
165 self.span_stack.pop();
166 result
167 }
168
169 pub fn without_span<R>(&mut self, f: impl FnOnce(&mut Emitter) -> R) -> R {
170 self.span_stack.push(None);
171 let result = f(self);
172 self.span_stack.pop();
173 result
174 }
175
176 pub fn ins(&mut self, text: &str) {
178 self.push_line(format!(" {}", text));
179 }
180
181 pub fn ins_cmt(&mut self, text: &str, comment: &str) {
183 self.push_line(format!(" {:<31} // {}", text, comment));
184 }
185
186 pub fn comment(&mut self, comment: &str) {
188 self.push_line(format!(" // {}", comment));
189 }
190
191 pub fn label(&mut self, name: &str) {
192 self.push_line(format!("{}:", name));
193 }
194
195 pub fn label_cmt(&mut self, name: &str, comment: &str) {
196 self.push_line(format!("{:<35} // {}", format!("{}:", name), comment));
197 }
198
199 pub fn new_label(&mut self) -> String {
200 let label = format!("{}{}", self.dialect.local_label_prefix(), self.label_count);
201 self.label_count += 1;
202 label
203 }
204
205 pub fn new_function_label(&mut self) -> String {
206 let label = format!("{}fn{}", self.dialect.local_label_prefix(), self.function_count);
207 self.function_count += 1;
208 label
209 }
210
211 pub fn intern_string(&mut self, bytes: &[u8]) -> (String, u64) {
214 if let Some((label, len)) = self.strings.get(bytes) {
215 return (label.clone(), *len);
216 }
217 let label = format!("{}str{}", self.dialect.local_label_prefix(), self.strings.len());
218 let len = bytes.len() as u64;
219 let preview: String = String::from_utf8_lossy(bytes)
220 .chars()
221 .take(32)
222 .map(|c| if c == '\n' || c == '\r' { ' ' } else { c })
223 .collect();
224 self.rodata.push(Line {
225 text: format!("{:<35} // \"{}\"", format!("{}:", label), preview),
226 span: None,
227 });
228 for chunk in bytes.chunks(16) {
230 let rendered: Vec<String> = chunk.iter().map(|b| format!("0x{:02x}", b)).collect();
231 self.rodata.push(Line {
232 text: format!(" .byte {}", rendered.join(", ")),
233 span: None,
234 });
235 }
236 self.strings.insert(bytes.to_vec(), (label.clone(), len));
237 (label, len)
238 }
239
240 pub fn load_imm64(&mut self, reg: &str, value: u64, comment: &str) {
244 let halfwords: Vec<u64> = (0..4).map(|i| (value >> (16 * i)) & 0xFFFF).collect();
245 let mut emitted = false;
246 for (index, halfword) in halfwords.iter().enumerate() {
247 if *halfword == 0 {
248 continue;
249 }
250 let shift = 16 * index;
251 let text = if !emitted {
252 if shift == 0 {
253 format!("movz {}, #0x{:x}", reg, halfword)
254 } else {
255 format!("movz {}, #0x{:x}, lsl #{}", reg, halfword, shift)
256 }
257 } else if shift == 0 {
258 format!("movk {}, #0x{:x}", reg, halfword)
259 } else {
260 format!("movk {}, #0x{:x}, lsl #{}", reg, halfword, shift)
261 };
262 if !emitted && !comment.is_empty() {
263 self.ins_cmt(&text, comment);
264 } else {
265 self.ins(&text);
266 }
267 emitted = true;
268 }
269 if !emitted {
270 let text = format!("movz {}, #0", reg);
271 if comment.is_empty() {
272 self.ins(&text);
273 } else {
274 self.ins_cmt(&text, comment);
275 }
276 }
277 }
278
279 pub fn load_label_address(&mut self, reg: &str, label: &str, comment: &str) {
282 let (page, low) = match self.dialect {
283 AsmDialect::LinuxElf => (
284 format!("adrp {}, {}", reg, label),
285 format!("add {}, {}, :lo12:{}", reg, reg, label),
286 ),
287 AsmDialect::MachO => (
288 format!("adrp {}, {}@PAGE", reg, label),
289 format!("add {}, {}, {}@PAGEOFF", reg, reg, label),
290 ),
291 };
292 if comment.is_empty() {
293 self.ins(&page);
294 } else {
295 self.ins_cmt(&page, comment);
296 }
297 self.ins(&low);
298 }
299
300 pub fn call_runtime(&mut self, name: &str, comment: &str) {
304 let text = format!("bl {}", self.dialect.global_symbol(name));
305 if comment.is_empty() {
306 self.ins(&text);
307 } else {
308 self.ins_cmt(&text, comment);
309 }
310 }
311
312 pub fn push_acc(&mut self, comment: &str) {
314 if comment.is_empty() {
315 self.ins("str x0, [sp, #-16]!");
316 } else {
317 self.ins_cmt("str x0, [sp, #-16]!", comment);
318 }
319 }
320
321 pub fn pop(&mut self, reg: &str, comment: &str) {
322 let text = format!("ldr {}, [sp], #16", reg);
323 if comment.is_empty() {
324 self.ins(&text);
325 } else {
326 self.ins_cmt(&text, comment);
327 }
328 }
329
330 fn adjust_sp(&mut self, mnemonic: &str, bytes: u64) {
331 if bytes == 0 {
332 return;
333 }
334 if bytes <= MAX_ARITH_IMM {
335 self.ins(&format!("{} sp, sp, #{}", mnemonic, bytes));
336 return;
337 }
338 if bytes <= MAX_ARITH_IMM + MAX_ARITH_IMM_SHIFTED {
339 let high = bytes >> 12;
340 let low = bytes & 0xFFF;
341 self.ins(&format!("{} sp, sp, #{}, lsl #12", mnemonic, high));
342 if low != 0 {
343 self.ins(&format!("{} sp, sp, #{}", mnemonic, low));
344 }
345 return;
346 }
347 self.load_imm64("x8", bytes, "sp adjustment beyond immediate range");
348 self.ins(&format!("{} sp, sp, x8", mnemonic));
349 }
350
351 pub fn sp_sub(&mut self, bytes: u64) {
354 debug_assert_eq!(bytes % 16, 0);
355 self.adjust_sp("sub", bytes);
356 }
357
358 pub fn sp_add(&mut self, bytes: u64) {
359 debug_assert_eq!(bytes % 16, 0);
360 self.adjust_sp("add", bytes);
361 }
362
363 pub fn frame_store(&mut self, reg: &str, offset: u64, comment: &str) {
367 debug_assert!(reg != "x8");
368 if offset <= MAX_UNSCALED_OFFSET {
369 let text = format!("stur {}, [x29, #-{}]", reg, offset);
370 if comment.is_empty() {
371 self.ins(&text);
372 } else {
373 self.ins_cmt(&text, comment);
374 }
375 return;
376 }
377 self.load_imm64("x8", offset, comment);
378 self.ins("sub x8, x29, x8");
379 self.ins(&format!("str {}, [x8]", reg));
380 }
381
382 pub fn frame_load(&mut self, reg: &str, offset: u64, comment: &str) {
383 debug_assert!(reg != "x8");
384 if offset <= MAX_UNSCALED_OFFSET {
385 let text = format!("ldur {}, [x29, #-{}]", reg, offset);
386 if comment.is_empty() {
387 self.ins(&text);
388 } else {
389 self.ins_cmt(&text, comment);
390 }
391 return;
392 }
393 self.load_imm64("x8", offset, comment);
394 self.ins("sub x8, x29, x8");
395 self.ins(&format!("ldr {}, [x8]", reg));
396 }
397
398 pub fn sp_store(&mut self, reg: &str, offset: u64, comment: &str) {
401 debug_assert!(reg != "x8");
402 debug_assert_eq!(offset % 8, 0);
403 if offset <= MAX_SCALED_OFFSET {
404 let text = if offset == 0 {
405 format!("str {}, [sp]", reg)
406 } else {
407 format!("str {}, [sp, #{}]", reg, offset)
408 };
409 if comment.is_empty() {
410 self.ins(&text);
411 } else {
412 self.ins_cmt(&text, comment);
413 }
414 return;
415 }
416 self.load_imm64("x8", offset, comment);
417 self.ins("add x8, sp, x8");
418 self.ins(&format!("str {}, [x8]", reg));
419 }
420
421 pub fn sp_address(&mut self, reg: &str, offset: u64, comment: &str) {
423 if offset <= MAX_ARITH_IMM {
424 let text = format!("add {}, sp, #{}", reg, offset);
425 if comment.is_empty() {
426 self.ins(&text);
427 } else {
428 self.ins_cmt(&text, comment);
429 }
430 return;
431 }
432 self.load_imm64(reg, offset, comment);
433 self.ins(&format!("add {}, sp, {}", reg, reg));
434 }
435
436 fn global_slot(&mut self, mnemonic: &str, reg: &str, index: usize, comment: &str) {
437 debug_assert!(reg != "x8" && reg != "x9");
438 self.load_label_address("x8", "g_globals", comment);
439 let offset = 8 * index as u64;
440 if offset <= MAX_SCALED_OFFSET {
441 if offset == 0 {
442 self.ins(&format!("{} {}, [x8]", mnemonic, reg));
443 } else {
444 self.ins(&format!("{} {}, [x8, #{}]", mnemonic, reg, offset));
445 }
446 return;
447 }
448 self.load_imm64("x9", offset, "global slot beyond immediate range");
449 self.ins(&format!("{} {}, [x8, x9]", mnemonic, reg));
450 }
451
452 pub fn global_load(&mut self, reg: &str, index: usize, comment: &str) {
455 self.global_slot("ldr", reg, index, comment);
456 }
457
458 pub fn global_store(&mut self, reg: &str, index: usize, comment: &str) {
459 self.global_slot("str", reg, index, comment);
460 }
461
462 fn capture(&mut self, f: impl FnOnce(&mut Emitter)) -> Vec<Line> {
465 self.open_functions.push(vec![]);
466 self.without_span(f);
467 self.open_functions.pop().unwrap()
468 }
469
470 pub fn begin_function(&mut self) {
473 self.open_functions.push(vec![]);
474 }
475
476 pub fn end_function(&mut self, frame: FunctionFrame) {
481 let body = self
482 .open_functions
483 .pop()
484 .expect("end_function without begin_function");
485 let num_slots = 1 + frame.num_definitions;
486 let frame_bytes = 16 * num_slots as u64;
487
488 let prologue = self.capture(|emitter| {
489 emitter.ins("stp x29, x30, [sp, #-16]!");
490 emitter.ins("mov x29, sp");
491 emitter.sp_sub(frame_bytes);
492 emitter.frame_store("x0", CLOSURE_SLOT_OFFSET, "closure (hidden argument)");
493 for index in 0..frame.num_parameters {
494 let name = frame
495 .parameter_names
496 .get(index)
497 .map(String::as_str)
498 .unwrap_or("parameter");
499 let register = format!("x{}", index + 1);
500 emitter.frame_store(®ister, slot_offset(index), name);
501 }
502 if frame.num_parameters < frame.num_definitions {
503 emitter.load_imm64("x9", NULL_VALUE, "null-initialize locals");
504 for index in frame.num_parameters..frame.num_definitions {
505 emitter.frame_store("x9", slot_offset(index), "");
506 }
507 }
508 });
509 let epilogue = self.capture(|emitter| {
510 emitter.label(&frame.epilogue_label);
511 emitter.ins("mov sp, x29");
512 emitter.ins("ldp x29, x30, [sp], #16");
513 emitter.ins("ret");
514 });
515
516 let mut lines = Vec::with_capacity(prologue.len() + body.len() + epilogue.len() + 1);
517 lines.push(Line {
518 text: format!("{:<35} // {}", format!("{}:", frame.label), frame.comment),
519 span: None,
520 });
521 lines.extend(prologue);
522 lines.extend(body);
523 lines.extend(epilogue);
524 self.functions.push(lines);
525 }
526
527 pub fn finish(
531 mut self,
532 globals_count: usize,
533 main_epilogue_label: &str,
534 observe: bool,
535 ) -> Assembly {
536 debug_assert!(self.open_functions.is_empty(), "unfinished function buffer");
537 let main_body = std::mem::take(&mut self.main_body);
538
539 let prologue = self.capture(|emitter| {
540 emitter.ins("stp x29, x30, [sp, #-16]!");
541 emitter.ins("mov x29, sp");
542 if observe {
543 emitter.load_imm64("x0", 3, "observer channel fd");
544 emitter.call_runtime("rt_observer_init", "");
545 }
546 emitter.load_label_address("x0", "g_globals", "");
547 emitter.load_imm64("x1", globals_count as u64, "global slot count");
548 emitter.call_runtime("rt_globals_init", "");
549 });
550 let epilogue = self.capture(|emitter| {
551 emitter.label(main_epilogue_label);
552 if observe {
553 emitter.call_runtime("rt_observe_result", "program result record");
554 }
555 emitter.ins_cmt("mov w0, #0", "exit code is never the tagged value");
556 emitter.ins("mov sp, x29");
557 emitter.ins("ldp x29, x30, [sp], #16");
558 emitter.ins("ret");
559 });
560
561 let mut lines: Vec<Line> = vec![];
562 let mut raw = |text: &str| {
563 lines.push(Line {
564 text: text.to_string(),
565 span: None,
566 });
567 };
568 let main_symbol = self.dialect.global_symbol("main");
569 raw("// Generated by monkey-asm (docs/arm64-asm-backend-design.md). Do not edit.");
570 raw(" .text");
571 raw(&format!(" .globl {}", main_symbol));
572 raw(" .p2align 2");
573 raw(&format!("{}:", main_symbol));
574 lines.extend(prologue);
575 lines.extend(main_body);
576 lines.extend(epilogue);
577 for function in std::mem::take(&mut self.functions) {
578 lines.push(Line {
579 text: String::new(),
580 span: None,
581 });
582 lines.extend(function);
583 }
584 if !self.rodata.is_empty() {
585 lines.push(Line {
586 text: String::new(),
587 span: None,
588 });
589 let rodata_section = match self.dialect {
590 AsmDialect::LinuxElf => " .section .rodata",
591 AsmDialect::MachO => " .section __TEXT,__const",
592 };
593 lines.push(Line {
594 text: rodata_section.to_string(),
595 span: None,
596 });
597 lines.append(&mut self.rodata);
598 }
599 lines.push(Line {
600 text: String::new(),
601 span: None,
602 });
603 match self.dialect {
604 AsmDialect::LinuxElf => {
605 lines.push(Line {
606 text: " .bss".to_string(),
607 span: None,
608 });
609 lines.push(Line {
610 text: " .balign 8".to_string(),
611 span: None,
612 });
613 lines.push(Line {
614 text: format!("{:<35} // {} global slot(s)", "g_globals:", globals_count),
615 span: None,
616 });
617 lines.push(Line {
618 text: format!(" .skip {}", 8 * globals_count),
619 span: None,
620 });
621 }
622 AsmDialect::MachO => {
623 let size = (8 * globals_count).max(8);
627 lines.push(Line {
628 text: format!(
629 " {:<31} // {} global slot(s)",
630 format!(".zerofill __DATA,__bss,g_globals,{},3", size),
631 globals_count
632 ),
633 span: None,
634 });
635 }
636 }
637
638 let mut text = String::new();
639 let mut line_spans = Vec::with_capacity(lines.len());
640 for line in &lines {
641 text.push_str(&line.text);
642 text.push('\n');
643 line_spans.push(line.span);
644 }
645 Assembly {
646 text,
647 line_spans,
648 }
649 }
650}