diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47329cc28..fff1931ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -219,6 +219,12 @@ jobs: Move-Item "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc" $llvm echo "LLVM_DIR=$llvm\lib\cmake\llvm" >> $env:GITHUB_ENV echo "$llvm\bin" >> $env:GITHUB_PATH + # A musl CLI ships tsc-ts's musl executable, and these jobs run on glibc + # runners, where pnpm installs only the glibc platform packages. + - name: Let pnpm install musl platform packages + if: endsWith(matrix.cli_target, '-musl') + run: | + printf 'supportedArchitectures:\n libc: [current, musl]\n' >> pnpm-workspace.yaml - run: pnpm install --frozen-lockfile - name: Build native packages (POSIX) if: matrix.platform != 'windows-x64' diff --git a/CHANGELOG.md b/CHANGELOG.md index 3de3f334f..c319a1b53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,73 @@ All notable changes to scriptc will be documented in this file. +## 0.4.0 + +### Breaking changes + +- **tsc-ts is the default TypeScript 7 checker** on Linux and macOS. scriptc installs it with the compiler (`tsc-ts` 0.0.5 and its platform package for the host). Programs get the same diagnostics and generated code as with tsgo, and compiling takes less checker memory. `SCRIPTC_CHECKER=tsgo` selects tsgo, which stays the default on Windows. A build cache entry made with one checker isn't reused with the other. +- **`--npm-static=auto`, the default, compiles more packages:** packages typed by their DefinitelyTyped twin (`@types/`), and untyped dependencies that another package's JavaScript imports. A program whose dependencies were refused before can now compile them, and a twin whose declarations disagree with the package's JavaScript fails the build at the use. Use `--npm-static ` to name the packages to compile. +- **Program type errors are reported as type errors** instead of as refusals of the npm package that contains them. + +### Highlights + +- **WebAssembly:** builds translate every WebAssembly module whose bytes they can see into native code, and `WebAssembly.compile`, `instantiate`, `validate`, `new WebAssembly.Module` and `new WebAssembly.Instance` run them. There is no interpreter. `WebAssembly.Memory`, `Table` and `Global`, `Module.imports`, `Module.exports` and `Module.customSections`, and function references from tables are supported, and modules can be sent to workers. `scriptc coverage` lists the modules a program loads, with `--wasm ` for modules loaded from paths the sources don't spell out. +- **Async functions that suspend only at their `await`s run as stackless coroutines** instead of on fibers with their own stack, so a suspended call keeps only the values live across its awaits. +- **Discriminated unions of records are the record pointer itself,** with no box around each value. +- **The dynamic tier computes with NaN-boxed values:** numbers, booleans, `undefined` and `null` between operators no longer allocate. `scriptc coverage --explain-dynamic` lists the functions that run in the dynamic tier and why, and `--tier=static-only` keeps a program fully static. +- **npm packages:** esbuild's bundled CommonJS chunks, CommonJS cycles and lazily initialized ES modules compile statically, as do classic CommonJS packages (late and JSON `require`s, require cycles), class hierarchies across a package's modules, and the packages unblocked by type-only imports, `@types` packages, `punycode` and `constants`. chalk 5 and the jose root entry compile. + +### Features + +- **`@scriptc/experimental` adds `ThreadSlots`,** one value per key and thread on shared objects, read without a lock. +- **`node:test` adds `mock.timers`, `getTestContext`, `t.assert.snapshot` and `t.assert.fileSnapshot`,** the `TestContext` values `filePath`, `attempt`, `passed`, `workerId` and `tags`, module-level `before` and `after` hooks, the `SuiteContext` and hook contexts, and unnamed `test` and `describe` calls. A timed-out test cancels its running subtests. +- **`node:worker_threads` adds `BroadcastChannel`,** `setEnvironmentData`/`getEnvironmentData`, `receiveMessageOnPort`, `markAsUntransferable`, `markAsUncloneable`, `threadName`, `resourceLimits.stackSizeMb`, the `stdout`/`stderr`/`stdin` options and streams, and `worker[Symbol.asyncDispose]`. +- **`node:net` adds `SocketAddress` and `BlockList`,** and `net.connect` resolves host names. +- **`node:http` pools keep-alive sockets in `Agent`s,** uses `globalAgent` for default requests, reads `http.request` options at run time, and speaks HTTP over Unix domain sockets. `fetch` tunnels through `HTTP_PROXY`/`HTTPS_PROXY` with `CONNECT`. +- **`DisposableStack`, `Float16Array`, `Error.isError`, `Promise.try` and the regular expression `v` flag** are supported. +- **Iterator helpers** run on generators, `Map` and `Set` iterators, string and typed array iterators, `Iterator.from` and untyped iterators. +- **`Object.groupBy` and `Map.groupBy`** group Sets, Maps and generators. +- **`v8`, `stream/web`, `dns/promises`, `EvalError` and `graceful-fs`** are available, and `Temporal` is reported as absent, as in Node 24. +- **`util.promisify`** works over the crypto callback functions, dns lookups, `child_process.exec`'s callback forms and callback-style functions. +- **`events.once`** takes an `AbortSignal` and works over streams. +- **`AsyncLocalStorage` stores** carry into timer, immediate, `process.nextTick` and microtask callbacks. +- **JavaScript classes compile like TypeScript classes** when their layout or JSDoc types allow it. +- **Classes that extend `EventEmitter` or a stream class can be used as values,** and so can `EventEmitter`, the stream classes and the error classes themselves. Constructor functions can inherit from them. +- **Class instances can be frozen and their fields redefined.** +- **Method overrides may differ from the method they override** in parameter and result types, as long as each value converts. +- **Library profiles add the `cstring` class.** +- **An experimental young-object nursery** for compiled classes and records is available at compile time with `SCRIPTC_YOUNG_NURSERY=1`. + +### Performance + +- **Dense arrays have head room,** so `shift`, `splice(0, n)` and `unshift` runs cost amortized O(1), and `pop` of a packed array is inline. +- **Index loops over arrays and typed arrays are versioned on one bounds guard,** with stable lengths proven and typed-array memory tagged for alias analysis. +- **Retain and release pairs are removed** through private-array precision and last-use moves, borrowed string slices go into stack buffers, and records, tuples and spread updates stay in frame storage. +- **SHA-384 and SHA-512 use AVX2, AVX-512VL or the arm64 SHA-512 instructions,** and MD5's block function is unrolled. +- **String character tests, optional-string `switch`es, `String.fromCharCode` and long-string `length` are faster,** and `Buffer.write` widens ASCII to UTF-16 sixteen bytes per step. +- **The cycle collector walks dead cycles once,** frees large dead cycle sets in address order and no longer creates self-capture cycles on the release fast path. +- **Workers return their memory** while the event loop waits and when they finish, and default-region memory is committed as it is used. +- **Lifetime regions carry across `await`,** and freed region chunks are reused. +- **Large closure environments compile in linear time,** and two frontend scans no longer repeat. +- **Programs keep stack frames only when they can read `stack`.** Before, a computed-key read on a dynamic value kept frames in every function; tsc-ts's own check is back to 0.3.6's speed. +- **The child-process runtime unit links only into programs that spawn.** + +### Fixes + +- **Node 24 parity:** `util.format`, `util.types` and `util.inspect`, `URLSearchParams`, `querystring` (null-prototype results), `string_decoder`, `os`, `events` and `timers/promises` match Node 24. Class instances, iterators, iterator helper objects and holes in arrays print as Node prints them, and `%d`/`%i` format dynamic arguments as Node does. +- **Silent wrong output is fixed** for object identity, `Object.prototype` values, `Date.parse` (V8's date grammar), stored builtins, constructor returns, `arr && arr[0]`, ES5 class names, builtin function values, uninitialized `let` writes, module-export `this` and `1 ** Infinity`. +- **Strings compare in UTF-16 code-unit order.** +- **Dates format with Node's local time zone names,** convert to local time once for their local getters, and follow `TZ` changes. `Date.prototype.toJSON` gives `null` for invalid dates. +- **Errors:** thrown Maps, Sets, typed arrays, functions and arrays keep their identity. Errors list their own name where they were created, errors held as `any` have their own `stack` accessor and convert with `Error.prototype.toString`, and errors that cross into dynamic code carry Node's error properties. +- **`matchAll` rows and `exec` results have `index`, `input` and `groups`,** and named groups keep their null prototype through optional chains. +- **Unhandled rejections are reported after each timer and immediate callback,** and async generator completions settle on Node's microtask turns. +- **Missing `Record` and index-signature keys read as `undefined`,** and generic functions that read a missing element return `undefined`. +- **Sockets:** `'data'` listeners get strings after `setEncoding`, socket and stream chunks are Buffers, and an idle pooled http socket that receives data is destroyed. +- **Diagnostic code frames window long source lines.** +- **npm crashes, internal errors and false TDZ errors** found while compiling popular packages are fixed. + + + ## 0.3.6 ### Features @@ -38,8 +105,6 @@ All notable changes to scriptc will be documented in this file. - **`Object.setPrototypeOf` returns primitive receivers,** and `createHmac` without a key throws Node's error. - **Boxed records that hold an async function link the dynamic promise unit.** - - ## 0.3.5 ### Features diff --git a/packages/cli-darwin-arm64/package.json b/packages/cli-darwin-arm64/package.json index ff55c940e..3c6e73e91 100644 --- a/packages/cli-darwin-arm64/package.json +++ b/packages/cli-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-darwin-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for darwin-arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-darwin-x64/package.json b/packages/cli-darwin-x64/package.json index b3b0a58dc..51121824b 100644 --- a/packages/cli-darwin-x64/package.json +++ b/packages/cli-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-darwin-x64", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for darwin-x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-arm64-gnu/package.json b/packages/cli-linux-arm64-gnu/package.json index ef9fe8bcc..1273cdfb8 100644 --- a/packages/cli-linux-arm64-gnu/package.json +++ b/packages/cli-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-arm64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for linux-arm64-gnu", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-arm64-musl/package.json b/packages/cli-linux-arm64-musl/package.json index a33eb60ab..bdba7d919 100644 --- a/packages/cli-linux-arm64-musl/package.json +++ b/packages/cli-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-arm64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for linux-arm64-musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-x64-gnu/package.json b/packages/cli-linux-x64-gnu/package.json index 25cd08204..3de4dcb72 100644 --- a/packages/cli-linux-x64-gnu/package.json +++ b/packages/cli-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-x64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for linux-x64-gnu", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-x64-musl/package.json b/packages/cli-linux-x64-musl/package.json index b07577400..97a1fae89 100644 --- a/packages/cli-linux-x64-musl/package.json +++ b/packages/cli-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-x64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for linux-x64-musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-win32-x64-msvc/package.json b/packages/cli-win32-x64-msvc/package.json index 23dc94994..9dcd31fa2 100644 --- a/packages/cli-win32-x64-msvc/package.json +++ b/packages/cli-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-win32-x64-msvc", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for win32-x64-msvc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli/package.json b/packages/cli/package.json index 434b76fe6..cfe908775 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "scriptc", - "version": "0.3.6", + "version": "0.4.0", "description": "Compile ordinary TypeScript and JavaScript to small, fast native executables and WebAssembly modules that run without Node.js", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 97c7688f5..0c058c467 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/compiler", - "version": "0.3.6", + "version": "0.4.0", "description": "The scriptc compiler — TypeScript/JavaScript frontend, typed IR, LLVM backend", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/compiler/src/backend/llvm/lib-stream.ts b/packages/compiler/src/backend/llvm/lib-stream.ts index 707fb6dbc..a319d0257 100644 --- a/packages/compiler/src/backend/llvm/lib-stream.ts +++ b/packages/compiler/src/backend/llvm/lib-stream.ts @@ -36,7 +36,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV host.emitPendingCheck(); return out; } - if (e.fn === "stream.dynNative") { + if (e.fn === "stream.dynNative" || e.fn === "stream.dynNativePipe") { // 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`); @@ -47,11 +47,16 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV 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)`); + // A pipe or unpipe destination in programs with constructor-function + // streams can be one of those streams too (scr_dyn_builtin_stream.c), + // as stream.unboxDyn does there. + const fn = + e.fn === "stream.dynNativePipe" && host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_dyn_native" + : "scr_stream_dyn_native"; + host.declare(`declare ptr @${fn}(ptr, double)`); const raw = B.tmp(); - B.line( - `${raw} = call ptr @scr_stream_dyn_native(ptr ${value.name}, double ${side.name}) ; +1 or NULL`, - ); + B.line(`${raw} = call ptr @${fn}(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") { diff --git a/packages/compiler/src/frontend/lowering/dyn-streams.ts b/packages/compiler/src/frontend/lowering/dyn-streams.ts index 51bcf0ea8..2ac09e8e5 100644 --- a/packages/compiler/src/frontend/lowering/dyn-streams.ts +++ b/packages/compiler/src/frontend/lowering/dyn-streams.ts @@ -58,11 +58,16 @@ export function nativeStreamClass(side: "r" | "w"): IrType { /** The native stream of a checked-dynamic value (+1, typed as the side's * class) or null: the stream.dynNative libCall, which tests the value's * typed capsule without throwing. */ -function dynNativeStream(lowerer: Lowerer, value: IrExpr, side: "r" | "w"): IrExpr { +function dynNativeStream( + lowerer: Lowerer, + value: IrExpr, + side: "r" | "w", + fn: "stream.dynNative" | "stream.dynNativePipe" = "stream.dynNative", +): IrExpr { const native = nativeStreamClass(side); return { kind: "libCall", - fn: "stream.dynNative", + fn, args: [value, numLit(side === "r" ? 1 : 2, value.loc)], type: { kind: "union", unionId: lowerer.unions.intern([native, { kind: "nullT" }]) }, loc: value.loc, @@ -268,8 +273,11 @@ export function lowerForAwaitDynStream( } /** A statement that runs `onNative` with the native stream a - * checked-dynamic value boxes (typed as the side's class), or `otherwise` - * for any other value. */ + * checked-dynamic pipe or unpipe destination stands for (typed as the + * side's class), or `otherwise` for any other value. The destination's + * stream is a boxed native one or a constructor-function stream's + * (stream.dynNativePipe): only the runtime's pipe reads it, while member + * calls on a slot keep a constructor-function stream's own methods. */ export function dynNativeBranch( lowerer: Lowerer, value: IrExpr, @@ -279,7 +287,7 @@ export function dynNativeBranch( ): IrStmt { const loc = value.loc; const nativeType = nativeStreamClass(side); - const nullable = dynNativeStream(lowerer, value, side); + const nullable = dynNativeStream(lowerer, value, side, "stream.dynNativePipe"); const nativeOrNull = lowerer.declareHiddenLocal("%streamNative", nullable.type); const unionId = nullable.type.kind === "union" ? nullable.type.unionId : ""; const tag = lowerer.armTag(unionId, nativeType); diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index a0dfff906..c9a1cc9df 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -7228,7 +7228,7 @@ function wideningAddedProperties( if ((prop.flags & ts.SymbolFlags.Optional) === 0 || prop.declarations.length === 0) continue; if (prop.declarations.some((d) => d.parent === literal)) continue; if ((ctx.checker.getTypeOfSymbol(prop).flags & ts.TypeFlags.Undefined) === 0) continue; - added.add(prop.name); + added.add(ts.symbolName(prop)); } return added; } diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 1ffae7536..cd3db784d 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -2689,6 +2689,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * (1) or writable (2) side — or null for any other value. Never throws; * the result union is `%Readable | null` or `%Writable | null`. */ "stream.dynNative": { argTypes: [DYN, F64], result: VOID }, + /** stream.dynNative for a pipe or unpipe destination: also a + * constructor-function stream's runtime stream, in programs that link + * that unit, unless it overrides write() or end(). */ + "stream.dynNativePipe": { argTypes: [DYN, F64], result: VOID }, /** readable.pipe(destination, end) into a checked-dynamic destination * that is not a native stream: [source, the source boxed, destination, * end]. Node's pipe listener protocol over the destination's members diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 808b20fe4..fd7c98cf6 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -170,6 +170,7 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "stream.errored", "stream.prop", "stream.dynNative", + "stream.dynNativePipe", "stream.pipeDyn", "stream.unpipeDyn", "stream.stdioWritable", @@ -209,6 +210,7 @@ const READABLE_DYN_ASYNC_PREFIXES: readonly string[] = ["readable.fromGen", "rea /** Entry points that link the dyn-invoke unit. */ const DYN_INVOKE_LIB_FNS: ReadonlySet = new Set([ "stream.dynNative", + "stream.dynNativePipe", "stream.pipeDyn", "stream.unpipeDyn", "dyn.arrayProtoCall", @@ -568,7 +570,9 @@ const RUNTIME_FEATURE_RULES = { dynTier: rule({ prefixes: ["js."] }), /** Checked-dynamic stream slots test values and pipe into JavaScript * stream objects (scr_stream_dyn.c). */ - streamDyn: rule({ fns: ["stream.dynNative", "stream.pipeDyn", "stream.unpipeDyn"] }), + streamDyn: rule({ + fns: ["stream.dynNative", "stream.dynNativePipe", "stream.pipeDyn", "stream.unpipeDyn"], + }), /** The stream unit emits through the emitter unit, also when the * program constructs no stream (a pipeline of functions). */ emitter: rule({ diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 2a2ddbd2f..f75f6f46e 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -5233,7 +5233,7 @@ function validateFunction( } return true; } - if (e.fn === "stream.dynNative") { + if (e.fn === "stream.dynNative" || e.fn === "stream.dynNativePipe") { const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined; const side = e.args[1]; const className = diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index b7cb3ab94..cb908d408 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -1,6 +1,6 @@ { "schemaVersion": 2, - "compilerVersion": "0.3.6", + "compilerVersion": "0.4.0", "coverage": [ "Entries are projected mechanically from the compiler's own decision tables: the diagnostics registry, the unsupported-syntax dispatch tables, the stdlib and node-builtin lowering tables, and the supported-builtin-module list. Nothing is hand-maintained; the manifest regenerates byte-identically from the source tree at this version.", "Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts.json new file mode 100644 index 000000000..49f4052b1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js", + "/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts" + ], + "diags": [] +} diff --git a/packages/experimental/package.json b/packages/experimental/package.json index 327f6fc97..f32a1085b 100644 --- a/packages/experimental/package.json +++ b/packages/experimental/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/experimental", - "version": "0.3.6", + "version": "0.4.0", "description": "Experimental scriptc APIs, with Node.js implementations: lifetime regions for ProgramLifetime objects, intern tables shared between threads, and per-thread slots on shared objects", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-darwin-arm64/package.json b/packages/llvm-darwin-arm64/package.json index 28865dc35..5330a5397 100644 --- a/packages/llvm-darwin-arm64/package.json +++ b/packages/llvm-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-darwin-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on macOS arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-darwin-x64/package.json b/packages/llvm-darwin-x64/package.json index 8d03e25cb..6282710b9 100644 --- a/packages/llvm-darwin-x64/package.json +++ b/packages/llvm-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-darwin-x64", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on macOS x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-arm64-gnu/package.json b/packages/llvm-linux-arm64-gnu/package.json index a6782dce1..59b4af11f 100644 --- a/packages/llvm-linux-arm64-gnu/package.json +++ b/packages/llvm-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-arm64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on Linux arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-arm64-musl/package.json b/packages/llvm-linux-arm64-musl/package.json index c4ac49e9b..5ea482253 100644 --- a/packages/llvm-linux-arm64-musl/package.json +++ b/packages/llvm-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-arm64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Statically linked LLVM code-generation helper for scriptc on Linux arm64 musl", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-x64-gnu/package.json b/packages/llvm-linux-x64-gnu/package.json index 6136a8e07..a17b6f393 100644 --- a/packages/llvm-linux-x64-gnu/package.json +++ b/packages/llvm-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-x64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on Linux x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-x64-musl/package.json b/packages/llvm-linux-x64-musl/package.json index 02d994cdd..161ca0cb7 100644 --- a/packages/llvm-linux-x64-musl/package.json +++ b/packages/llvm-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-x64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Statically linked LLVM code-generation helper for scriptc on Linux x64 musl", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-win32-x64-msvc/package.json b/packages/llvm-win32-x64-msvc/package.json index fe1329515..cfcc28a01 100644 --- a/packages/llvm-win32-x64-msvc/package.json +++ b/packages/llvm-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-win32-x64-msvc", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on Windows x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-android-arm64/package.json b/packages/runtime-android-arm64/package.json index 155c6ce87..2d8e22c92 100644 --- a/packages/runtime-android-arm64/package.json +++ b/packages/runtime-android-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-android-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc library runtime pack for Android arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-darwin-arm64/package.json b/packages/runtime-darwin-arm64/package.json index a36cd273b..68c81b246 100644 --- a/packages/runtime-darwin-arm64/package.json +++ b/packages/runtime-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-darwin-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for macOS arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-darwin-x64/package.json b/packages/runtime-darwin-x64/package.json index f81f21139..54c0d7835 100644 --- a/packages/runtime-darwin-x64/package.json +++ b/packages/runtime-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-darwin-x64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for macOS x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-ios-arm64/package.json b/packages/runtime-ios-arm64/package.json index c76226c6f..eb3d0ee27 100644 --- a/packages/runtime-ios-arm64/package.json +++ b/packages/runtime-ios-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-ios-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc library runtime pack for iOS arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-ios-simulator-arm64/package.json b/packages/runtime-ios-simulator-arm64/package.json index ec916922e..3b805bb6f 100644 --- a/packages/runtime-ios-simulator-arm64/package.json +++ b/packages/runtime-ios-simulator-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-ios-simulator-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc library runtime pack for iOS simulator arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-arm64-gnu/package.json b/packages/runtime-linux-arm64-gnu/package.json index bb972ebbe..ad50f2fe3 100644 --- a/packages/runtime-linux-arm64-gnu/package.json +++ b/packages/runtime-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-arm64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Linux arm64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-arm64-musl/package.json b/packages/runtime-linux-arm64-musl/package.json index 69110991a..4d4c23fdd 100644 --- a/packages/runtime-linux-arm64-musl/package.json +++ b/packages/runtime-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-arm64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Linux arm64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-x64-gnu/package.json b/packages/runtime-linux-x64-gnu/package.json index ceb7b975f..ab8780c39 100644 --- a/packages/runtime-linux-x64-gnu/package.json +++ b/packages/runtime-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-x64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Linux x64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-x64-musl/package.json b/packages/runtime-linux-x64-musl/package.json index 21f83ccee..862b46187 100644 --- a/packages/runtime-linux-x64-musl/package.json +++ b/packages/runtime-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-x64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Linux x64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-wasm32-wasi/package.json b/packages/runtime-wasm32-wasi/package.json index fa073ecf2..48c57d59a 100644 --- a/packages/runtime-wasm32-wasi/package.json +++ b/packages/runtime-wasm32-wasi/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-wasm32-wasi", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for WASI Preview 1", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-win32-x64-msvc/package.json b/packages/runtime-win32-x64-msvc/package.json index 4f6017b29..fe9ba6892 100644 --- a/packages/runtime-win32-x64-msvc/package.json +++ b/packages/runtime-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-win32-x64-msvc", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Windows x64 MSVC", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 0b9559931..b63f4fad3 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime", - "version": "0.3.6", + "version": "0.4.0", "description": "scriptc native runtime sources and vendored dependencies", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index 20392e3a5..401bf2b39 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -1192,6 +1192,10 @@ ScrDyn *scr_dyn_builtin_stream_object(struct ScrStream *s); ScrDyn *scr_dyn_builtin_stream_view(struct ScrStream *s); struct ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind); struct ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *message); +/* stream.dynNative in those programs: scr_stream_dyn_native's contract, + * also taking constructor-function streams that keep the runtime's write + * and end (+1 or NULL). */ +struct ScrStream *scr_dyn_builtin_stream_dyn_native(const ScrDyn *value, double side); bool scr_dyn_class_is_key(const ScrDyn *value, const ScrStr *type_key); bool scr_dyn_class_is(const ScrDyn *value, const char *type_key); /* Calls a dyn value: a non-function kind throws the catchable TypeError diff --git a/packages/runtime/src/scr_dyn_builtin_stream.c b/packages/runtime/src/scr_dyn_builtin_stream.c index 1926b0622..b7295f59a 100644 --- a/packages/runtime/src/scr_dyn_builtin_stream.c +++ b/packages/runtime/src/scr_dyn_builtin_stream.c @@ -1673,6 +1673,31 @@ ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind) { return ok ? scr_stream_retain(s) : NULL; } +/* stream.dynNative (checked-dynamic stream slots, scr_stream_dyn.c) in + * programs that link this unit: a constructor-function stream with the + * requested side (1 readable, 2 writable) answers its runtime stream (+1), + * so a pipe into it, and an unpipe of it, are the runtime's own, unless it + * overrides write() or end(), which the runtime's pipe would bypass: that + * one is NULL and pipes through its JavaScript members. Other values take + * scr_stream_dyn_native. Never throws. */ +ScrStream *scr_dyn_builtin_stream_dyn_native(const ScrDyn *value, double side) { + ScrStream *s = value->kind == SCR_DYN_OBJ ? bcs_of(value) : NULL; + if (s) { + const ScrVt *vt = s->vt; + bool readable = vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post; + bool writable = vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post; + bool duplex = vt->pre >= scr_duplex_vt.pre && vt->pre <= scr_duplex_vt.post; + if (!(duplex || (side == 1 ? readable : writable))) return NULL; + ScrDyn *object = (ScrDyn *)value; + if (side == 2 && (bcs_overrides(object, "write") || bcs_overrides(object, "end"))) { + if (scr_exc_pending()) scr_exc_clear(); + return NULL; + } + return scr_stream_retain(s); + } + return scr_stream_dyn_native(value, side); +} + /* stream.unboxDyn (an untyped pipe destination or pipeline stage) in * programs that link this unit: a constructor-function stream answers its * runtime stream (+1), unless it overrides write() or end(), which the diff --git a/packages/runtime/src/scr_wasm.c b/packages/runtime/src/scr_wasm.c index 92d556272..121acacbb 100644 --- a/packages/runtime/src/scr_wasm.c +++ b/packages/runtime/src/scr_wasm.c @@ -306,9 +306,50 @@ static void scr_wasm_throw_trap(int code) { /* AddressSanitizer intercepts siglongjmp and warns when it cannot tell the * extent of the running stack, as on the runtime's async fibers. Sanitized - * builds therefore jump with the compiler's builtin pair, which no runtime - * intercepts, and unpoison the abandoned frames themselves. */ -#ifdef SCR_WASM_ASAN + * builds therefore jump with a pair no runtime intercepts and unpoison the + * abandoned frames themselves: the compiler's builtin pair, or on AArch64, + * where the compiler has no builtin setjmp lowering, the pair below. */ +#if defined(SCR_WASM_ASAN) && defined(__aarch64__) +void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +/* x19-x28, the frame and link registers, sp and d8-d15: the registers the + * AArch64 procedure call standard has a callee preserve. */ +typedef uint64_t ScrWasmJmp[21]; +__attribute__((naked, noinline, returns_twice)) static int scr_wasm_setjmp(ScrWasmJmp buf) { + __asm__ volatile("stp x19, x20, [x0, #0]\n" + "stp x21, x22, [x0, #16]\n" + "stp x23, x24, [x0, #32]\n" + "stp x25, x26, [x0, #48]\n" + "stp x27, x28, [x0, #64]\n" + "stp x29, x30, [x0, #80]\n" + "mov x16, sp\n" + "str x16, [x0, #96]\n" + "stp d8, d9, [x0, #104]\n" + "stp d10, d11, [x0, #120]\n" + "stp d12, d13, [x0, #136]\n" + "stp d14, d15, [x0, #152]\n" + "mov w0, #0\n" + "ret\n"); +} +/* Returns 1 from the scr_wasm_setjmp call that filled buf. */ +__attribute__((naked, noinline, noreturn)) static void scr_wasm_longjmp(ScrWasmJmp buf) { + __asm__ volatile("ldp x19, x20, [x0, #0]\n" + "ldp x21, x22, [x0, #16]\n" + "ldp x23, x24, [x0, #32]\n" + "ldp x25, x26, [x0, #48]\n" + "ldp x27, x28, [x0, #64]\n" + "ldp x29, x30, [x0, #80]\n" + "ldr x16, [x0, #96]\n" + "mov sp, x16\n" + "ldp d8, d9, [x0, #104]\n" + "ldp d10, d11, [x0, #120]\n" + "ldp d12, d13, [x0, #136]\n" + "ldp d14, d15, [x0, #152]\n" + "mov w0, #1\n" + "ret\n"); +} +#define SCR_WASM_SETJMP(buf) scr_wasm_setjmp(buf) +#define SCR_WASM_LONGJMP(buf) scr_wasm_longjmp(buf) +#elif defined(SCR_WASM_ASAN) void __asan_unpoison_memory_region(void const volatile *addr, size_t size); typedef void *ScrWasmJmp[5]; #define SCR_WASM_SETJMP(buf) __builtin_setjmp(buf) diff --git a/packages/threads/package.json b/packages/threads/package.json index e3b5c91ff..358b06dc4 100644 --- a/packages/threads/package.json +++ b/packages/threads/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/threads", - "version": "0.3.6", + "version": "0.4.0", "description": "Zero-copy publication of immutable object graphs to worker threads (scriptc), with a deep-freezing Node.js implementation", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/types/package.json b/packages/types/package.json index b694abb1a..74d19fc24 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/types", - "version": "0.3.6", + "version": "0.4.0", "description": "Contract types for scriptc: type-only annotations (Int32, NoThrow, Immutable) that scriptc checks and optimizes, with no runtime semantics", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c42ad00c..3974f346e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1374,6 +1374,11 @@ packages: '@ts-morph/common@0.11.1': resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} + '@tsc-ts/darwin-arm64@0.0.5': + resolution: {integrity: sha512-yP64b2VpAKWG92KepBqxs1P89wpwiheYwNMk4EmmhSl6aw/ImIBrdHYNjvao3sg6FHZCXTc+PB6Vukh6hyQiJQ==} + cpu: [arm64] + os: [darwin] + '@tsc-ts/darwin-x64@0.0.5': resolution: {integrity: sha512-CEeHOFfpx5H9V3WSq+V7XGU8tfc06nRN818auiRVwuwPHvpti9gHq0HYkvf/43PH1pIGcgf68sQYdIxJhrQDKQ==} cpu: [x64] @@ -3442,6 +3447,9 @@ snapshots: mkdirp: 1.0.4 path-browserify: 1.0.1 + '@tsc-ts/darwin-arm64@0.0.5': + optional: true + '@tsc-ts/darwin-x64@0.0.5': optional: true @@ -4800,6 +4808,7 @@ snapshots: dependencies: '@scriptc/threads': 0.3.6 optionalDependencies: + '@tsc-ts/darwin-arm64': 0.0.5 '@tsc-ts/darwin-x64': 0.0.5 '@tsc-ts/linux-arm64-gnu': 0.0.5 '@tsc-ts/linux-arm64-musl': 0.0.5 diff --git a/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts b/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts new file mode 100644 index 000000000..223e10ee8 --- /dev/null +++ b/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts @@ -0,0 +1,35 @@ +// Constructor-function streams in NodeJS.ReadableStream / WritableStream +// slots: member calls run the stream's own pause, resume and on, and a pipe +// into one, and an unpipe of it, are the runtime's. +import { Readable } from "node:stream"; +import { collector, recordingSource } from "./sources.js"; + +function control(stream: NodeJS.ReadableStream): void { + stream.pause(); + stream.resume(); + stream.on("end", () => {}); +} + +function attach(src: Readable, dst: NodeJS.WritableStream): void { + src.pipe(dst); +} + +function detach(src: Readable, dst: NodeJS.WritableStream): void { + src.unpipe(dst); +} + +const recording = recordingSource(); +control(recording); +console.log("recorded", recording.calls.join(",")); + +const sink = collector(); +sink.on("pipe", () => console.log("pipe event")); +sink.on("unpipe", () => console.log("unpipe event")); +const source = new Readable({ read() {} }); +attach(source, sink); +source.push("a"); +setImmediate(() => { + detach(source, sink); + source.push("b"); + setImmediate(() => console.log("collected", sink.chunks.join(","), source.isPaused())); +}); diff --git a/tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js b/tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js new file mode 100644 index 000000000..462cd0538 --- /dev/null +++ b/tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js @@ -0,0 +1,39 @@ +// Constructor-function streams that override stream methods, and a plain +// JavaScript sink. +import { Readable, Writable } from "node:stream"; +import util from "node:util"; + +/** A Readable whose pause, resume and on record their calls before + * running Readable's own. */ +function Recording() { + Readable.call(this, { read() {} }); + this.calls = []; +} +util.inherits(Recording, Readable); +for (const name of ["pause", "resume", "on"]) { + Recording.prototype[name] = function (...args) { + this.calls.push(name); + return Readable.prototype[name].apply(this, args); + }; +} + +/** @returns {any} */ +export function recordingSource() { + return new Recording(); +} + +/** A Writable without overrides, piped into natively. */ +function Collector() { + Writable.call(this); + this.chunks = []; +} +util.inherits(Collector, Writable); +Collector.prototype._write = function (chunk, _encoding, done) { + this.chunks.push(String(chunk)); + done(); +}; + +/** @returns {any} */ +export function collector() { + return new Collector(); +} diff --git a/tests/corpus/6471-stream-data-buffer-or-string.ts b/tests/corpus/6471-stream-data-buffer-or-string.ts index 69289ced5..54c63945b 100644 --- a/tests/corpus/6471-stream-data-buffer-or-string.ts +++ b/tests/corpus/6471-stream-data-buffer-or-string.ts @@ -32,9 +32,12 @@ fs.writeFileSync(file, "file contents\n"); const stream = fs.createReadStream(file); const release = (): void => console.log("released"); stream.on("data", (chunk: Buffer | string) => describe("file", chunk)); -stream.on("close", release); stream.on("error", release); -stream.on("end", () => { +// The second read starts once the first stream has closed its file, so the +// order of its 'close' and the second stream's data does not depend on +// which file operation finishes first. +stream.on("close", () => { + release(); const encoded = fs.createReadStream(file, { encoding: "utf8" }); encoded.on("data", (chunk: Buffer | string) => describe("file utf8", chunk)); encoded.on("close", () => { diff --git a/tests/harness/corpus-costs/5820-node-stream-dyn-constructor-overrides/main.ts.json b/tests/harness/corpus-costs/5820-node-stream-dyn-constructor-overrides/main.ts.json new file mode 100644 index 000000000..f8ccf0699 --- /dev/null +++ b/tests/harness/corpus-costs/5820-node-stream-dyn-constructor-overrides/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3490, "san": 8050 }