Compare commits

..
2 Commits
Author SHA1 Message Date
uditdey 2f21347088 Invert event loop 2026-10-08 08:53:11 +00:00
uditdey c4d0bab3d9 Silence many warnings 2026-10-08 07:34:56 +00:00
12 changed files with 244 additions and 135 deletions
+30
View File
@@ -97,6 +97,36 @@ impl<'a> Emitter<'a> {
let value = operand(self, FIRST_ARG as usize, self.rep(&self.cache.protos[kind as usize])); let value = operand(self, FIRST_ARG as usize, self.rep(&self.cache.protos[kind as usize]));
(format!("({} = {value}, (undefined_t){{0}})", proto_global(kind)), Type::Undefined) (format!("({} = {value}, (undefined_t){{0}})", proto_global(kind)), Type::Undefined)
} }
// Keeps the fn where `jsrt_complete` finds it
Code::HostLink => {
let value = operand(self, FIRST_ARG as usize, self.rep(&self.cache.host_complete));
(format!("({HOST_COMPLETE_GLOBAL} = {value}, (undefined_t){{0}})"), Type::Undefined)
}
} }
} }
/// The body of `jsrt_complete(h)`: a call of the fn that the stdlib links
/// for the host, with op `h` (see `infer::host_calls()`)
pub(super) fn host_complete(&mut self) -> String {
let ty = self.cache.host_complete.clone();
let mut out = String::new();
if ty == Type::Unknown {
return out;
}
let rep = self.rep(&ty);
let operands = [(HOST_COMPLETE_GLOBAL.to_string(), rep), ("(undefined_t){0}".into(), Rep::Undefined), ("h".into(), Rep::Int)];
self.dispatch(HOST_COMPLETE_GLOBAL, &ty, "jsrt_type_error();", &mut out, |e, member, callee, rep| {
let Type::Obj(cell) = member else { return None };
let mut operands = operands.clone();
operands[0] = (callee.into(), rep);
let arg_types = [member.clone(), Type::Undefined, Type::Int];
Some(format!("{};", e.call_code(cell.code()?, HOST_COMPLETE, &arg_types, &operands).0))
});
out
}
} }
+1
View File
@@ -121,6 +121,7 @@ pub(super) fn cell_sig(cell: &Cell, class: &HashMap<Cell, CellNum>, sig: &mut Ve
Code::Fn(id) => [1, id as u32], Code::Fn(id) => [1, id as u32],
Code::Native(id) => [2, id as u32], Code::Native(id) => [2, id as u32],
Code::ProtoLink(kind) => [3, kind as u32], Code::ProtoLink(kind) => [3, kind as u32],
Code::HostLink => [5, 0],
}), }),
ObjKind::Array { elems } => { ObjKind::Array { elems } => {
sig.push(4); sig.push(4);
+1
View File
@@ -69,6 +69,7 @@ impl<'a> Emitter<'a> {
JsValue::String(s) => self.string_lit(&js_string_text(s)), JsValue::String(s) => self.string_lit(&js_string_text(s)),
JsValue::NativeFn(id) => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::Native(*id))))), JsValue::NativeFn(id) => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::Native(*id))))),
JsValue::ProtoLink(kind) => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::ProtoLink(*kind))))), JsValue::ProtoLink(kind) => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::ProtoLink(*kind))))),
JsValue::HostLink => format!("&{}", builtin_name(self.num(self.cache.builtin(Code::HostLink)))),
_ => format!("({}){{0}}", self.c_type(rep)), _ => format!("({}){{0}}", self.c_type(rep)),
}, },
Undefined | Null => format!("({}){{0}}", self.c_type(rep)), Undefined | Null => format!("({}){{0}}", self.c_type(rep)),
+14 -4
View File
@@ -18,7 +18,7 @@ use std::{
use crate::{ use crate::{
infer::{ infer::{
HOST, InferResult, Place, TypeCache, HOST, HOST_COMPLETE, InferResult, Place, TypeCache,
cells::{Cell, Code, LENGTH_FIELD, ObjKind, read_value}, cells::{Cell, Code, LENGTH_FIELD, ObjKind, read_value},
types::Type, types::Type,
}, },
@@ -94,11 +94,16 @@ pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom]) -> String {
} }
} }
// The walks that the code calls, from the entry. The host calls the entry if cache.host_complete != Type::Unknown {
// with nothing: its env and `this` are undefined let _ = writeln!(statics, "static {} {HOST_COMPLETE_GLOBAL};", e.c_type(e.rep(&cache.host_complete)));
}
// The walks that the code calls, from the host's calls. The host calls the
// entry with nothing: its env and `this` are undefined
let entry = cache.find(fns.len() - 1, HOST, &[], fns); let entry = cache.find(fns.len() - 1, HOST, &[], fns);
let entry_args = vec!["(undefined_t){0}"; cache.walks[entry].req.arg_types.len()].join(", "); let entry_args = vec!["(undefined_t){0}"; cache.walks[entry].req.arg_types.len()].join(", ");
e.use_walk(entry); e.use_walk(entry);
let complete = e.host_complete();
let mut fn_protos = String::new(); let mut fn_protos = String::new();
let mut bodies = String::new(); let mut bodies = String::new();
@@ -127,7 +132,9 @@ pub fn emit(fns: &[RegFn], cache: &TypeCache, atoms: &[Atom]) -> String {
e.helper_protos, e.helper_protos,
e.helper_defs e.helper_defs
); );
let _ = writeln!(out, "int main(void) {{\n{init} {}({entry_args});\n return 0;\n}}", e.fn_name(entry)); // What the host calls (see `lib/rt/jsrt.h`)
let _ = writeln!(out, "void jsrt_start(void) {{\n{init} {}({entry_args});\n}}\n", e.fn_name(entry));
let _ = writeln!(out, "void jsrt_complete(int32_t h) {{\n{complete}}}");
out out
} }
@@ -287,6 +294,9 @@ fn builtin_name(id: CellNum) -> String {
format!("b{id}") format!("b{id}")
} }
/// The global that holds what the host calls when an IO op completes
const HOST_COMPLETE_GLOBAL: &str = "jsrt_host_complete";
fn proto_global(kind: ProtoOf) -> String { fn proto_global(kind: ProtoOf) -> String {
format!("proto_{kind:?}").to_lowercase() format!("proto_{kind:?}").to_lowercase()
} }
+1
View File
@@ -27,6 +27,7 @@ pub enum Code {
Fn(usize), Fn(usize),
Native(usize), Native(usize),
ProtoLink(ProtoOf), ProtoLink(ProtoOf),
HostLink,
} }
/// What the objects of a cell are, besides objects with named fields /// What the objects of a cell are, besides objects with named fields
+32 -4
View File
@@ -29,6 +29,10 @@ pub type Place = (usize, usize);
/// The call place of the entry fn, which the host calls /// The call place of the entry fn, which the host calls
pub const HOST: Place = (usize::MAX, 0); pub const HOST: Place = (usize::MAX, 0);
/// The call place of the fn that the host calls when an IO op completes (see
/// `Code::HostLink`)
pub const HOST_COMPLETE: Place = (usize::MAX, 1);
#[derive(Debug, Clone, Hash, PartialEq, Eq)] #[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct InferRequest { pub struct InferRequest {
pub fn_id: usize, pub fn_id: usize,
@@ -60,6 +64,7 @@ pub struct TypeCache {
objects: BTreeMap<(usize, usize, Place), Cell>, // the cell of each name: the fn and instr that make the objects, and the call place objects: BTreeMap<(usize, usize, Place), Cell>, // the cell of each name: the fn and instr that make the objects, and the call place
builtins: Vec<Cell>, // the cells of the natives, then of the proto links builtins: Vec<Cell>, // the cells of the natives, then of the proto links
pub protos: Protos, // the prototype of each `ProtoOf`, as the stdlib links it pub protos: Protos, // the prototype of each `ProtoOf`, as the stdlib links it
pub host_complete: Type, // what the host calls when an IO op completes, as the stdlib links it
version: usize, // goes up on every change of a cell, a return or a proto, and on every new cell or walk version: usize, // goes up on every change of a cell, a return or a proto, and on every new cell or walk
} }
@@ -83,6 +88,7 @@ impl TypeCache {
match code { match code {
Code::Native(id) => &self.builtins[id], Code::Native(id) => &self.builtins[id],
Code::ProtoLink(kind) => &self.builtins[NATIVES.len() + kind as usize], Code::ProtoLink(kind) => &self.builtins[NATIVES.len() + kind as usize],
Code::HostLink => &self.builtins[NATIVES.len() + ProtoOf::ALL.len()],
Code::Fn(_) => unreachable!(), Code::Fn(_) => unreachable!(),
} }
} }
@@ -184,6 +190,28 @@ fn call_code(cache: &mut TypeCache, code: Code, arg_types: &[Type], fns: &[RegFn
cache.protos[kind as usize] = joined; cache.protos[kind as usize] = joined;
Type::Undefined Type::Undefined
} }
Code::HostLink => {
let joined = join(&cache.host_complete, args.first().unwrap_or(&Type::Undefined));
cache.changed(joined != cache.host_complete);
cache.host_complete = joined;
Type::Undefined
}
}
}
/// The calls that the host makes: the entry, with no args, then the fn that
/// it calls when an IO op completes, with the op's handle. Each member of its
/// type is its own env, and `this` is undefined
fn host_calls(cache: &mut TypeCache, fns: &[RegFn]) {
// The synthetic program entry is appended last by `flatten`
cache.call(fns.len() - 1, HOST, &[], fns);
for member in cache.host_complete.clone().members() {
if let Type::Obj(cell) = member
&& let Some(Code::Fn(fn_id)) = cell.code()
{
cache.call(fn_id, HOST_COMPLETE, &[member.clone(), Type::Undefined, Type::Int], fns);
}
} }
} }
@@ -206,19 +234,18 @@ pub fn infer_types(fns: &[RegFn]) -> TypeCache {
objects: BTreeMap::new(), objects: BTreeMap::new(),
builtins: vec![], builtins: vec![],
protos: std::array::from_fn(|_| Type::Unknown), protos: std::array::from_fn(|_| Type::Unknown),
host_complete: Type::Unknown,
version: 0, version: 0,
}; };
let codes = (0..NATIVES.len()).map(Code::Native).chain(ProtoOf::ALL.map(Code::ProtoLink)); let codes = (0..NATIVES.len()).map(Code::Native).chain(ProtoOf::ALL.map(Code::ProtoLink)).chain([Code::HostLink]);
cache.builtins = codes.map(|code| Cell::new(ObjKind::Fn(code))).collect(); cache.builtins = codes.map(|code| Cell::new(ObjKind::Fn(code))).collect();
loop { loop {
let version = cache.version; let version = cache.version;
cache.walks.iter_mut().for_each(|w| w.result = None); cache.walks.iter_mut().for_each(|w| w.result = None);
// The synthetic program entry is appended last by `flatten`. It is host_calls(&mut cache, fns);
// called by the host with no args
cache.call(fns.len() - 1, HOST, &[], fns);
if cache.version == version { if cache.version == version {
return cache; return cache;
@@ -302,6 +329,7 @@ fn walk_fn(w: usize, reg_fns: &[RegFn], cache: &mut TypeCache) -> InferResult {
JsValue::Undefined => Type::Undefined, JsValue::Undefined => Type::Undefined,
JsValue::NativeFn(id) => Type::Obj(cache.builtin(Code::Native(id)).clone()), JsValue::NativeFn(id) => Type::Obj(cache.builtin(Code::Native(id)).clone()),
JsValue::ProtoLink(kind) => Type::Obj(cache.builtin(Code::ProtoLink(kind)).clone()), JsValue::ProtoLink(kind) => Type::Obj(cache.builtin(Code::ProtoLink(kind)).clone()),
JsValue::HostLink => Type::Obj(cache.builtin(Code::HostLink).clone()),
_ => unreachable!(), _ => unreachable!(),
}, },
+104 -102
View File
@@ -21,8 +21,8 @@ pub mod opcodes {
#[derive(Debug)] #[derive(Debug)]
pub struct QuickJSBinary { pub struct QuickJSBinary {
pub version: u8, pub _version: u8,
pub checksum: u32, pub _checksum: u32,
pub atoms: Vec<Atom>, pub atoms: Vec<Atom>,
pub root_mod: Module, pub root_mod: Module,
} }
@@ -42,8 +42,8 @@ impl QuickJSBinary {
Ok(( Ok((
bin, bin,
QuickJSBinary { QuickJSBinary {
version, _version: version,
checksum, _checksum: checksum,
atoms, atoms,
root_mod, root_mod,
}, },
@@ -164,8 +164,8 @@ pub enum JsValue {
/// array body; `raw` is the extra trailing value stored as `.raw` /// array body; `raw` is the extra trailing value stored as `.raw`
/// (`Undefined` when the template has no raw form). /// (`Undefined` when the template has no raw form).
TemplateObject { TemplateObject {
cooked: Vec<JsValue>, _cooked: Vec<JsValue>,
raw: Box<JsValue>, _raw: Box<JsValue>,
}, },
Function(Function), Function(Function),
FnById(usize), FnById(usize),
@@ -173,6 +173,8 @@ pub enum JsValue {
NativeFn(usize), NativeFn(usize),
/// Makes its arg the prototype of a primitive kind (see `native`) /// Makes its arg the prototype of a primitive kind (see `native`)
ProtoLink(crate::native::ProtoOf), ProtoLink(crate::native::ProtoOf),
/// `__link_host_complete`: its arg is what the host calls when an op completes
HostLink,
Module(Module), Module(Module),
TypedArray, TypedArray,
ArrayBuffer, ArrayBuffer,
@@ -240,8 +242,8 @@ impl JsValue {
( (
bin, bin,
Self::TemplateObject { Self::TemplateObject {
cooked, _cooked: cooked,
raw: Box::new(raw), _raw: Box::new(raw),
}, },
) )
} }
@@ -271,16 +273,16 @@ pub enum FuncKind {
#[derive(Debug)] #[derive(Debug)]
pub struct FunctionFlags { pub struct FunctionFlags {
pub has_prototype: bool, pub _has_prototype: bool,
pub has_simple_param_list: bool, pub _has_simple_param_list: bool,
pub is_derived_class_constructor: bool, pub _is_derived_class_constructor: bool,
pub need_home_object: bool, pub _need_home_object: bool,
pub func_kind: FuncKind, pub _func_kind: FuncKind,
pub new_target_allowed: bool, pub _new_target_allowed: bool,
pub super_call_allowed: bool, pub _super_call_allowed: bool,
pub super_allowed: bool, pub _super_allowed: bool,
pub arguments_allowed: bool, pub _arguments_allowed: bool,
pub backtrace_barrier: bool, pub _backtrace_barrier: bool,
pub has_debug: bool, pub has_debug: bool,
} }
@@ -291,16 +293,16 @@ impl FunctionFlags {
Ok(( Ok((
bin, bin,
Self { Self {
has_prototype: raw & (1 << 0) != 0, _has_prototype: raw & (1 << 0) != 0,
has_simple_param_list: raw & (1 << 1) != 0, _has_simple_param_list: raw & (1 << 1) != 0,
is_derived_class_constructor: raw & (1 << 2) != 0, _is_derived_class_constructor: raw & (1 << 2) != 0,
need_home_object: raw & (1 << 3) != 0, _need_home_object: raw & (1 << 3) != 0,
func_kind: FuncKind::try_from_primitive((raw >> 4) & 0b11).unwrap(), _func_kind: FuncKind::try_from_primitive((raw >> 4) & 0b11).unwrap(),
new_target_allowed: raw & (1 << 6) != 0, _new_target_allowed: raw & (1 << 6) != 0,
super_call_allowed: raw & (1 << 7) != 0, _super_call_allowed: raw & (1 << 7) != 0,
super_allowed: raw & (1 << 8) != 0, _super_allowed: raw & (1 << 8) != 0,
arguments_allowed: raw & (1 << 9) != 0, _arguments_allowed: raw & (1 << 9) != 0,
backtrace_barrier: raw & (1 << 10) != 0, _backtrace_barrier: raw & (1 << 10) != 0,
has_debug: raw & (1 << 11) != 0, has_debug: raw & (1 << 11) != 0,
}, },
)) ))
@@ -326,9 +328,9 @@ pub enum VarKind {
#[derive(Debug)] #[derive(Debug)]
pub struct VarDefFlags { pub struct VarDefFlags {
pub kind: VarKind, pub _kind: VarKind,
pub is_const: bool, pub _is_const: bool,
pub is_lexical: bool, pub _is_lexical: bool,
pub is_captured: bool, pub is_captured: bool,
} }
@@ -344,9 +346,9 @@ impl VarDefFlags {
Ok(( Ok((
bin, bin,
Self { Self {
kind, _kind: kind,
is_const, _is_const: is_const,
is_lexical, _is_lexical: is_lexical,
is_captured, is_captured,
}, },
)) ))
@@ -355,11 +357,11 @@ impl VarDefFlags {
#[derive(Debug)] #[derive(Debug)]
pub struct VarDef { pub struct VarDef {
pub name: AtomRef, pub _name: AtomRef,
pub scope_level: usize, pub _scope_level: usize,
pub scope_next_plus_1: usize, pub _scope_next_plus_1: usize,
pub flags: VarDefFlags, pub _flags: VarDefFlags,
pub var_ref_idx: Option<usize>, pub _var_ref_idx: Option<usize>,
} }
impl VarDef { impl VarDef {
@@ -379,11 +381,11 @@ impl VarDef {
Ok(( Ok((
bin, bin,
Self { Self {
name, _name: name,
scope_level, _scope_level: scope_level,
scope_next_plus_1, _scope_next_plus_1: scope_next_plus_1,
flags, _flags: flags,
var_ref_idx, _var_ref_idx: var_ref_idx,
}, },
)) ))
} }
@@ -405,9 +407,9 @@ pub enum ClosureKind {
#[derive(Debug)] #[derive(Debug)]
pub struct ClosureVarFlags { pub struct ClosureVarFlags {
pub closure_kind: ClosureKind, pub closure_kind: ClosureKind,
pub is_const: bool, pub _is_const: bool,
pub is_lexical: bool, pub is_lexical: bool,
pub var_kind: VarKind, pub _var_kind: VarKind,
} }
impl ClosureVarFlags { impl ClosureVarFlags {
@@ -424,9 +426,9 @@ impl ClosureVarFlags {
bin, bin,
Self { Self {
closure_kind, closure_kind,
is_const, _is_const: is_const,
is_lexical, is_lexical,
var_kind, _var_kind: var_kind,
}, },
)) ))
} }
@@ -434,7 +436,7 @@ impl ClosureVarFlags {
#[derive(Debug)] #[derive(Debug)]
pub struct ClosureVar { pub struct ClosureVar {
pub name: AtomRef, pub _name: AtomRef,
pub var_idx: usize, pub var_idx: usize,
pub flags: ClosureVarFlags, pub flags: ClosureVarFlags,
} }
@@ -448,7 +450,7 @@ impl ClosureVar {
Ok(( Ok((
bin, bin,
Self { Self {
name, _name: name,
var_idx, var_idx,
flags, flags,
}, },
@@ -487,7 +489,7 @@ pub enum ImmOperand {
/// (the scope chain entry point, FORMAT.md §3.3). /// (the scope chain entry point, FORMAT.md §3.3).
NpopU16 { NpopU16 {
npop: u16, npop: u16,
scope: u16, _scope: u16,
}, },
/// Jump. The wire value is a signed offset from the operand /// Jump. The wire value is a signed offset from the operand
/// field's own position; `target` is already resolved. /// field's own position; `target` is already resolved.
@@ -501,21 +503,21 @@ pub enum ImmOperand {
}, },
AtomU16 { AtomU16 {
atom: AtomRef, atom: AtomRef,
arg: u16, _arg: u16,
}, },
AtomLabelU8 { AtomLabelU8 {
atom: AtomRef, _atom: AtomRef,
target: usize, _target: usize,
arg: u8, _arg: u8,
}, },
AtomLabelU16 { AtomLabelU16 {
atom: AtomRef, _atom: AtomRef,
target: usize, _target: usize,
arg: u16, _arg: u16,
}, },
LabelU16 { LabelU16 {
target: usize, _target: usize,
arg: u16, _arg: u16,
}, },
} }
@@ -618,7 +620,7 @@ fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec<Instr>> {
opcodes::OpFmt::Npop => ImmOperand::Npop(u16_at(0)), opcodes::OpFmt::Npop => ImmOperand::Npop(u16_at(0)),
opcodes::OpFmt::NpopU16 => ImmOperand::NpopU16 { opcodes::OpFmt::NpopU16 => ImmOperand::NpopU16 {
npop: u16_at(0), npop: u16_at(0),
scope: u16_at(2), _scope: u16_at(2),
}, },
opcodes::OpFmt::Loc => ImmOperand::Loc(u16_at(0)), opcodes::OpFmt::Loc => ImmOperand::Loc(u16_at(0)),
opcodes::OpFmt::Arg => ImmOperand::Arg(u16_at(0)), opcodes::OpFmt::Arg => ImmOperand::Arg(u16_at(0)),
@@ -639,21 +641,21 @@ fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec<Instr>> {
}, },
opcodes::OpFmt::AtomU16 => ImmOperand::AtomU16 { opcodes::OpFmt::AtomU16 => ImmOperand::AtomU16 {
atom: AtomRef::from_code_u32(u32_at(0)), atom: AtomRef::from_code_u32(u32_at(0)),
arg: u16_at(4), _arg: u16_at(4),
}, },
opcodes::OpFmt::AtomLabelU8 => ImmOperand::AtomLabelU8 { opcodes::OpFmt::AtomLabelU8 => ImmOperand::AtomLabelU8 {
atom: AtomRef::from_code_u32(u32_at(0)), _atom: AtomRef::from_code_u32(u32_at(0)),
target: jump(atom_label_base, u32_at(4) as i32 as i64), _target: jump(atom_label_base, u32_at(4) as i32 as i64),
arg: u8_at(8), _arg: u8_at(8),
}, },
opcodes::OpFmt::AtomLabelU16 => ImmOperand::AtomLabelU16 { opcodes::OpFmt::AtomLabelU16 => ImmOperand::AtomLabelU16 {
atom: AtomRef::from_code_u32(u32_at(0)), _atom: AtomRef::from_code_u32(u32_at(0)),
target: jump(atom_label_base, u32_at(4) as i32 as i64), _target: jump(atom_label_base, u32_at(4) as i32 as i64),
arg: u16_at(8), _arg: u16_at(8),
}, },
opcodes::OpFmt::LabelU16 => ImmOperand::LabelU16 { opcodes::OpFmt::LabelU16 => ImmOperand::LabelU16 {
target: jump(pc + 1, u32_at(0) as i32 as i64), _target: jump(pc + 1, u32_at(0) as i32 as i64),
arg: u16_at(4), _arg: u16_at(4),
}, },
}; };
@@ -671,18 +673,18 @@ fn parse_bytecode(code: &[u8]) -> IResult<&[u8], Vec<Instr>> {
#[derive(Debug)] #[derive(Debug)]
pub struct Function { pub struct Function {
pub flags: FunctionFlags, pub _flags: FunctionFlags,
pub is_strict_mode: bool, pub _is_strict_mode: bool,
pub name: AtomRef, pub name: AtomRef,
pub arg_count: usize, pub arg_count: usize,
/// Num locals /// Num locals
pub var_count: usize, pub var_count: usize,
/// Parameters before the first default or rest, this is Function.prototype.length /// Parameters before the first default or rest, this is Function.prototype.length
pub defined_arg_count: usize, pub _defined_arg_count: usize,
pub stack_size: usize, pub _stack_size: usize,
pub var_ref_count: usize, pub _var_ref_count: usize,
/// arguments first, then locals /// arguments first, then locals
pub var_defs: Vec<VarDef>, pub _var_defs: Vec<VarDef>,
pub closure_vars: Vec<ClosureVar>, pub closure_vars: Vec<ClosureVar>,
pub cpool: Vec<JsValue>, pub cpool: Vec<JsValue>,
/// LLM GENERATED, TENTATIVE /// LLM GENERATED, TENTATIVE
@@ -728,15 +730,15 @@ impl Function {
Ok(( Ok((
bin, bin,
Self { Self {
flags, _flags: flags,
is_strict_mode: strict_mode == 1, _is_strict_mode: strict_mode == 1,
name, name,
arg_count, arg_count,
var_count, var_count,
defined_arg_count, _defined_arg_count: defined_arg_count,
stack_size, _stack_size: stack_size,
var_ref_count, _var_ref_count: var_ref_count,
var_defs, _var_defs: var_defs,
closure_vars, closure_vars,
cpool, cpool,
byte_code, byte_code,
@@ -747,12 +749,12 @@ impl Function {
#[derive(Debug)] #[derive(Debug)]
pub struct Module { pub struct Module {
pub name: AtomRef, pub _name: AtomRef,
pub import_modules: Vec<AtomRef>, pub _import_modules: Vec<AtomRef>,
pub exports: Vec<ExportInfo>, pub _exports: Vec<ExportInfo>,
pub star_exports: Vec<usize>, pub _star_exports: Vec<usize>,
pub imports: Vec<ImportInfo>, pub _imports: Vec<ImportInfo>,
pub has_top_level_await: bool, pub _has_top_level_await: bool,
pub root_fn: Function, pub root_fn: Function,
} }
@@ -780,12 +782,12 @@ impl Module {
Ok(( Ok((
bin, bin,
Self { Self {
name, _name: name,
import_modules, _import_modules: import_modules,
exports, _exports: exports,
star_exports, _star_exports: star_exports,
imports, _imports: imports,
has_top_level_await: has_tla == 1, _has_top_level_await: has_tla == 1,
root_fn, root_fn,
}, },
)) ))
@@ -795,7 +797,7 @@ impl Module {
#[derive(Debug)] #[derive(Debug)]
pub enum ExportInfo { pub enum ExportInfo {
/// All exports except aliases and star exports /// All exports except aliases and star exports
Local { var_idx: usize, name: AtomRef }, Local { _var_idx: usize, _name: AtomRef },
/// Alias exports, has `from`, eg: export { a } from "./dep.js"; /// Alias exports, has `from`, eg: export { a } from "./dep.js";
Alias, Alias,
} }
@@ -808,7 +810,7 @@ impl ExportInfo {
0 => { 0 => {
let (bin, var_idx) = leb128(bin)?; let (bin, var_idx) = leb128(bin)?;
let (bin, name) = AtomRef::parse(bin)?; let (bin, name) = AtomRef::parse(bin)?;
(bin, Self::Local { var_idx, name }) (bin, Self::Local { _var_idx: var_idx, _name: name })
} }
1 => (bin, Self::Alias), 1 => (bin, Self::Alias),
_ => unreachable!(), _ => unreachable!(),
@@ -820,9 +822,9 @@ impl ExportInfo {
#[derive(Debug)] #[derive(Debug)]
pub struct ImportInfo { pub struct ImportInfo {
pub var_idx: usize, pub _var_idx: usize,
pub name: AtomRef, pub _name: AtomRef,
pub req_module_idx: usize, pub _req_module_idx: usize,
} }
impl ImportInfo { impl ImportInfo {
@@ -834,9 +836,9 @@ impl ImportInfo {
Ok(( Ok((
bin, bin,
Self { Self {
var_idx, _var_idx: var_idx,
name, _name: name,
req_module_idx, _req_module_idx: req_module_idx,
}, },
)) ))
} }
+10 -1
View File
@@ -58,7 +58,6 @@ pub const NATIVES: &[NativeFn] = &[
native!("__native_io_queue_close"(Int) -> Int), native!("__native_io_queue_close"(Int) -> Int),
native!("__native_io_queue_stat"(String) -> Int), native!("__native_io_queue_stat"(String) -> Int),
native!("__native_io_queue_cancel"(Int) -> Int), native!("__native_io_queue_cancel"(Int) -> Int),
native!("__native_io_wait"() -> Int),
native!("__native_io_status"(Int) -> Int), native!("__native_io_status"(Int) -> Int),
native!("__native_io_result_string"(Int) -> String), native!("__native_io_result_string"(Int) -> String),
native!("__native_io_result_number"(Int) -> Float), native!("__native_io_result_number"(Int) -> Float),
@@ -110,6 +109,12 @@ static PROTO_LINKS: &[(&str, ProtoOf)] = &[
("__link_proto_array", ProtoOf::Array), ("__link_proto_array", ProtoOf::Array),
]; ];
/// `__link_host_complete(f)` makes `f` what the host calls when an IO op
/// completes, with the op's handle (see `jsrt_complete` in `lib/rt/jsrt.h`).
/// The host owns the event loop: a flavor of the runtime runs it, and the
/// program only queues ops and handles their completions
pub const HOST_LINK: &str = "__link_host_complete";
/// The value of the global `name`. The only globals are the ones the stdlib /// The value of the global `name`. The only globals are the ones the stdlib
/// may read, anything else is an error /// may read, anything else is an error
pub fn global(name: AtomRef, atoms: &[Atom]) -> JsValue { pub fn global(name: AtomRef, atoms: &[Atom]) -> JsValue {
@@ -124,6 +129,10 @@ pub fn global(name: AtomRef, atoms: &[Atom]) -> JsValue {
return JsValue::NativeFn(idx); return JsValue::NativeFn(idx);
} }
if name == HOST_LINK {
return JsValue::HostLink;
}
match PROTO_LINKS.iter().find(|(link, _)| name == *link) { match PROTO_LINKS.iter().find(|(link, _)| name == *link) {
Some(&(_, kind)) => JsValue::ProtoLink(kind), Some(&(_, kind)) => JsValue::ProtoLink(kind),
None => panic!("unknown global `{name}`"), None => panic!("unknown global `{name}`"),
+20 -5
View File
@@ -2,9 +2,10 @@
// io_uring (see uring.h) // io_uring (see uring.h)
// //
// A `__native_io_queue_<op>` queues an op and gives its handle. It makes no // A `__native_io_queue_<op>` queues an op and gives its handle. It makes no
// syscall. `__native_io_wait` puts queued ops into the SQ, submits them, // syscall, and no native waits. The host's loop (`main` below) puts queued
// waits until one op is done, and gives its handle. The getters then read // ops into the SQ, submits them, waits until one op is done, and gives its
// that op's result, until the next wait // handle to `jsrt_complete`. The getters read that op's result while
// `jsrt_complete` runs
// //
// At most `cq_entries` ops are in flight, so the CQ cannot overflow. The rest // At most `cq_entries` ops are in flight, so the CQ cannot overflow. The rest
// stay queued until a later wait. So a queue never fails, and the program // stay queued until a later wait. So a queue never fails, and the program
@@ -392,8 +393,8 @@ int32_t __native_io_queue_cancel(int32_t target) {
} }
// The handle of an op that is done, or -1 if no op is queued or in flight. // The handle of an op that is done, or -1 if no op is queued or in flight.
// The op that the last wait gave is freed now // The op that the last call gave is freed now
int32_t __native_io_wait(void) { static int32_t jsrt_io_next(void) {
if (jsrt_io.given >= 0) { if (jsrt_io.given >= 0) {
jsrt_io_release(jsrt_io.given); jsrt_io_release(jsrt_io.given);
jsrt_io.given = -1; jsrt_io.given = -1;
@@ -452,4 +453,18 @@ double __native_io_result_number(int32_t h) {
return op->res; return op->res;
} }
#ifndef JSRT_NO_HOST
// The Linux flavor's host: run the program, then give it each op as it
// completes, until none is left
int main(void) {
jsrt_start();
for (int32_t h; (h = jsrt_io_next()) >= 0;) {
jsrt_complete(h);
}
return 0;
}
#endif
#endif // JSRT_IMPLEMENTATION #endif // JSRT_IMPLEMENTATION
+15 -1
View File
@@ -10,7 +10,15 @@
// str.h strings and the string natives // str.h strings and the string natives
// number.h the number natives // number.h the number natives
// any.h any_t and JS's operators on it // any.h any_t and JS's operators on it
// io.h IO natives, async on io_uring (uring.h) // io.h IO natives, async on io_uring (uring.h), and the host's loop
//
// The host owns the event loop. The program exports two fns, which the host
// calls, and a flavor of the runtime is the host. This one, for Linux, is the
// `main` in io.h: define JSRT_NO_HOST to bring another
//
// jsrt_start() runs the program's top level, then its microtasks
// jsrt_complete(h) op `h` is done: runs its callback, then the microtasks.
// The op's result can be read only until this returns
#pragma once #pragma once
// io.h needs Linux's syscall() and mmap(), which -std=c11 hides. This must // io.h needs Linux's syscall() and mmap(), which -std=c11 hides. This must
@@ -20,6 +28,12 @@
#endif #endif
#include "core.h" #include "core.h"
#include <stdint.h>
void jsrt_start(void);
void jsrt_complete(int32_t h);
#include "str.h" #include "str.h"
#include "number.h" #include "number.h"
#include "any.h" #include "any.h"
+3 -2
View File
@@ -1,2 +1,3 @@
// Runs after the program: the event loop (see loop.js) // Runs after the program's top level: its microtasks. The host then runs
__run_loop(); // the event loop (see loop.js)
__drain_microtasks();
+13 -16
View File
@@ -1,8 +1,8 @@
// The event loop. A macrotask is an IO op or a timer, which completes in C // The event loop's JS side. The host owns the loop (see lib/rt/jsrt.h). A
// (see lib/rt/io.h): `__native_io_wait()` gives the handle of the next one // macrotask is an IO op or a timer, which the host runs: it calls
// that completes. A microtask is a promise job, in `__microtasks`. After the // `__complete(h)` when op `h` is done. A microtask is a promise job, in
// program, end.js runs the loop: all microtasks, then one macrotask, then all // `__microtasks`. After the program's top level, end.js runs all microtasks,
// microtasks again, until nothing is pending // and after each macrotask, `__complete` does
// What to do when IO op `h` completes: `__pending[h]`, a fn that gets `h`. // What to do when IO op `h` completes: `__pending[h]`, a fn that gets `h`.
// Handles are small ints, and a handle is used again after its op completes // Handles are small ints, and a handle is used again after its op completes
@@ -32,15 +32,12 @@ function __drain_microtasks() {
q.head = 0; q.head = 0;
} }
function __run_loop() { // What the host calls when op `h` is done: its callback, then the microtasks
for (;;) { function __complete(h) {
__drain_microtasks(); const done = __pending[h];
__pending[h] = undefined;
const h = __native_io_wait(); if (done !== undefined) done(h);
if (h < 0) return; __drain_microtasks();
const done = __pending[h];
__pending[h] = undefined;
if (done !== undefined) done(h);
}
} }
__link_host_complete(__complete);