diff --git a/changelog.d/14525-wpfacts-gen-protocol-split.md b/changelog.d/14525-wpfacts-gen-protocol-split.md new file mode 100644 index 0000000000..6e311c99d1 --- /dev/null +++ b/changelog.d/14525-wpfacts-gen-protocol-split.md @@ -0,0 +1 @@ +- **wpfacts**: the promise and iteration protocol helpers of constraint generation (`awaited`, `resolver_fn`, `async_body`, `promise_of`, `iterate`, `iterator_of`) move from `gen_expr.rs` into the new module `gen_protocol.rs`. A pure move with no behaviour change: `gen_expr.rs` was over the file size cap (2011 code lines, now 1932), so `scripts/check_file_size.sh` failed on main. diff --git a/crates/perry-wpfacts/src/gen_expr.rs b/crates/perry-wpfacts/src/gen_expr.rs index 438cee8ca7..a8a8b6a784 100644 --- a/crates/perry-wpfacts/src/gen_expr.rs +++ b/crates/perry-wpfacts/src/gen_expr.rs @@ -576,116 +576,6 @@ impl<'a> Gen<'a> { (tm, t) } - /// `await v` / promise adoption: the value itself (non-thenables), the - /// settled value of promise objects, and for thenables whatever their - /// `then` passes to the resolve function. - pub(crate) fn awaited(&mut self, v: NodeId) -> NodeId { - let out = self.tmp("awaited"); - self.m.edge(v, out); - let pa = self.m.atom("#value"); - self.m.cons( - v, - Cons::Load { - key: Key::Named(pa), - dst: out, - }, - ); - // Thenables: `then(resolve, reject)` with runtime resolvers. - let resolver = self.resolver_fn(out); - let rej = self.resolver_fn(self.thrown); - let then = self.m.atom("then"); - let (call, ret) = self.new_call(ThisArg::Node(v), vec![resolver, rej], vec![]); - self.m.cons( - v, - Cons::MCall { - key: Key::Named(then), - call, - }, - ); - let _ = ret; - out - } - - /// A synthetic function whose first parameter flows into `dst` (a - /// promise resolver handed to user code). - pub(crate) fn resolver_fn(&mut self, dst: NodeId) -> NodeId { - let t = crate::builtins::resolver(&mut self.m, dst); - self.tok_node(t) - } - - /// The body of an async function that runs untransformed: one without - /// `await`, which `async_to_generator` leaves `is_async` and codegen - /// lowers direct (`lower_async_rejecting_stmts`): a call returns a fresh - /// promise (`js_async_fn_result`) settled with what the body returns, a - /// returned thenable adopted, and rejected with what the body throws; a - /// call never throws. Returns the body's context, whose `ret` is the - /// settled value, and its throw region, the realm's rejection cell (as - /// for the async-step driver's step closure, `async_callback`). - pub(crate) fn async_body(&mut self, fx: &Fx) -> (Fx, NodeId) { - let settled = self.tmp("async-settled"); - let p = self.promise_of(fx.module, settled); - self.m.edge(p, fx.ret); - ( - Fx { - ret: settled, - ..fx.clone() - }, - self.thrown, - ) - } - - /// A fresh promise template whose settled value is `value`. - pub(crate) fn promise_of(&mut self, mi: u32, value: NodeId) -> NodeId { - let tm = self.new_bobj_template(mi, "Promise"); - let t = self.m.tok(TokKind::Obj(tm, NO_CTX)); - let a = self.m.atom("#value"); - let f = self.m.field(t, a); - let aw = self.awaited(value); - self.m.edge(aw, f); - self.tok_node(t) - } - - /// The values produced by iterating whatever `v` holds: elements of - /// arrays/collections/primitive boxes, and for iterables the protocol: - /// `v[Symbol.iterator]()`, then `.next()` on the result and on `v` - /// itself, then `.value`. - pub(crate) fn iterate(&mut self, v: NodeId) -> NodeId { - let out = self.tmp("iterated"); - let ik = self.m.iter_key(); - self.m.cons(v, Cons::Load { key: ik, dst: out }); - let it = self.iterator_of(v); - let na = self.m.atom("next"); - let r1 = self.mcall(it, Key::Named(na), vec![], vec![]); - // The step of `v` itself: the runtime's (`js_get_iterator` returns a - // value it finds no iterator for unchanged), a protocol call as in - // `builtins::iterate`. - let (sc, r2) = self.new_call(ThisArg::Node(v), vec![], vec![]); - self.m.protocol_calls.insert(sc); - self.m.cons( - v, - Cons::MCall { - key: Key::Named(na), - call: sc, - }, - ); - // Only results seen not done: `Model::yield_key`. - let va = self.m.yield_key(); - self.m.cons(r1, Cons::Load { key: va, dst: out }); - self.m.cons(r2, Cons::Load { key: va, dst: out }); - out - } - - /// What `v[Symbol.iterator]()` returns (GetIterator, ECMA-262 7.4.3): - /// a protocol call. - pub(crate) fn iterator_of(&mut self, v: NodeId) -> NodeId { - let (pc, it) = self.new_call(ThisArg::Node(v), vec![], vec![]); - let wk = Key::Named(self.m.atom(crate::builtins::WK_ITERATOR)); - self.m.protocol_calls.insert(pc); - self.m.rel(|| format!("protocolcall {pc}")); - self.m.cons(v, Cons::MCall { key: wk, call: pc }); - it - } - /// Every node an expression makes belongs to the function walking it. pub(crate) fn expr(&mut self, fx: &Fx, e: &Expr) -> NodeId { let prev = std::mem::replace(&mut self.m.cur_fn, fx.f); diff --git a/crates/perry-wpfacts/src/gen_protocol.rs b/crates/perry-wpfacts/src/gen_protocol.rs new file mode 100644 index 0000000000..e275a110ca --- /dev/null +++ b/crates/perry-wpfacts/src/gen_protocol.rs @@ -0,0 +1,120 @@ +//! Constraint generation: the promise and iteration protocols. +//! +//! What `await`, promise adoption, an untransformed async body, `for-of` +//! and GetIterator contribute to the model. Split from `gen_expr.rs` (file +//! size cap); the methods stay on `Gen`. + +use crate::gen::{Fx, Gen}; +use crate::model::*; + +impl<'a> Gen<'a> { + /// `await v` / promise adoption: the value itself (non-thenables), the + /// settled value of promise objects, and for thenables whatever their + /// `then` passes to the resolve function. + pub(crate) fn awaited(&mut self, v: NodeId) -> NodeId { + let out = self.tmp("awaited"); + self.m.edge(v, out); + let pa = self.m.atom("#value"); + self.m.cons( + v, + Cons::Load { + key: Key::Named(pa), + dst: out, + }, + ); + // Thenables: `then(resolve, reject)` with runtime resolvers. + let resolver = self.resolver_fn(out); + let rej = self.resolver_fn(self.thrown); + let then = self.m.atom("then"); + let (call, ret) = self.new_call(ThisArg::Node(v), vec![resolver, rej], vec![]); + self.m.cons( + v, + Cons::MCall { + key: Key::Named(then), + call, + }, + ); + let _ = ret; + out + } + + /// A synthetic function whose first parameter flows into `dst` (a + /// promise resolver handed to user code). + pub(crate) fn resolver_fn(&mut self, dst: NodeId) -> NodeId { + let t = crate::builtins::resolver(&mut self.m, dst); + self.tok_node(t) + } + + /// The body of an async function that runs untransformed: one without + /// `await`, which `async_to_generator` leaves `is_async` and codegen + /// lowers direct (`lower_async_rejecting_stmts`): a call returns a fresh + /// promise (`js_async_fn_result`) settled with what the body returns, a + /// returned thenable adopted, and rejected with what the body throws; a + /// call never throws. Returns the body's context, whose `ret` is the + /// settled value, and its throw region, the realm's rejection cell (as + /// for the async-step driver's step closure, `async_callback`). + pub(crate) fn async_body(&mut self, fx: &Fx) -> (Fx, NodeId) { + let settled = self.tmp("async-settled"); + let p = self.promise_of(fx.module, settled); + self.m.edge(p, fx.ret); + ( + Fx { + ret: settled, + ..fx.clone() + }, + self.thrown, + ) + } + + /// A fresh promise template whose settled value is `value`. + pub(crate) fn promise_of(&mut self, mi: u32, value: NodeId) -> NodeId { + let tm = self.new_bobj_template(mi, "Promise"); + let t = self.m.tok(TokKind::Obj(tm, NO_CTX)); + let a = self.m.atom("#value"); + let f = self.m.field(t, a); + let aw = self.awaited(value); + self.m.edge(aw, f); + self.tok_node(t) + } + + /// The values produced by iterating whatever `v` holds: elements of + /// arrays/collections/primitive boxes, and for iterables the protocol: + /// `v[Symbol.iterator]()`, then `.next()` on the result and on `v` + /// itself, then `.value`. + pub(crate) fn iterate(&mut self, v: NodeId) -> NodeId { + let out = self.tmp("iterated"); + let ik = self.m.iter_key(); + self.m.cons(v, Cons::Load { key: ik, dst: out }); + let it = self.iterator_of(v); + let na = self.m.atom("next"); + let r1 = self.mcall(it, Key::Named(na), vec![], vec![]); + // The step of `v` itself: the runtime's (`js_get_iterator` returns a + // value it finds no iterator for unchanged), a protocol call as in + // `builtins::iterate`. + let (sc, r2) = self.new_call(ThisArg::Node(v), vec![], vec![]); + self.m.protocol_calls.insert(sc); + self.m.cons( + v, + Cons::MCall { + key: Key::Named(na), + call: sc, + }, + ); + // Only results seen not done: `Model::yield_key`. + let va = self.m.yield_key(); + self.m.cons(r1, Cons::Load { key: va, dst: out }); + self.m.cons(r2, Cons::Load { key: va, dst: out }); + out + } + + /// What `v[Symbol.iterator]()` returns (GetIterator, ECMA-262 7.4.3): + /// a protocol call. + pub(crate) fn iterator_of(&mut self, v: NodeId) -> NodeId { + let (pc, it) = self.new_call(ThisArg::Node(v), vec![], vec![]); + let wk = Key::Named(self.m.atom(crate::builtins::WK_ITERATOR)); + self.m.protocol_calls.insert(pc); + self.m.rel(|| format!("protocolcall {pc}")); + self.m.cons(v, Cons::MCall { key: wk, call: pc }); + it + } +} diff --git a/crates/perry-wpfacts/src/lib.rs b/crates/perry-wpfacts/src/lib.rs index b72f4da8ae..087554e125 100644 --- a/crates/perry-wpfacts/src/lib.rs +++ b/crates/perry-wpfacts/src/lib.rs @@ -38,6 +38,7 @@ mod gen_call; mod gen_cjs; mod gen_const; mod gen_expr; +mod gen_protocol; mod gen_rest; mod held_lengths; mod ledger;