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