Rewrite with working async

This commit is contained in:
2026-10-16 08:00:06 +00:00
parent d0c2f291e9
commit 1e4647347f
36 changed files with 5106 additions and 654 deletions
+71 -15
View File
@@ -7,15 +7,25 @@
//! - converts child ownership to a reference by-idx
//! - extracts closure captures into a separate vec
//!
//! Also adds a synthetic entry point function that calls the top level fn generated by QuickJS
//! Also adds a synthetic entry point function that calls the top level fn generated by QuickJS,
//! and makes the module's bindings its locals
use std::collections::HashMap;
use crate::libqjs::parse::{
Atom, AtomRef, ClosureVar, Function, ImmOperand, Instr, JsValue, QuickJSBinary, opcodes::OpCode,
Atom, AtomRef, ClosureKind, ClosureVar, FuncKind, Function, ImmOperand, Instr, JsValue,
QuickJSBinary, opcodes::OpCode,
};
/// The module's bindings, which are locals of the entry fn, by name. The compiler reads the
/// stdlib's by name where it needs one: the classes whose prototypes the primitives use, and
/// what the host calls when an IO op completes
pub type ModuleBindings = HashMap<String, usize>;
#[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>,
@@ -27,14 +37,45 @@ pub struct FlatFn {
/// Returns:
/// - flat_fns[i] = the i'th flattened function
/// - captures[i] = the i'th fn's captures from its parent
pub fn flatten(bin: QuickJSBinary) -> (Vec<FlatFn>, Vec<Vec<ClosureVar>>, Vec<Atom>) {
/// - the module's bindings, as locals of the entry fn
pub fn flatten(
bin: QuickJSBinary,
) -> (Vec<FlatFn>, Vec<Vec<ClosureVar>>, Vec<Atom>, ModuleBindings) {
let mut fns = vec![];
let mut captures = vec![];
walk(bin.root_mod.root_fn, &mut fns, &mut captures);
add_entry_point(&mut fns, &mut captures);
let bindings = bind_module(&mut fns, &mut captures, &bin.atoms);
(fns, captures, bin.atoms)
(fns, captures, bin.atoms, bindings)
}
/// Make each binding of the module a local of the entry fn, after its own two. The module fn
/// (`<eval>`, the first fn) captures them, as any fn captures its parent's locals, so they are
/// cells (see transform/closure.rs)
fn bind_module(
fns: &mut [FlatFn],
captures: &mut [Vec<ClosureVar>],
atoms: &[Atom],
) -> ModuleBindings {
let entry = fns.len() - 1;
let mut bindings = ModuleBindings::new();
for capture in &mut captures[0] {
if capture.flags.closure_kind != ClosureKind::ModuleDecl {
continue;
}
let local = fns[entry].local_count;
fns[entry].local_count += 1;
capture.flags.closure_kind = ClosureKind::Local;
capture.var_idx = local;
bindings.insert(capture.name.text(atoms), local);
}
bindings
}
fn walk(func: Function, flat_fns: &mut Vec<FlatFn>, captures: &mut Vec<Vec<ClosureVar>>) -> usize {
@@ -42,6 +83,7 @@ fn walk(func: Function, flat_fns: &mut Vec<FlatFn>, captures: &mut Vec<Vec<Closu
flat_fns.push(FlatFn {
name: func.name,
kind: func.flags.func_kind,
arg_count: func.arg_count,
local_count: func.var_count,
cpool: vec![],
@@ -73,11 +115,20 @@ fn walk(func: Function, flat_fns: &mut Vec<FlatFn>, captures: &mut Vec<Vec<Closu
/// ...
/// }
///
/// <eval>.call(true); // Link phase (`this = true`)
/// <eval>.call(false); // Run phase (`this = false`)
/// // Link phase (`this = true`), then run phase (`this = false`)
/// let phase = true;
/// while (true) {
/// <eval>.call(phase);
/// if (!phase) break;
/// phase = false;
/// }
/// }
/// ```
///
/// Both phases call `<eval>` from one call instr. Infer names the objects that a walk makes
/// by its call site, so with two call instrs, each phase would make its own copy of every
/// module-level object, and each module binding would hold one of two copies
///
/// This gets called by C main()
fn add_entry_point(fns: &mut Vec<FlatFn>, captures: &mut Vec<Vec<ClosureVar>>) {
let instr = |pc, op, imm| Instr { pc, op, imm };
@@ -86,20 +137,25 @@ fn add_entry_point(fns: &mut Vec<FlatFn>, captures: &mut Vec<Vec<ClosureVar>>) {
instr(0, OpCode::Fclosure8, ImmOperand::Const(0)),
instr(1, OpCode::PutLoc, ImmOperand::Loc(0)),
instr(2, OpCode::PushTrue, ImmOperand::None),
instr(3, OpCode::GetLoc, ImmOperand::Loc(0)),
instr(4, OpCode::CallMethod, ImmOperand::Npop(0)),
instr(5, OpCode::Drop, ImmOperand::None),
instr(6, OpCode::PushFalse, ImmOperand::None),
instr(7, OpCode::GetLoc, ImmOperand::Loc(0)),
instr(8, OpCode::CallMethod, ImmOperand::Npop(0)),
instr(9, OpCode::Drop, ImmOperand::None),
instr(10, OpCode::ReturnUndef, ImmOperand::None),
instr(3, OpCode::PutLoc, ImmOperand::Loc(1)),
// Loop head: one call for both phases
instr(4, OpCode::GetLoc, ImmOperand::Loc(1)),
instr(5, OpCode::GetLoc, ImmOperand::Loc(0)),
instr(6, OpCode::CallMethod, ImmOperand::Npop(0)),
instr(7, OpCode::Drop, ImmOperand::None),
instr(8, OpCode::GetLoc, ImmOperand::Loc(1)),
instr(9, OpCode::IfFalse8, ImmOperand::Label { target: 13 }),
instr(10, OpCode::PushFalse, ImmOperand::None),
instr(11, OpCode::PutLoc, ImmOperand::Loc(1)),
instr(12, OpCode::Goto8, ImmOperand::Label { target: 4 }),
instr(13, OpCode::ReturnUndef, ImmOperand::None),
];
fns.push(FlatFn {
name: AtomRef::Predefined(0),
kind: FuncKind::Normal,
arg_count: 0,
local_count: 1,
local_count: 2, // <eval>, and the phase
cpool: vec![JsValue::FnByIdx(0)], // First fn is the top level <eval>
code,
});