119 lines
3.6 KiB
Rust
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
|
|
}
|