Skip to main content

monkey_asm/
lower.rs

1//! Single-pass AST → AArch64 lowering (design §6, §7): accumulator in `x0`,
2//! temporaries on the machine stack, scope analysis via the bytecode
3//! compiler's `SymbolTable`, and every dynamic operation through the frozen
4//! `rt_*` ABI. No IR, no register allocation, no optimization.
5
6use compiler::symbol_table::{Symbol, SymbolScope, SymbolTable};
7use object::builtins::BuiltIns;
8use parser::ast::{
9    BlockStatement, ClassDeclaration, Expression, FunctionDeclaration, Let, Literal,
10    MethodDefinition, MethodKind, Node, Statement,
11};
12use parser::lexer::token::{Span, TokenKind};
13use parser::validation::validate_program;
14use std::rc::Rc;
15
16use crate::emitter::{
17    call_area_size, scratch_area_size, slot_offset, AsmDialect, Assembly, Emitter, FunctionFrame,
18    CLOSURE_SLOT_OFFSET,
19};
20use crate::runtime_core::{
21    builtin_id_for_symbol_index, builtin_value, i64_fits_smi, FALSE_VALUE, NULL_VALUE, TRUE_VALUE,
22};
23
24/// Hard limits from the calling convention (design §2.2, §7): closures pass
25/// user parameters in `x1..x7`; methods spend `x1` on `this`.
26pub const MAX_FUNCTION_PARAMETERS: usize = 7;
27pub const MAX_METHOD_PARAMETERS: usize = 6;
28
29/// `main`'s epilogue label, in the dialect's private-label spelling
30/// (`.Lmain_exit` on ELF, `Lmain_exit` on Mach-O).
31fn main_epilogue_label(dialect: AsmDialect) -> String {
32    format!("{}main_exit", dialect.local_label_prefix())
33}
34
35#[derive(Clone, Debug)]
36pub struct LowerError {
37    pub message: String,
38    pub span: Option<(usize, usize)>,
39}
40
41impl std::fmt::Display for LowerError {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        write!(f, "{}", self.message)
44    }
45}
46
47fn error<T>(message: impl Into<String>, span: &Span) -> Result<T, LowerError> {
48    Err(LowerError {
49        message: message.into(),
50        span: Some((span.start, span.end)),
51    })
52}
53
54/// Lowers a parsed program to a complete assembly module in the given
55/// dialect. `observe` selects the differential-testing build:
56/// `rt_observer_init(3)` at startup and one `rt_observe_result` before exit
57/// (design §10.2).
58pub fn lower_node(
59    source: &str,
60    node: &Node,
61    dialect: AsmDialect,
62    observe: bool,
63) -> Result<Assembly, LowerError> {
64    let program = match node {
65        Node::Program(program) => program,
66        _ => {
67            return Err(LowerError {
68                message: "lowering expects a full program".to_string(),
69                span: None,
70            })
71        }
72    };
73    let builtin_names: Vec<&str> = BuiltIns.iter().map(|builtin| builtin.name).collect();
74    validate_program(program, &builtin_names).map_err(|validation| LowerError {
75        message: validation.message,
76        span: Some((validation.span.start, validation.span.end)),
77    })?;
78
79    let mut symbols = SymbolTable::new();
80    for (index, builtin) in BuiltIns.iter().enumerate() {
81        symbols.define_builtin(index, builtin.name.to_string());
82    }
83
84    let exit_label = main_epilogue_label(dialect);
85    let mut lowerer = Lowerer {
86        source,
87        emitter: Emitter::new(dialect),
88        symbols,
89        epilogues: vec![exit_label.clone()],
90    };
91
92    let mut last_leaves_value = false;
93    for statement in &program.body {
94        last_leaves_value = lowerer.lower_statement(statement)?;
95    }
96    if !last_leaves_value {
97        // A program ending in a non-expression statement results in `null`
98        // (design §10.2).
99        lowerer.emitter.without_span(|emitter| {
100            emitter.load_imm64("x0", NULL_VALUE, "program result: null");
101        });
102    }
103
104    let globals_count = lowerer.symbols.num_definitions;
105    Ok(lowerer.emitter.finish(globals_count, &exit_label, observe))
106}
107
108/// Parses + lowers in one step for the CLI and tests; parse and lowering
109/// failures are joined into printable messages.
110pub fn compile_source(
111    source: &str,
112    dialect: AsmDialect,
113    observe: bool,
114) -> Result<Assembly, String> {
115    let node = parser::parse(source).map_err(|errors| errors.join("\n"))?;
116    lower_node(source, &node, dialect, observe).map_err(|lower| lower.message)
117}
118
119struct Lowerer<'a> {
120    source: &'a str,
121    emitter: Emitter,
122    symbols: SymbolTable,
123    /// Return statements branch to the top label; bottom is `main`'s
124    /// epilogue (top-level `return` ends the program).
125    epilogues: Vec<String>,
126}
127
128impl<'a> Lowerer<'a> {
129    fn snippet(&self, span: &Span) -> String {
130        let text = self.source.get(span.start..span.end).unwrap_or("");
131        let mut cleaned: String = text
132            .chars()
133            .take(40)
134            .map(|c| if c == '\n' || c == '\r' || c == '\t' { ' ' } else { c })
135            .collect();
136        if text.chars().count() > 40 {
137            cleaned.push('…');
138        }
139        cleaned
140    }
141
142    fn enter_scope(&mut self) {
143        let outer = std::mem::replace(&mut self.symbols, SymbolTable::new());
144        self.symbols = SymbolTable::new_enclosed_symbol_table(outer);
145    }
146
147    fn leave_scope(&mut self) {
148        let outer = self
149            .symbols
150            .outer
151            .as_ref()
152            .expect("leave_scope at the global scope")
153            .as_ref()
154            .clone();
155        self.symbols = outer;
156    }
157
158    /// Lowers one statement. Returns whether it leaves the statement value
159    /// in `x0` (only expression statements do; blocks and the program use
160    /// this for their `null` completion rule).
161    fn lower_statement(&mut self, statement: &Statement) -> Result<bool, LowerError> {
162        match statement {
163            Statement::Let(let_statement) => {
164                self.lower_let(let_statement)?;
165                Ok(false)
166            }
167            Statement::Return(return_statement) => {
168                let comment = format!("return {}", self.snippet(return_statement.argument.span()));
169                let target = self
170                    .epilogues
171                    .last()
172                    .expect("epilogue stack is never empty")
173                    .clone();
174                self.emitter.comment(&comment);
175                self.lower_expression(&return_statement.argument)?;
176                let span = return_statement.span.clone();
177                self.emitter.with_span(&span, |emitter| {
178                    emitter.ins(&format!("b {}", target));
179                });
180                Ok(true)
181            }
182            Statement::Expr(expression) => {
183                self.lower_expression(expression)?;
184                Ok(true)
185            }
186            Statement::Class(class) => {
187                self.lower_class(class)?;
188                Ok(false)
189            }
190            Statement::SetProperty(set) => {
191                let comment = self.snippet(&set.span);
192                self.emitter.comment(&comment);
193                self.lower_expression(&set.object)?;
194                self.emitter.with_span(&set.span.clone(), |emitter| {
195                    emitter.push_acc("object");
196                });
197                self.lower_expression(&set.value)?;
198                let (name_label, name_len) =
199                    self.emitter.intern_string(set.property.name.as_bytes());
200                let property = set.property.name.clone();
201                self.emitter.with_span(&set.span.clone(), |emitter| {
202                    emitter.ins_cmt("mov x3, x0", "value");
203                    emitter.pop("x0", "object");
204                    emitter.load_label_address("x1", &name_label, &property);
205                    emitter.load_imm64("x2", name_len, "");
206                    emitter.call_runtime("rt_set_property", "");
207                });
208                Ok(false)
209            }
210        }
211    }
212
213    fn lower_let(&mut self, let_statement: &Let) -> Result<(), LowerError> {
214        let name = let_statement.identifier.kind.to_string();
215        let comment = self.snippet(&let_statement.span);
216        self.emitter.comment(&comment);
217        // The right-hand side sees the previous binding, if any. Named
218        // recursion does not depend on predeclaring this slot: function
219        // bodies resolve their parser-provided name through Function scope.
220        self.lower_expression(&let_statement.expr)?;
221        let symbol = self.symbols.define(name.clone());
222        let span = let_statement.span.clone();
223        self.emitter.with_span(&span, |emitter| match symbol.scope {
224            SymbolScope::Global => {
225                emitter.global_store("x0", symbol.index, &format!("let {}", name))
226            }
227            _ => emitter.frame_store("x0", slot_offset(symbol.index), &format!("let {}", name)),
228        });
229        Ok(())
230    }
231
232    /// Block completion value (design §10.2 and the interpreter): the value
233    /// of the last expression statement, otherwise `null`.
234    fn lower_block_value(&mut self, block: &BlockStatement) -> Result<(), LowerError> {
235        let mut leaves_value = false;
236        for statement in &block.body {
237            leaves_value = self.lower_statement(statement)?;
238        }
239        if !leaves_value {
240            self.emitter.without_span(|emitter| {
241                emitter.load_imm64("x0", NULL_VALUE, "empty/valueless block: null");
242            });
243        }
244        Ok(())
245    }
246
247    fn load_symbol(&mut self, symbol: &Rc<Symbol>, span: &Span) -> Result<(), LowerError> {
248        let name = symbol.name.clone();
249        match symbol.scope {
250            SymbolScope::Global => {
251                let index = symbol.index;
252                self.emitter.with_span(&span.clone(), |emitter| {
253                    emitter.global_load("x0", index, &name);
254                });
255            }
256            SymbolScope::LOCAL => {
257                let index = symbol.index;
258                self.emitter.with_span(&span.clone(), |emitter| {
259                    emitter.frame_load("x0", slot_offset(index), &name);
260                });
261            }
262            SymbolScope::Builtin => {
263                let id = match builtin_id_for_symbol_index(symbol.index) {
264                    Some(id) => id,
265                    None => return error(format!("unknown builtin '{}'", name), span),
266                };
267                self.emitter.with_span(&span.clone(), |emitter| {
268                    emitter.load_imm64("x0", builtin_value(id), &format!("builtin {}", name));
269                });
270            }
271            SymbolScope::Free => {
272                let index = symbol.index;
273                self.emitter.with_span(&span.clone(), |emitter| {
274                    emitter.frame_load("x0", CLOSURE_SLOT_OFFSET, "current closure");
275                    emitter.load_imm64("x1", index as u64, &format!("free variable {}", name));
276                    emitter.call_runtime("rt_get_free", "");
277                });
278            }
279            SymbolScope::Function => {
280                // Named self-reference reads the spilled closure slot
281                // (design §7); requires `define_function_name` on scope entry.
282                self.emitter.with_span(&span.clone(), |emitter| {
283                    emitter.frame_load(
284                        "x0",
285                        CLOSURE_SLOT_OFFSET,
286                        &format!("current closure ({})", name),
287                    );
288                });
289            }
290        }
291        Ok(())
292    }
293
294    fn lower_expression(&mut self, expression: &Expression) -> Result<(), LowerError> {
295        match expression {
296            Expression::IDENTIFIER(identifier) => {
297                let symbol = match self.symbols.resolve(identifier.name.clone()) {
298                    Some(symbol) => symbol,
299                    None => {
300                        return error(
301                            format!("undefined variable '{}'", identifier.name),
302                            &identifier.span,
303                        )
304                    }
305                };
306                self.load_symbol(&symbol, &identifier.span)
307            }
308            Expression::LITERAL(literal) => self.lower_literal(literal),
309            Expression::PREFIX(prefix) => {
310                self.lower_expression(&prefix.operand)?;
311                let comment = self.snippet(&prefix.span);
312                let runtime_call = match prefix.op.kind {
313                    TokenKind::MINUS => "rt_minus",
314                    TokenKind::BANG => "rt_bang",
315                    _ => {
316                        return error(format!("unexpected prefix op: {}", prefix.op), &prefix.span)
317                    }
318                };
319                self.emitter.with_span(&prefix.span.clone(), |emitter| {
320                    emitter.call_runtime(runtime_call, &comment);
321                });
322                Ok(())
323            }
324            Expression::INFIX(infix) => self.lower_infix(infix),
325            Expression::IF(if_node) => {
326                self.lower_expression(&if_node.condition)?;
327                let else_label = self.emitter.new_label();
328                let end_label = self.emitter.new_label();
329                let comment = format!("if ({})", self.snippet(if_node.condition.span()));
330                self.emitter.with_span(&if_node.span.clone(), |emitter| {
331                    emitter.call_runtime("rt_truthy", &comment);
332                    emitter.ins(&format!("cbz x0, {}", else_label));
333                });
334                self.lower_block_value(&if_node.consequent)?;
335                self.emitter.with_span(&if_node.span.clone(), |emitter| {
336                    emitter.ins(&format!("b {}", end_label));
337                    emitter.label(&else_label);
338                });
339                match &if_node.alternate {
340                    Some(alternate) => self.lower_block_value(alternate)?,
341                    None => {
342                        self.emitter.without_span(|emitter| {
343                            emitter.load_imm64("x0", NULL_VALUE, "if without else: null");
344                        });
345                    }
346                }
347                self.emitter.with_span(&if_node.span.clone(), |emitter| {
348                    emitter.label(&end_label);
349                });
350                Ok(())
351            }
352            Expression::Index(index) => {
353                self.lower_expression(&index.object)?;
354                self.emitter.with_span(&index.span.clone(), |emitter| {
355                    emitter.push_acc("indexed object");
356                });
357                self.lower_expression(&index.index)?;
358                let comment = self.snippet(&index.span);
359                self.emitter.with_span(&index.span.clone(), |emitter| {
360                    emitter.ins_cmt("mov x1, x0", "index");
361                    emitter.pop("x0", "object");
362                    emitter.call_runtime("rt_index", &comment);
363                });
364                Ok(())
365            }
366            Expression::FUNCTION(function) => self.lower_function(function),
367            Expression::FunctionCall(call) => {
368                let argc = call.arguments.len();
369                let area = call_area_size(argc);
370                let comment = self.snippet(&call.span);
371                self.emitter.with_span(&call.span.clone(), |emitter| {
372                    emitter.comment(&comment);
373                    emitter.sp_sub(area);
374                });
375                self.lower_expression(&call.callee)?;
376                self.emitter.with_span(&call.span.clone(), |emitter| {
377                    emitter.sp_store("x0", 0, "callee");
378                });
379                for (index, argument) in call.arguments.iter().enumerate() {
380                    self.lower_expression(argument)?;
381                    self.emitter.with_span(&call.span.clone(), |emitter| {
382                        emitter.sp_store("x0", 8 * (index as u64 + 1), &format!("arg {}", index));
383                    });
384                }
385                self.emitter.with_span(&call.span.clone(), |emitter| {
386                    emitter.ins_cmt("ldr x0, [sp]", "callee");
387                    emitter.load_imm64("x1", argc as u64, "argc");
388                    emitter.sp_address("x2", 8, "argv");
389                    emitter.call_runtime("rt_call", "");
390                    emitter.sp_add(area);
391                });
392                Ok(())
393            }
394            Expression::This(this) => {
395                let symbol = match self.symbols.resolve("this".to_string()) {
396                    Some(symbol) => symbol,
397                    None => return error("this is only available inside a method", &this.span),
398                };
399                self.load_symbol(&symbol, &this.span)
400            }
401            Expression::Property(property) => {
402                self.lower_expression(&property.object)?;
403                let (name_label, name_len) = self
404                    .emitter
405                    .intern_string(property.property.name.as_bytes());
406                let name = property.property.name.clone();
407                self.emitter.with_span(&property.span.clone(), |emitter| {
408                    emitter.load_label_address("x1", &name_label, &name);
409                    emitter.load_imm64("x2", name_len, "");
410                    emitter.call_runtime("rt_get_property", &format!(".{}", name));
411                });
412                Ok(())
413            }
414            Expression::New(new_expression) => {
415                let symbol = match self.symbols.resolve(new_expression.callee.name.clone()) {
416                    Some(symbol) => symbol,
417                    None => {
418                        return error(
419                            format!("undefined variable '{}'", new_expression.callee.name),
420                            &new_expression.callee.span,
421                        )
422                    }
423                };
424                let argc = new_expression.arguments.len();
425                let area = call_area_size(argc);
426                let comment = self.snippet(&new_expression.span);
427                self.emitter
428                    .with_span(&new_expression.span.clone(), |emitter| {
429                        emitter.comment(&comment);
430                        emitter.sp_sub(area);
431                    });
432                self.load_symbol(&symbol, &new_expression.callee.span)?;
433                self.emitter
434                    .with_span(&new_expression.span.clone(), |emitter| {
435                        emitter.sp_store("x0", 0, "class");
436                    });
437                for (index, argument) in new_expression.arguments.iter().enumerate() {
438                    self.lower_expression(argument)?;
439                    self.emitter
440                        .with_span(&new_expression.span.clone(), |emitter| {
441                            emitter.sp_store(
442                                "x0",
443                                8 * (index as u64 + 1),
444                                &format!("arg {}", index),
445                            );
446                        });
447                }
448                // `new` lowers to rt_construct by AST node kind, never by the
449                // callee's runtime type (design §7.1).
450                self.emitter
451                    .with_span(&new_expression.span.clone(), |emitter| {
452                        emitter.ins_cmt("ldr x0, [sp]", "class");
453                        emitter.load_imm64("x1", argc as u64, "argc");
454                        emitter.sp_address("x2", 8, "argv");
455                        emitter.call_runtime("rt_construct", "");
456                        emitter.sp_add(area);
457                    });
458                Ok(())
459            }
460        }
461    }
462
463    fn lower_literal(&mut self, literal: &Literal) -> Result<(), LowerError> {
464        match literal {
465            Literal::Integer(integer) => {
466                let raw = integer.raw;
467                self.emitter.with_span(&integer.span.clone(), |emitter| {
468                    if i64_fits_smi(raw) {
469                        emitter.load_imm64("x0", (raw << 1) as u64, &format!("{}", raw));
470                    } else {
471                        emitter.load_imm64("x0", raw as u64, &format!("{} (beyond SMI)", raw));
472                        emitter.call_runtime("rt_box_int", "");
473                    }
474                });
475                Ok(())
476            }
477            Literal::Boolean(boolean) => {
478                let (value, text) =
479                    if boolean.raw { (TRUE_VALUE, "true") } else { (FALSE_VALUE, "false") };
480                self.emitter.with_span(&boolean.span.clone(), |emitter| {
481                    emitter.load_imm64("x0", value, text);
482                });
483                Ok(())
484            }
485            Literal::String(string) => {
486                let (label, len) = self.emitter.intern_string(string.raw.as_bytes());
487                let preview = self.snippet(&string.span);
488                self.emitter.with_span(&string.span.clone(), |emitter| {
489                    emitter.load_label_address("x0", &label, &preview);
490                    emitter.load_imm64("x1", len, "byte length");
491                    emitter.call_runtime("rt_string_from_bytes", "");
492                });
493                Ok(())
494            }
495            Literal::Array(array) => {
496                let len = array.elements.len();
497                let area = scratch_area_size(len);
498                self.emitter.with_span(&array.span.clone(), |emitter| {
499                    emitter.sp_sub(area);
500                });
501                for (index, element) in array.elements.iter().enumerate() {
502                    self.lower_expression(element)?;
503                    self.emitter.with_span(&array.span.clone(), |emitter| {
504                        emitter.sp_store("x0", 8 * index as u64, &format!("element {}", index));
505                    });
506                }
507                self.emitter.with_span(&array.span.clone(), |emitter| {
508                    emitter.sp_address("x0", 0, "element base");
509                    emitter.load_imm64("x1", len as u64, "element count");
510                    emitter.call_runtime("rt_array", "");
511                    emitter.sp_add(area);
512                });
513                Ok(())
514            }
515            Literal::Hash(hash) => {
516                let pairs = hash.elements.len();
517                let area = scratch_area_size(pairs * 2);
518                self.emitter.with_span(&hash.span.clone(), |emitter| {
519                    emitter.sp_sub(area);
520                });
521                for (index, (key, value)) in hash.elements.iter().enumerate() {
522                    self.lower_expression(key)?;
523                    self.emitter.with_span(&hash.span.clone(), |emitter| {
524                        emitter.sp_store("x0", 8 * (2 * index as u64), &format!("key {}", index));
525                    });
526                    self.lower_expression(value)?;
527                    self.emitter.with_span(&hash.span.clone(), |emitter| {
528                        emitter.sp_store(
529                            "x0",
530                            8 * (2 * index as u64 + 1),
531                            &format!("value {}", index),
532                        );
533                    });
534                }
535                self.emitter.with_span(&hash.span.clone(), |emitter| {
536                    emitter.sp_address("x0", 0, "pair base");
537                    emitter.load_imm64("x1", pairs as u64, "pair count");
538                    emitter.call_runtime("rt_hash", "");
539                    emitter.sp_add(area);
540                });
541                Ok(())
542            }
543        }
544    }
545
546    fn lower_infix(&mut self, infix: &parser::ast::BinaryExpression) -> Result<(), LowerError> {
547        let comment = self.snippet(&infix.span);
548        self.lower_expression(&infix.left)?;
549        self.emitter.with_span(&infix.span.clone(), |emitter| {
550            emitter.push_acc("left operand");
551        });
552        self.lower_expression(&infix.right)?;
553
554        // Preserve left-to-right evaluation. There is no rt_lt entry point,
555        // so compare `right > left` after evaluating both source operands.
556        if infix.op.kind == TokenKind::LT {
557            self.emitter.with_span(&infix.span.clone(), |emitter| {
558                emitter.pop("x1", "left operand");
559                emitter.call_runtime("rt_gt", &comment);
560            });
561            return Ok(());
562        }
563
564        let runtime_call = match infix.op.kind {
565            TokenKind::PLUS => "rt_add",
566            TokenKind::MINUS => "rt_sub",
567            TokenKind::ASTERISK => "rt_mul",
568            TokenKind::SLASH => "rt_div",
569            TokenKind::GT => "rt_gt",
570            TokenKind::EQ => "rt_eq",
571            TokenKind::NotEq => "rt_neq",
572            _ => return error(format!("unexpected infix op: {}", infix.op), &infix.span),
573        };
574
575        if infix.op.kind == TokenKind::PLUS {
576            // SMI fast path (design §5.2): both bit0 clear, `adds` whose V
577            // flag only signals SMI-range overflow; anything else falls back
578            // to rt_add for checked i64 + string concat.
579            let slow_label = self.emitter.new_label();
580            let done_label = self.emitter.new_label();
581            self.emitter.with_span(&infix.span.clone(), |emitter| {
582                emitter.ins_cmt("mov x1, x0", "right operand");
583                emitter.pop("x0", "left operand");
584                emitter.ins_cmt("orr x8, x0, x1", "SMI check on both bit0");
585                emitter.ins(&format!("tbnz x8, #0, {}", slow_label));
586                emitter.ins_cmt("adds x8, x0, x1", "(a<<1)+(b<<1) = (a+b)<<1");
587                emitter.ins(&format!("bvs {}", slow_label));
588                emitter.ins("mov x0, x8");
589                emitter.ins(&format!("b {}", done_label));
590                emitter.label(&slow_label);
591                emitter.call_runtime(runtime_call, &comment);
592                emitter.label(&done_label);
593            });
594            return Ok(());
595        }
596
597        self.emitter.with_span(&infix.span.clone(), |emitter| {
598            emitter.ins_cmt("mov x1, x0", "right operand");
599            emitter.pop("x0", "left operand");
600            emitter.call_runtime(runtime_call, &comment);
601        });
602        Ok(())
603    }
604
605    fn lower_function(&mut self, function: &FunctionDeclaration) -> Result<(), LowerError> {
606        if function.params.len() > MAX_FUNCTION_PARAMETERS {
607            return error(
608                format!("functions accept at most {} parameters", MAX_FUNCTION_PARAMETERS),
609                &function.span,
610            );
611        }
612        self.enter_scope();
613        // Named self-reference (design §7): the name resolves to the closure
614        // slot instead of an outer binding.
615        if !function.name.is_empty() {
616            self.symbols.define_function_name(function.name.clone());
617        }
618        let mut parameter_names = Vec::with_capacity(function.params.len());
619        for parameter in &function.params {
620            self.symbols.define(parameter.name.clone());
621            parameter_names.push(parameter.name.clone());
622        }
623
624        let label = self.emitter.new_function_label();
625        let epilogue_label = format!("{}_ret", label);
626        let display_name = if function.name.is_empty() {
627            "fn".to_string()
628        } else {
629            format!("fn {}", function.name)
630        };
631        let signature = format!("{}({})", display_name, parameter_names.join(", "));
632
633        self.epilogues.push(epilogue_label.clone());
634        self.emitter.begin_function();
635        self.lower_block_value(&function.body)?;
636        self.epilogues.pop();
637
638        let num_definitions = self.symbols.num_definitions;
639        let free_symbols = self.symbols.free_symbols.clone();
640        self.emitter.end_function(FunctionFrame {
641            label: label.clone(),
642            comment: signature.clone(),
643            num_parameters: function.params.len(),
644            num_definitions,
645            epilogue_label,
646            parameter_names,
647        });
648        self.leave_scope();
649
650        self.emit_closure(&label, function.params.len(), &free_symbols, &signature, &function.span)
651    }
652
653    fn lower_method(
654        &mut self,
655        class_name: &str,
656        method: &MethodDefinition,
657    ) -> Result<(), LowerError> {
658        if method.params.len() > MAX_METHOD_PARAMETERS {
659            return error(
660                format!("methods accept at most {} parameters", MAX_METHOD_PARAMETERS),
661                &method.span,
662            );
663        }
664        self.enter_scope();
665        // `this` is symbol 0, before the declared parameters, matching
666        // compile_method in the bytecode compiler.
667        self.symbols.define("this".to_string());
668        let mut parameter_names = vec!["this".to_string()];
669        for parameter in &method.params {
670            self.symbols.define(parameter.name.clone());
671            parameter_names.push(parameter.name.clone());
672        }
673
674        let label = self.emitter.new_function_label();
675        let epilogue_label = format!("{}_ret", label);
676        let signature =
677            format!("{}.{}({})", class_name, method.name.name, parameter_names.join(", "));
678
679        self.epilogues.push(epilogue_label.clone());
680        self.emitter.begin_function();
681        self.lower_block_value(&method.body)?;
682        if method.kind == MethodKind::Constructor {
683            // Constructors always evaluate to their instance (design §7.2);
684            // `return` inside them is already rejected by validation.
685            self.emitter.without_span(|emitter| {
686                emitter.frame_load("x0", slot_offset(0), "constructor returns this");
687            });
688        }
689        self.epilogues.pop();
690
691        let num_definitions = self.symbols.num_definitions;
692        let free_symbols = self.symbols.free_symbols.clone();
693        self.emitter.end_function(FunctionFrame {
694            label: label.clone(),
695            comment: signature.clone(),
696            num_parameters: method.params.len() + 1,
697            num_definitions,
698            epilogue_label,
699            parameter_names,
700        });
701        self.leave_scope();
702
703        self.emit_closure(&label, method.params.len() + 1, &free_symbols, &signature, &method.span)
704    }
705
706    /// Builds the closure value in the parent scope: captured values are
707    /// loaded in `free_symbols` order into a scratch area, then handed to
708    /// `rt_closure` (design §6.1 step 4).
709    fn emit_closure(
710        &mut self,
711        code_label: &str,
712        num_parameters: usize,
713        free_symbols: &[Rc<Symbol>],
714        signature: &str,
715        span: &Span,
716    ) -> Result<(), LowerError> {
717        let num_free = free_symbols.len();
718        let area = scratch_area_size(num_free);
719        self.emitter.with_span(&span.clone(), |emitter| {
720            emitter.sp_sub(area);
721        });
722        for (index, symbol) in free_symbols.iter().enumerate() {
723            self.load_symbol(symbol, span)?;
724            let name = symbol.name.clone();
725            self.emitter.with_span(&span.clone(), |emitter| {
726                emitter.sp_store("x0", 8 * index as u64, &format!("capture {}", name));
727            });
728        }
729        self.emitter.with_span(&span.clone(), |emitter| {
730            emitter.load_label_address("x0", code_label, signature);
731            emitter.load_imm64("x1", num_parameters as u64, "num_parameters");
732            emitter.sp_address("x2", 0, "captured values");
733            emitter.load_imm64("x3", num_free as u64, "num_free");
734            emitter.call_runtime("rt_closure", "");
735            emitter.sp_add(area);
736        });
737        Ok(())
738    }
739
740    fn lower_class(&mut self, class: &ClassDeclaration) -> Result<(), LowerError> {
741        let class_name = class.name.name.clone();
742        let comment = format!("class {}", class_name);
743        self.emitter.comment(&comment);
744        // Define first so methods can reference the class (e.g. `new C()`
745        // in a method body), matching the bytecode compiler.
746        let symbol = self.symbols.define(class_name.clone());
747        let (name_label, name_len) = self.emitter.intern_string(class_name.as_bytes());
748        self.emitter.with_span(&class.span.clone(), |emitter| {
749            emitter.load_label_address("x0", &name_label, &class_name);
750            emitter.load_imm64("x1", name_len, "");
751            emitter.call_runtime("rt_class", "");
752            emitter.push_acc("class value");
753        });
754        for method in &class.methods {
755            self.lower_method(&class_name, method)?;
756            let (method_label, method_len) =
757                self.emitter.intern_string(method.name.name.as_bytes());
758            let is_constructor = method.kind == MethodKind::Constructor;
759            let method_name = method.name.name.clone();
760            self.emitter.with_span(&method.span.clone(), |emitter| {
761                emitter.ins_cmt("mov x3, x0", "method closure");
762                emitter.ins_cmt("ldr x0, [sp]", "class value (kept pushed)");
763                emitter.load_label_address("x1", &method_label, &method_name);
764                emitter.load_imm64("x2", method_len, "");
765                emitter.load_imm64("x4", if is_constructor { 1 } else { 0 }, "is_ctor");
766                emitter.call_runtime("rt_class_add_method", "");
767            });
768        }
769        self.emitter.with_span(&class.span.clone(), |emitter| {
770            emitter.pop("x0", "class value");
771        });
772        let span = class.span.clone();
773        self.emitter.with_span(&span, |emitter| match symbol.scope {
774            SymbolScope::Global => {
775                emitter.global_store("x0", symbol.index, &format!("class {}", class_name))
776            }
777            _ => emitter.frame_store(
778                "x0",
779                slot_offset(symbol.index),
780                &format!("class {}", class_name),
781            ),
782        });
783        Ok(())
784    }
785}