Files
endorphin-aster-vm/crates/compiler/src/codegen.rs
T

322 lines
11 KiB
Rust

use endor_core::{
CodeLayout, DecodedInstruction, EndorCode, EndorNumber, Instruction, ObjectRef, Opcode, Value,
Vm, VmError,
};
use frontend::ast::{AstRoot, Decl, DeclKind, Expr, ExprKind, Stmt, StmtKind, UnaryOp};
use frontend::{Diagnostic, DiagnosticKind, SourceUnit, Span};
use crate::{NameBinding, Scope, ScopeKind, SymbolTable, diagnostic};
/// Validated, heap-independent executable template. Nested code constants
/// remain templates until load(), so no ObjectRef is shared across VM heaps.
#[derive(Debug, Clone)]
pub struct CompiledCode {
code: EndorCode,
children: Vec<(usize, CompiledCode)>,
}
impl CompiledCode {
pub fn instructions(&self) -> &[DecodedInstruction] {
self.code.instructions()
}
pub fn layout(&self) -> &CodeLayout {
self.code.layout()
}
pub fn name(&self) -> &str {
self.code.name()
}
pub fn filename(&self) -> &str {
self.code.filename()
}
pub fn first_line(&self) -> usize {
self.code.first_line()
}
pub fn children(&self) -> impl Iterator<Item = &CompiledCode> {
self.children.iter().map(|(_, child)| child)
}
/// Return one owned code-object root; release it after execution. The
/// parent retains its nested code constants. Temporary child roots are
/// released on both success and failure, including partial recursive loads.
pub fn load(&self, vm: &mut Vm) -> Result<ObjectRef, VmError> {
let mut roots = Vec::new();
let result = (|| {
let mut constants = self.code.constants().to_vec();
for (index, child) in &self.children {
let reference = child.load(vm)?;
roots.push(reference);
constants[*index] = Value::Object(reference);
}
let code = EndorCode::with_layout(
self.code.bytecode().to_vec(),
constants,
self.code.layout().clone(),
)?
.with_location(
self.code.name(),
self.code.filename(),
self.code.first_line(),
);
vm.new_code(code)
})();
for reference in roots {
vm.release(reference)
.expect("loader owns each temporary code root");
}
result
}
}
fn error(span: Span, message: impl Into<String>) -> Diagnostic {
diagnostic(DiagnosticKind::Compilation, span, message)
}
pub(crate) fn generate(
root: &AstRoot,
source: &SourceUnit,
symbols: &SymbolTable,
) -> Result<CompiledCode, Diagnostic> {
let mut emitter = Emitter::new(&symbols.root, source);
match root {
AstRoot::Module(program) => {
for declaration in &program.declarations {
emitter.declaration(declaration, symbols)?;
}
emitter.constant(Value::Nil)?;
emitter.simple(Opcode::ReturnValue);
}
AstRoot::Expression(expression) => {
emitter.expression(expression)?;
emitter.simple(Opcode::ReturnValue);
}
AstRoot::Interactive(input) => {
for statement in &input.statements {
if let StmtKind::Expression(expression) = &statement.kind {
emitter.expression(expression)?;
} else {
emitter.statement(statement, symbols)?;
emitter.constant(Value::Nil)?;
}
}
emitter.simple(Opcode::ReturnValue);
}
}
emitter.finish()
}
struct Emitter<'a> {
scope: &'a Scope,
source: &'a SourceUnit,
instructions: Vec<Instruction>,
constants: Vec<Value>,
children: Vec<(usize, CompiledCode)>,
}
impl<'a> Emitter<'a> {
fn new(scope: &'a Scope, source: &'a SourceUnit) -> Self {
Self {
scope,
source,
instructions: Vec::new(),
constants: Vec::new(),
children: Vec::new(),
}
}
fn emit(&mut self, opcode: Opcode, argument: usize) -> Result<(), Diagnostic> {
let argument = u32::try_from(argument)
.map_err(|_| error(self.scope.span, "bytecode operand exceeds u32"))?;
self.instructions.push(Instruction::new(opcode, argument));
Ok(())
}
fn simple(&mut self, opcode: Opcode) {
self.instructions.push(Instruction::simple(opcode));
}
fn constant(&mut self, value: Value) -> Result<(), Diagnostic> {
let index = self.constants.len();
self.constants.push(value);
self.emit(Opcode::LoadConst, index)
}
fn name_index(&self, name: &str) -> usize {
self.scope
.layout
.names
.iter()
.position(|candidate| candidate == name)
.expect("analysis interns every emitted name")
}
fn declaration(&mut self, declaration: &Decl, symbols: &SymbolTable) -> Result<(), Diagnostic> {
let DeclKind::Function(function) = &declaration.kind;
let scope = symbols
.functions
.iter()
.find(|scope| scope.span == function.span && scope.name == function.name.text)
.expect("analysis creates each function scope");
let mut body = Emitter::new(scope, self.source);
for statement in &function.body.statements {
body.statement(statement, symbols)?;
}
if !matches!(
function
.body
.statements
.last()
.map(|statement| &statement.kind),
Some(StmtKind::Return(_))
) {
body.constant(Value::Nil)?;
body.simple(Opcode::ReturnValue);
}
let child = body.finish()?;
let index = self.constants.len();
self.children.push((index, child));
self.constant(Value::Nil)?; // Replaced by a managed code object during load().
self.instructions.push(Instruction::make_function(0, 0));
self.emit(Opcode::StoreGlobal, self.name_index(&function.name.text))
}
fn store_binding(&mut self, name: &str, value: &Expr) -> Result<(), Diagnostic> {
self.expression(value)?;
if self.scope.kind == ScopeKind::Function {
let index = self
.scope
.layout
.locals
.iter()
.position(|local| local == name)
.expect("analysis allocates binding and assignment targets");
self.emit(Opcode::StoreFast, index)?;
} else {
self.emit(Opcode::StoreGlobal, self.name_index(name))?;
}
Ok(())
}
fn statement(&mut self, statement: &Stmt, symbols: &SymbolTable) -> Result<(), Diagnostic> {
match &statement.kind {
StmtKind::Binding(binding) => self.store_binding(&binding.name.text, &binding.value)?,
StmtKind::Assignment(assignment) => {
self.store_binding(&assignment.name.text, &assignment.value)?
}
StmtKind::Return(statement) => {
if let Some(value) = &statement.value {
self.expression(value)?;
} else {
self.constant(Value::Nil)?;
}
self.simple(Opcode::ReturnValue);
}
StmtKind::Expression(expression) => {
self.expression(expression)?;
self.simple(Opcode::PopTop);
}
StmtKind::Declaration(declaration) => self.declaration(declaration, symbols)?,
}
Ok(())
}
fn expression(&mut self, expression: &Expr) -> Result<(), Diagnostic> {
match &expression.kind {
ExprKind::String(value) => self.constant(Value::Text(value.clone()))?,
ExprKind::Number(spelling) => self.number(spelling, expression.span)?,
ExprKind::Name(name) => {
let reference = self
.scope
.references
.iter()
.find(|reference| reference.span == expression.span && &reference.name == name)
.expect("analysis resolves every name expression");
let (opcode, index) = match reference.binding {
NameBinding::Local(index) => (Opcode::LoadFast, index),
NameBinding::Global(index) => (Opcode::LoadGlobal, index),
};
self.emit(opcode, index)?;
}
ExprKind::Call { callee, arguments } => {
self.expression(callee)?;
self.simple(Opcode::PushNull);
for argument in arguments {
self.expression(argument)?;
}
self.emit(Opcode::Call, arguments.len())?;
}
ExprKind::Member { object, name } => {
self.expression(object)?;
self.emit(Opcode::LoadAttr, self.name_index(&name.text))?;
}
ExprKind::Unary { .. } => {
// Literal sign folding preserves arbitrarily large integers
// and decimal spelling; this is not general numeric arithmetic.
let Some(spelling) = signed_literal(expression, false) else {
return Err(error(
expression.span,
"numeric unary operations are not implemented; only signed number literals are supported",
));
};
self.number(&spelling, expression.span)?;
}
ExprKind::Binary { .. } => {
return Err(error(
expression.span,
"numeric arithmetic bytecode is not implemented yet",
));
}
ExprKind::Symbol(_) => {
return Err(error(
expression.span,
"symbol runtime values are not implemented yet",
));
}
}
Ok(())
}
fn number(&mut self, spelling: &str, span: Span) -> Result<(), Diagnostic> {
let value = if spelling.contains('.') {
EndorNumber::decimal(spelling)
} else {
EndorNumber::integer(spelling)
}
.map_err(|failure| error(span, failure.to_string()))?;
self.constant(Value::Number(value))
}
fn finish(self) -> Result<CompiledCode, Diagnostic> {
let line = self
.scope
.span
.location(self.source.text())
.map_or(1, |location| location.line);
let code = EndorCode::from_instructions(
&self.instructions,
self.constants,
self.scope.layout.clone(),
)
.map_err(|failure| error(self.scope.span, failure.to_string()))?
.with_location(&self.scope.name, self.source.origin().to_string(), line);
Ok(CompiledCode {
code,
children: self.children,
})
}
}
fn signed_literal(expression: &Expr, negative: bool) -> Option<String> {
match &expression.kind {
ExprKind::Number(spelling) => Some(if negative {
format!("-{spelling}")
} else {
spelling.clone()
}),
ExprKind::Unary {
operation: UnaryOp::Negate,
operand,
} => signed_literal(operand, !negative),
_ => None,
}
}