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
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
- **Streams and readline:** Stream chunks are Buffers or strings.
`NodeJS.ReadableStream` and `NodeJS.WritableStream`, and `stream.Readable` and `stream.Writable` under `@types/node`, accept every stream: when a program passes file, zlib, or `node:stream` streams through them, child-process pipes and `process.stdout` and `process.stderr` join those streams as `Readable` and `Writable` instances.
`readable.pipe(process.stdout)` and `readable.pipe(process.stderr)` compile and, as in Node, do not end the process stream.
A `pipeline` stage that arrives as an untyped value must be a native stream at run time; other values throw a not-supported error.
When an untyped value reaches `NodeJS.ReadableStream` or `NodeJS.WritableStream`, those two interfaces hold any value, so JavaScript stream objects (constructor-function streams, plain objects with `on` and `pipe`) pass through TypeScript functions typed with them and come back to JavaScript as the same objects.
Member calls on such a value use the native stream when it is one and call the object's own members otherwise; `readable.pipe()` into a JavaScript destination follows Node's pipe protocol.
A `pipeline` stage and a `for await` source that arrive as untyped values must be native streams at run time; other values throw a not-supported error.
Native streams expose no `emit` to JavaScript, so a JavaScript stream whose own `pipe()` emits on its destination (the legacy `Stream` pipe) cannot pipe into one.
`Readable.from` accepts arrays, Sets, Map and Set iterators, generators, async generators, and class iterables and async iterables whose values are strings or Buffers, and in JavaScript any iterable or async iterable of them; a stream that mixes strings and Buffers is unsupported.
In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported.
Object-mode streams of other values (including `Readable.from` over a `Map`, whose values are entry arrays) are unsupported.
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/backend/executable-features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
dc: features.dc,
dynAsync: features.dynAsync,
...(features.dynTier ? { dynTier: true } : {}),
...(features.streamDyn ? { streamDyn: true } : {}),
events: features.processEvents,
emitter: features.emitter,
symbol: features.symbol,
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/backend/llvm/lib-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
"procStream.write": "scr_proc_stream_write",
"stream.stdioWritable": "scr_stream_stdio_writable",
"stream.unboxDyn": "scr_stream_unbox_dyn",
"stream.pipeDyn": "scr_stream_pipe_dyn",
"stream.unpipeDyn": "scr_stream_unpipe_dyn",
"process.kill": "scr_process_kill_named",
"process.killNum": "scr_process_kill",
// fs.promises (scr_bytes_io.c / scr_lib.c): the promise forms settle
Expand Down
18 changes: 18 additions & 0 deletions packages/compiler/src/backend/llvm/lib-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,24 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
host.emitPendingCheck();
return out;
}
if (e.fn === "stream.dynNative") {
// Checked-dynamic stream slots: the boxed native stream (+1) or NULL.
if (e.type.kind !== "union")
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} result is not a union`);
const def = host.unionsById.get(e.type.unionId);
const streamTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1;
const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1;
if (streamTag < 0 || nullTag < 0)
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} union lacks its arms`);
const value = host.emitExpr(e.args[0]!);
const side = host.emitExpr(e.args[1]!);
host.declare(`declare ptr @scr_stream_dyn_native(ptr, double)`);
const raw = B.tmp();
B.line(
`${raw} = call ptr @scr_stream_dyn_native(ptr ${value.name}, double ${side.name}) ; +1 or NULL`,
);
return host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag);
}
if (e.fn === "stream.onData") {
// The callback MOVES into the stream's listener registry; the
// adapter is per callback shape — runtime-provided for the
Expand Down
3 changes: 3 additions & 0 deletions packages/compiler/src/backend/native-link-info-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export interface NativeLinkFeatures {
/** Dynamic-tier jsval operations: the scr_js_* units. Absent (not false)
* for programs without them, so their link features stay unchanged. */
dynTier?: boolean;
/** Checked-dynamic stream slots: the scr_stream_dyn.c unit. Absent (not
* false) for programs without them. */
streamDyn?: boolean;
events: boolean;
emitter: boolean;
symbol: boolean;
Expand Down
3 changes: 3 additions & 0 deletions packages/compiler/src/backend/native/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ export interface CcOptions {
* the IR): compiles the scr_js_* units (two-tier design I3). Programs
* without them link no scr_js_ symbol. */
dynTier?: boolean;
/** Checked-dynamic stream slots (stream.dynNative / stream.pipeDyn):
* compiles scr_stream_dyn.c. */
streamDyn?: boolean;
/** The program constructs EventTarget/Event/CustomEvent values or
* calls events.addAbortListener (the event.* libCalls), or uses the
* native fetch AbortSignal built on the same engine: compiles
Expand Down
3 changes: 3 additions & 0 deletions packages/compiler/src/backend/native/executable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
// stream unit.
unit((u) => u.opts.zlib && u.opts.stream, "scr_zlib_stream.c"),
unit((u) => u.opts.stream && u.opts.dynAsync, "scr_stream_ops.c"),
// Checked-dynamic stream slots test values and call JavaScript stream
// members through the dyn-invoke unit.
unit((u) => u.opts.streamDyn === true, "scr_stream_dyn.c"),
// FileHandle streams reference both the stream unit and FileHandle.
unit((u) => u.opts.stream && u.opts.fileHandle, "scr_fs_stream_fh.c"),
// The readiness-poller backends (scr_platform.h): kqueue on macOS/BSD,
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler/src/backend/runtime-pack-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
eventTarget: boolean;
wasm: boolean;
dynTier: boolean;
streamDyn: boolean;
warningReplace: boolean;
stdioExtended: boolean;
nativeFetch: boolean;
Expand Down Expand Up @@ -275,6 +276,7 @@ export function effectiveRuntimeFeatures(
eventTarget: features.eventTarget === true,
wasm: features.wasm === true,
dynTier: features.dynTier === true,
streamDyn: features.streamDyn === true,
warningReplace: features.warningReplace === true,
stdioExtended: features.stdioExtended === true,
nativeFetch,
Expand Down
1 change: 1 addition & 0 deletions packages/compiler/src/executable/executable-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export interface EarlyExecutableNativeFeatures {
dc: boolean;
dynAsync: boolean;
dynTier?: boolean;
streamDyn?: boolean;
events: boolean;
emitter: boolean;
symbol: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,9 @@ function generalChildStdioType(
const className = name === "stdin" ? "%Writable" : "%Readable";
const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr));
if (!mapped) return null;
// Checked-dynamic stream slots: the declared stream type maps to dyn
// (dyn-streams.ts); the read still produces the bridged stream.
if (lowerer.dynNodeStreams && mapped.kind === "dyn") return { kind: "object", className };
const arms =
mapped.kind === "union" ? (lowerer.unions.get(mapped.unionId)?.arms ?? []) : [mapped];
return arms.some((arm) => arm.kind === "object" && arm.className === className)
Expand Down
Loading
Loading