Files
jsrt/compiler/src/flatten.rs
T
2026-10-26 12:39:58 +00:00

119 lines
3.6 KiB
Rust

//! QuickJS bytecode represents each module as a hierarchy. Each function has a set of child
//! functions that reference captured variables in their parent
//!
//! This module:
//! - flattens each module's hierarchy into one list for the program
//! - assigns an integer idx to each function
//! - converts child ownership to a reference by-idx
//! - extracts closure captures into a separate vec
//!
//! Fn 0 is the program's entry, which link.rs writes: it runs every module's body
use crate::libqjs::parse::{
Atom, AtomRef, ClosureVar, ExportInfo, FuncKind, Function, ImportInfo, Instr, JsValue,
QuickJSBinary,
};
/// The entry fn (see link.rs)
pub const ENTRY_FN: usize = 0;
#[derive(Debug)]
pub struct FlatFn {
pub name: AtomRef,
pub kind: FuncKind, // a normal fn, or one that suspends: async, a generator, or both (see transform/async.rs)
pub arg_count: usize,
pub local_count: usize,
pub cpool: Vec<JsValue>,
pub code: Vec<Instr>,
}
/// A module, as QuickJS gives it, with its body as a flat fn
#[derive(Debug)]
pub struct FlatModule {
pub name: String, // the name that it was loaded by (see `libqjs::compile`)
pub requests: Vec<String>, // the modules that it imports from, as the source writes them
pub exports: Vec<ExportInfo>,
pub star_exports: Vec<usize>, // `export * from`: each an index into `requests`
pub imports: Vec<ImportInfo>,
pub has_top_level_await: bool,
pub body: usize, // the fn of its body. Its captures are the module's bindings
}
/// Flattens every module of the binary into one list of fns. Gives the fns, each fn's captures
/// from its parent, the atoms, and the modules
pub fn flatten(
bin: QuickJSBinary,
) -> (
Vec<FlatFn>,
Vec<Vec<ClosureVar>>,
Vec<Atom>,
Vec<FlatModule>,
) {
let text = |atom: AtomRef| atom.text(&bin.atoms);
let mut fns = vec![entry_placeholder()];
let mut captures = vec![vec![]];
let mut modules = vec![];
for module in bin.modules {
let has_top_level_await = module.has_top_level_await;
let body = walk(module.root_fn, &mut fns, &mut captures);
// QuickJS makes every module's body async. It only suspends if it awaits at its top
if !has_top_level_await {
fns[body].kind = FuncKind::Normal;
}
modules.push(FlatModule {
name: text(module.name),
requests: module.requests.iter().map(|&r| text(r)).collect(),
exports: module.exports,
star_exports: module.star_exports,
imports: module.imports,
has_top_level_await,
body,
});
}
(fns, captures, bin.atoms, modules)
}
/// Fn 0, before link.rs writes it
fn entry_placeholder() -> FlatFn {
FlatFn {
name: AtomRef::Predefined(0),
kind: FuncKind::Normal,
arg_count: 0,
local_count: 0,
cpool: vec![],
code: vec![],
}
}
fn walk(func: Function, flat_fns: &mut Vec<FlatFn>, captures: &mut Vec<Vec<ClosureVar>>) -> usize {
let id = flat_fns.len();
flat_fns.push(FlatFn {
name: func.name,
kind: func.flags.func_kind,
arg_count: func.arg_count,
local_count: func.var_count,
cpool: vec![],
code: func.byte_code,
});
captures.push(func.closure_vars);
// Every direct child ownership is replaced by a by-idx reference
let cpool = func
.cpool
.into_iter()
.map(|c| match c {
JsValue::Function(child) => JsValue::FnByIdx(walk(child, flat_fns, captures)),
other => other,
})
.collect();
flat_fns[id].cpool = cpool;
id
}