Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions crates/perry-api-manifest/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4170,19 +4170,22 @@ pub static API_MANIFEST: &[ApiEntry] = &[
property("stream/web", "default"),
class("stream/web", "ReadableStream"),
class("stream/web", "ReadableStreamDefaultReader"),
class("stream/web", "ReadableStreamBYOBReader")
.stub_note("constructor/use throws: not yet implemented (#4915)"),
class("stream/web", "ReadableStreamBYOBRequest")
.stub_note("constructor/use throws: not yet implemented (#4915)"),
// #4915: BYOB readers are real — `new ReadableStreamBYOBReader(stream)` /
// `getReader({ mode: "byob" })` mint a reader whose `read(view)` fills the
// caller-supplied buffer; the byte-stream controller's `byobRequest`
// exposes `view` / `respond(bytesWritten)` / `respondWithNewView(view)`.
class("stream/web", "ReadableStreamBYOBReader"),
class("stream/web", "ReadableStreamBYOBRequest"),
class("stream/web", "ReadableByteStreamController"),
class("stream/web", "ReadableStreamDefaultController"),
class("stream/web", "TransformStream"),
class("stream/web", "TransformStreamDefaultController"),
class("stream/web", "WritableStream"),
class("stream/web", "WritableStreamDefaultWriter"),
class("stream/web", "WritableStreamDefaultController"),
class("stream/web", "ByteLengthQueuingStrategy")
.stub_note("constructor/use throws: not yet implemented (#4915)"),
// #4915: real byteLength accounting — per-chunk size() results are summed
// into desiredSize for ReadableStream/WritableStream/TransformStream.
class("stream/web", "ByteLengthQueuingStrategy"),
class("stream/web", "CountQueuingStrategy"),
class("stream/web", "TextEncoderStream"),
class("stream/web", "TextDecoderStream"),
Expand Down Expand Up @@ -5125,9 +5128,10 @@ pub static API_MANIFEST: &[ApiEntry] = &[
class("streams", "TextEncoder"),
class("streams", "TextDecoder"),
class("streams", "DecompressionStream"),
// node:stream/web QueuingStrategy classes (#1545).
class("streams", "ByteLengthQueuingStrategy")
.stub_note("constructor/use throws: not yet implemented (#4915)"),
// node:stream/web QueuingStrategy classes (#1545). #4915: the
// constructor lowers through the same stdlib builtin arm as the
// node:stream/web form, with real byteLength desiredSize accounting.
class("streams", "ByteLengthQueuingStrategy"),
class("streams", "CountQueuingStrategy"),
// --- node:http server (issue #577) ---
method("http", "createServer", false, None),
Expand Down
5 changes: 4 additions & 1 deletion crates/perry-api-manifest/tests/stub_inventory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ fn stub_inventory_matches_known_clusters() {
// listening port via SO_REUSEPORT binds + a fork-IPC 'listening'
// round-trip; SCHED_RR fd-passing + shared ephemeral `listen(0)`
// port are tracked in #4962 and are policy fidelity, not lies.
("#4915", 4), // BYOB readers + ByteLengthQueuingStrategy (throw)
// #4915 (BYOB readers + ByteLengthQueuingStrategy) intentionally
// absent: read(view), controller.byobRequest respond/
// respondWithNewView, and real byteLength desiredSize accounting
// landed (perry-stdlib/src/streams/byob.rs).
("#4916", 2), // v8 get/writeHeapSnapshot (empty graph)
("#4917", 18), // stdlib adapters: zlib(11) + http.Agent(3) + worker ref/unref(2) + mongodb(1) + backoff(1)
];
Expand Down
4 changes: 4 additions & 0 deletions crates/perry-codegen/src/ext_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ const FFI_REGISTRY: &[(&str, OwnerKind)] = &[
("js_writer_ready", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
("js_writer_desired_size", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
("js_reader_read", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
// #4915: BYOB reader surface.
("js_reader_read_with_view", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
("js_readable_stream_get_byob_reader", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
("js_readable_stream_controller_byob_request", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
("js_reader_release_lock", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
("js_reader_closed", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
("js_reader_cancel", OwnerKind::Stdlib { feature: Some("bundled-streams") }),
Expand Down
65 changes: 42 additions & 23 deletions crates/perry-codegen/src/lower_call/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1274,20 +1274,11 @@ pub(super) fn lower_builtin_new(
}
}
if args.len() >= 2 {
if let Some(qprops) = extract_options_fields(ctx, &args[1]) {
for (k, vexpr) in &qprops {
if k == "highWaterMark" {
hwm = lower_expr(ctx, vexpr)?;
}
}
} else {
let strategy = lower_expr(ctx, &args[1])?;
hwm = ctx.block().call(
DOUBLE,
"js_streams_strategy_high_water_mark",
&[(DOUBLE, &strategy)],
);
}
// #4915: pass the whole strategy value through — the runtime
// accepts a plain highWaterMark number or a strategy object
// (e.g. ByteLengthQueuingStrategy) and reads highWaterMark +
// size() from it.
hwm = lower_expr(ctx, &args[1])?;
}
if let Some(sink) = sink_object {
let h = ctx.block().call(
Expand Down Expand Up @@ -1340,20 +1331,26 @@ pub(super) fn lower_builtin_new(
transformer_object = Some(lower_expr(ctx, &args[0])?);
}
}
// #4915: writableStrategy (arg 1) / readableStrategy (arg 2) —
// each may be a plain highWaterMark number or a strategy object;
// the runtime parses either form.
let mut writable_strategy = hwm;
let mut readable_strategy = double_literal(1.0);
if args.len() >= 2 {
if let Some(qprops) = extract_options_fields(ctx, &args[1]) {
for (k, vexpr) in &qprops {
if k == "highWaterMark" {
hwm = lower_expr(ctx, vexpr)?;
}
}
}
writable_strategy = lower_expr(ctx, &args[1])?;
}
if args.len() >= 3 {
readable_strategy = lower_expr(ctx, &args[2])?;
}
if let Some(transformer) = transformer_object {
let h = ctx.block().call(
DOUBLE,
"js_transform_stream_new_from_transformer_object",
&[(DOUBLE, &transformer), (DOUBLE, &hwm)],
&[
(DOUBLE, &transformer),
(DOUBLE, &writable_strategy),
(DOUBLE, &readable_strategy),
],
);
return Ok(Some(h));
}
Expand All @@ -1364,7 +1361,8 @@ pub(super) fn lower_builtin_new(
(DOUBLE, &start),
(DOUBLE, &transform),
(DOUBLE, &flush),
(DOUBLE, &hwm),
(DOUBLE, &writable_strategy),
(DOUBLE, &readable_strategy),
],
);
Ok(Some(h))
Expand Down Expand Up @@ -1420,6 +1418,27 @@ pub(super) fn lower_builtin_new(
Ok(Some(h))
}

// #4915: `new ReadableStreamBYOBReader(stream)` — equivalent to
// `stream.getReader({ mode: "byob" })`. The runtime validates that
// the argument is a byte stream (TypeError otherwise) and returns a
// reader handle whose `read(view)` fills the caller's buffer.
"ReadableStreamBYOBReader" => {
let stream = if !args.is_empty() {
lower_expr(ctx, &args[0])?
} else {
double_literal(f64::from_bits(crate::nanbox::TAG_UNDEFINED))
};
for a in args.iter().skip(1) {
let _ = lower_expr(ctx, a)?;
}
let h = ctx.block().call(
DOUBLE,
"js_readable_stream_get_byob_reader",
&[(DOUBLE, &stream)],
);
Ok(Some(h))
}

// node:stream/web QueuingStrategy classes (#1545). Both take a single
// `{ highWaterMark }` options object; the runtime reads
// `opts.highWaterMark` and builds a `{ highWaterMark, size }` object.
Expand Down
23 changes: 23 additions & 0 deletions crates/perry-codegen/src/lower_call/options/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -964,13 +964,36 @@ pub(in crate::lower_call) fn lower_fetch_native_method(
);
return Ok(Some(v));
}
// #4915: byte-stream controller's BYOB request — `{ view,
// respond, respondWithNewView }` while a BYOB read is parked,
// null otherwise.
"byobRequest" => {
let v = ctx.block().call(
DOUBLE,
"js_readable_stream_controller_byob_request",
&[(DOUBLE, &recv_handle)],
);
return Ok(Some(v));
}
_ => return Ok(None),
}
}

if module == "readable_stream_reader" {
let recv_handle = lower_expr(ctx, recv)?;
match method {
// #4915: `read(view)` is the BYOB form — the runtime fills the
// caller-supplied view (default readers ignore the argument).
"read" if !args.is_empty() => {
let view = lower_expr(ctx, &args[0])?;
let blk = ctx.block();
let promise = blk.call(
I64,
"js_reader_read_with_view",
&[(DOUBLE, &recv_handle), (DOUBLE, &view)],
);
return Ok(Some(nanbox_pointer_inline(blk, &promise)));
}
"read" => {
let blk = ctx.block();
let promise = blk.call(I64, "js_reader_read", &[(DOUBLE, &recv_handle)]);
Expand Down
16 changes: 13 additions & 3 deletions crates/perry-codegen/src/runtime_decls/strings_part2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,13 @@ pub(crate) fn declare_phase_b_strings_part2(module: &mut LlModule) {
DOUBLE,
&[DOUBLE, DOUBLE],
);
// #4915: BYOB reader surface.
module.declare_function("js_readable_stream_get_byob_reader", DOUBLE, &[DOUBLE]);
module.declare_function(
"js_readable_stream_controller_byob_request",
DOUBLE,
&[DOUBLE],
);
// #1645: ReadableStream.from(iterable) — builds a pre-loaded stream.
module.declare_function("js_readable_stream_from_iterable", DOUBLE, &[DOUBLE]);
module.declare_function("js_readable_stream_locked", DOUBLE, &[DOUBLE]);
Expand Down Expand Up @@ -1027,6 +1034,8 @@ pub(crate) fn declare_phase_b_strings_part2(module: &mut LlModule) {
);
// ReadableStreamDefaultReader.
module.declare_function("js_reader_read", I64, &[DOUBLE]);
// #4915: BYOB `read(view)` — fills the caller-supplied view.
module.declare_function("js_reader_read_with_view", I64, &[DOUBLE, DOUBLE]);
module.declare_function("js_reader_release_lock", DOUBLE, &[DOUBLE]);
module.declare_function("js_reader_closed", I64, &[DOUBLE]);
module.declare_function("js_reader_cancel", I64, &[DOUBLE, DOUBLE]);
Expand Down Expand Up @@ -1059,16 +1068,17 @@ pub(crate) fn declare_phase_b_strings_part2(module: &mut LlModule) {
module.declare_function("js_writer_ready", I64, &[DOUBLE]);
module.declare_function("js_writer_desired_size", DOUBLE, &[DOUBLE]);
// TransformStream.
// #1644: leading arg is the `start` hook.
// #1644: leading arg is the `start` hook. #4915: the two trailing args
// are the writable / readable strategies (number or strategy object).
module.declare_function(
"js_transform_stream_new",
DOUBLE,
&[DOUBLE, DOUBLE, DOUBLE, DOUBLE],
&[DOUBLE, DOUBLE, DOUBLE, DOUBLE, DOUBLE],
);
module.declare_function(
"js_transform_stream_new_from_transformer_object",
DOUBLE,
&[DOUBLE, DOUBLE],
&[DOUBLE, DOUBLE, DOUBLE],
);
module.declare_function("js_transform_stream_readable", DOUBLE, &[DOUBLE]);
module.declare_function("js_transform_stream_writable", DOUBLE, &[DOUBLE]);
Expand Down
24 changes: 7 additions & 17 deletions crates/perry-ext-streams/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@
//! returns the blob handle as-is. A v0.6.0 followup needs a
//! cross-wrapper handle-bytes exchange (e.g. perry-ffi
//! `wrapper_get_bytes(symbol, handle)`) to wire them up.
//! - **BYOB readers** — `getReader({ mode: "byob" })`. Throws via
//! `js_streams_throw_byob_not_implemented`.
//! - **`ByteLengthQueuingStrategy`** — custom `size()` callbacks per
//! chunk. Throws via `js_streams_throw_byte_length_not_implemented`.
//! - **BYOB readers / `ByteLengthQueuingStrategy`** — implemented in the
//! node:stream/web path (`perry-stdlib/src/streams/byob.rs`, #4915);
//! this perry-ffi port doesn't expose them yet.
//! - **`ReadableStream.from(asyncIterable)`** — needs cooperative
//! async iteration which the spawn-blocking-only perry-ffi v0.5.x
//! surface can't express; throws.
Expand Down Expand Up @@ -1388,19 +1387,10 @@ unsafe fn transform_close(writable_id: usize) -> *mut Promise {
promise
}

// ─────────────────────────────────────────────────────────────────────
// Stubs for deferred surface (issue #237 followups)
// ─────────────────────────────────────────────────────────────────────

#[no_mangle]
pub unsafe extern "C" fn js_streams_throw_byob_not_implemented() -> f64 {
throw_with_message("BYOB readers are not yet implemented (issue #237 followup)");
}

#[no_mangle]
pub unsafe extern "C" fn js_streams_throw_byte_length_not_implemented() -> f64 {
throw_with_message("ByteLengthQueuingStrategy is not yet implemented (issue #237 followup)");
}
// BYOB readers and ByteLengthQueuingStrategy are implemented in the
// node:stream/web path (perry-stdlib/src/streams/byob.rs, #4915); the old
// `js_streams_throw_*_not_implemented` stubs here were never reachable from
// codegen and are gone.

// ─────────────────────────────────────────────────────────────────────
// Public helpers
Expand Down
11 changes: 11 additions & 0 deletions crates/perry-hir/src/destructuring/var_decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,6 +999,17 @@ pub(crate) fn lower_var_decl_with_destructuring(
);
ctx.uses_fetch = true;
}
// #4915: `new ReadableStreamBYOBReader(stream)` —
// the handle is a reader, same module tag as
// `stream.getReader({ mode: "byob" })`.
"ReadableStreamBYOBReader" => {
ctx.register_native_instance(
name.clone(),
"readable_stream_reader".to_string(),
"ReadableStreamBYOBReader".to_string(),
);
ctx.uses_fetch = true;
}
"WritableStream" => {
ctx.register_native_instance(
name.clone(),
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/lower/expr_call/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub(super) fn is_stream_api_method(module: &str, method: &str) -> bool {
| "close"
| "error"
| "desiredSize"
| "byobRequest"
),
"readable_stream_reader" => {
matches!(method, "read" | "releaseLock" | "cancel" | "closed")
Expand Down
9 changes: 8 additions & 1 deletion crates/perry-hir/src/lower/expr_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1464,7 +1464,13 @@ fn lower_member_inner(ctx: &mut LoweringContext, member: &ast::MemberExpr) -> Re
// Getter properties keep the 0-arg NativeMethodCall below
// (they really are getters); everything else here is a
// callable method.
"locked" | "desiredSize" | "closed" | "ready" | "readable" | "writable"
"locked"
| "desiredSize"
| "closed"
| "ready"
| "readable"
| "writable"
| "byobRequest"
) {
// #1642: a value-read of a Web Streams *method* (not a
// getter) must yield a callable bound-method reference, not
Expand Down Expand Up @@ -2662,6 +2668,7 @@ fn is_stream_api_member(module: &str, prop: &str) -> bool {
| "close"
| "error"
| "desiredSize"
| "byobRequest"
),
"readable_stream_reader" => {
matches!(prop, "read" | "releaseLock" | "cancel" | "closed")
Expand Down
1 change: 1 addition & 0 deletions crates/perry-hir/src/lower/expr_new.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,7 @@ pub(super) fn lower_new(ctx: &mut LoweringContext, new_expr: &ast::NewExpr) -> R
| "Blob"
| "File"
| "ReadableStream"
| "ReadableStreamBYOBReader"
| "WritableStream"
| "TransformStream"
) {
Expand Down
Loading
Loading