//! 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, pub code: Vec, } /// 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, // the modules that it imports from, as the source writes them pub exports: Vec, pub star_exports: Vec, // `export * from`: each an index into `requests` pub imports: Vec, 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, Vec>, Vec, Vec, ) { 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, captures: &mut Vec>) -> 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 }