github vercel-labs/scriptc v0.4.9

2 hours ago

0.4.9

Highlights

  • Faster programs:
    • Typed-array and dense number[] loops run as lane loops: elementwise stores and dense sums, counts and stores run whole vectors and finish the remainder with the scalar loop. The typed-array benchmark runs 1.83x faster, and integer sums and counts 1.56x.
    • Typed arrays copy in bulk where the element kinds allow it: construction, set() and TypedArray.from(view) between typed arrays move same-width integer pairs with one memmove and convert every other pair in one loop. A Uint8ClampedArray over a decoded buffer runs 1.9x faster, at 1.19x Node's time instead of 2.39x.
    • A callee called in a loop with an array parameter of one known length is compiled for that length, so the loop loses a per-element hole guard: n-body at 50,000,000 iterations is 5.0% faster.
    • Byte loops that count a constant product and byte loops that index downwards are versioned on their trip count: 1.31x and 5.98x on probes of those shapes (1,305 ms to 1,000 ms and 1,140 ms to 190 ms).
    • Dynamic computed access has a faster path: the fasta benchmark port runs at 1.73x Node's time instead of about 3.2x, a byte-array store falls from 63.3 to 20.4 ns per operation, and an indexed charCodeAt from 172.0 to 90.2 ns.
    • Buffer.write with a latin1 or ascii encoding uses the word-wise encoder: a 16 MB write is 55% faster, from 3.9x to 1.75x Node's time. Indexed scans of long non-ASCII texts are 47-51% faster.
    • Untyped process.stdout.write(text) and process.stderr.write(text) calls reach one runtime call: 10,000,000 writes to /dev/null take 1.38 s instead of 3.13 s. The opt-in --stdout-buffering keeps console output in the 64 KiB buffer when stdout is not a terminal: 10,000,000 console.log lines to a pipe take 0.54 s instead of 6.3 s.
    • The collector does less work at collection points and sizes its nursery window from what it collects: channel-messages is 12.4% faster and worker-messages 5.9% faster. Object chunks of 1 MiB or more come from huge pages, an async function's statement frame stays out of a group when nothing in it awaits, and depth-21 binary-trees runs at 0.84 of its former wall time with 199 MB of peak memory instead of 253 MB.
    • Large immutable number tables are built in the program image, so a small CLI program starts in 1.21 ms instead of 1.51 ms. An optional-element array read skips the runtime getter (13% faster, 35% fewer instructions), and 200,000 calls through builtin function values take 4.6 ms against Node's 75.3 ms.
    • --cpu-dispatch copies only the functions whose body has a loop: 1.305x on mandelbrot and 1.068x on n-body against the default build.
  • Faster builds:
    • A failing program's whole-slot relower runs on the pass whose result is kept, after the general-streams relower: a large program that takes the general-streams relower builds 1.22x faster (1,141.9 s to 932.5 s cold).
    • A build whose inputs did not change returns the executable already on disk, without reading a source, lowering, emitting LLVM, generating objects or linking: a no-change build takes 16.6 ms instead of 71.9 ms, 17.6 ms instead of 426.8 ms with an LLVM helper set, and 58.6 ms instead of 959 ms with a profile and an FFI manifest.
    • Runtime-pack selections and link-driver jobs are kept in the build cache: cold builds take 16.3 ms less on average over 369 paired builds.
    • Partition groups are numbered from the file name relative to the entry module, so a warm one-function edit that adds a module recompiles 3 of 64 partitions and the module-init pass is 19.5% faster.
    • A build's repeated frontend loads share one checker project, so parsed files carry between passes: a large multi-package program builds 15.9% faster (652.96 s to 549.05 s). Source parses are cached and declaration type queries batched (59,075 source-parser requests become 2,595), alias answers are memoized for one load (573,047 requests become 190,448), and npm-static package selection is memoized per build: in a separate pairing on the same program the build falls from 634.0 s to 404.6 s and the island reloads from 222.4 s to 188.1 s.
    • Lowering a large program reads each mapped symbol name once, builds union arm keys once per sort and answers the library-file question once per file: a large compiler build's coverage analysis falls 20.2%, from 1,360.20 s to 1,085.14 s.
    • A build of the compiler's own sources loads a 3.1-3.8 MB frontend graph instead of 15.7 MB, and one analyze and compile of a fixture falls from 111.9 s to 24.6 s. The bundled-library scan asks once per directory, 1.2 ms to 0.1 ms.
    • Windows release executables request no debug info: hello world is 146,432 bytes instead of 1,803,264 bytes. Analysis peak memory falls 8.7% on a 1000-module graph and from 1207 MB to 1053 MB on 3000 modules, and a macOS type check's peak memory falls 19.0-21.5% with compact class references.
    • Large shared buffers get a mapping of their own: a 16-checker type check's peak memory falls from 2856 MB to 2805 MB.
  • Node.js parity:
    • node:dns lowers lookup, lookupService, getServers and the default result-order functions with Node's argument ladder, error codes and error properties, and its address, hint and error-code constants come from the target libc.
    • node:tls accepts tls.createServer({ SNICallback }) without a default cert/key pair.
    • node:readline interfaces have prependListener, prependOnceListener, removeListener, off, removeAllListeners, listenerCount and eventNames, with removal by the registered function, and question(query, { signal }, callback) cancels on abort.
    • process.allowedNodeEnvironmentFlags is the target's NODE_OPTIONS flag names, process emits 'beforeExit' and has abort(), and ChildProcess reports spawnfile and spawnargs.
    • child_process accepts stdio arrays past fd 2, detached, options.signal and stdio: "inherit", and spawnSync's error carries Node's errno, code, syscall, path and spawnargs fields.
    • Buffer#toJSON returns Node's snapshot, Blob#stream() and Blob#textStream() return real ReadableStreams, URL.createObjectURL, URL.revokeObjectURL and buffer.resolveObjectURL share Node's table, and fetch sends Blob and File bodies as their bytes with the blob's type.
    • url.Url is Node's legacy constructor, strict-mode property writes name bigint and symbol receivers, util.format walks Error values for %j, and typed emits reach listeners registered through untyped values with errorMonitor first.
    • Native handles accept own properties, and a class instance's getter-backed fields read through its checked view.
    • process.report is answered by the Node island, node:test's context has t.runOnly and t.waitFor, and static builds read /etc/passwd for os.homedir and os.userInfo and throw Node's catchable SystemError.
    • The compatibility ledger records the implemented web globals, the Headers class and the node:diagnostics_channel surface against the programs that exercise them.
  • TypeScript parity:
    • Every dynamic-to-typed conversion asks one predicate, and an optional record field whose type maps to the checked-dynamic slot can be absent, so delete works and an omitted field is absent to in, Object.hasOwn, Object.keys, util.inspect, JSON.stringify and spread.
    • new Set(x) and new Map(x) seed from a checked-dynamic iterable, and a Map or Set arm of a class field's undefined-armed union lowers.
    • A function's own properties use one representation, a class instance passed to a callee that reads its fields keeps its record shape, and accessor literals for intersections, unions and mapped types keep their checked-identity view.
    • Method calls on a union receiver with a primitive arm lower, && spreads that drop the left operand's widened arm lower, Promise.all over a mixed armed-union array literal lowers, and Object.assign takes dynamic sources into index-signature records.
    • Overrides that append trailing optional or defaulted parameters, optional captures, computed keys and variadic function values lower.
    • Builtin members read as values compile: Function.prototype's call, apply and bind, RegExp.prototype's exec, test and toString, DataView.prototype, builtin functions carrying Node's name and length, and an http.request alias or a client chosen at run time.
    • Packages that declare their data shapes in declaration files compile, as do package module edges reached through literal require() and import() calls, a package's own JSON documents, and rest bindings from a CommonJS module's export names.
    • More JavaScript semantics compile in typed sources: BigInt operands of the arithmetic, shift and bitwise operators, symbol to string coercion, Object.prototype.__proto__ on dynamic receivers, rest bindings over index-signature sources, and delete through an erased assertion.
    • Five diagnostics are admitted for JavaScript sources, and a project that checks its JavaScript still reports them.
  • CommonJS and islands:
    • A constant-decided top-level if exports what its branch exports, a failed CommonJS initializer retries on the next require(), and a shared module's exported function exists before the module evaluates.
    • A local JavaScript import answers its runtime sibling through a typed sidecar, and shared exports publish between declarators.
    • An island's module tree stops at a type-only import, and type-only island edges ship as the no-ops Node erases.
    • A union type alias a package declares over Node data types lowers as the checked dynamic value, and a callable type from a types-only package stays the island's function handle.
  • Fixes:
    • A nested marker table is a brand marker, so branded primitives stay Map keys. A parameter that shadows a builtin module alias is no longer reported as a patched module, and a generator declared to return any lowers instead of reporting SC2001.
    • A boxed union map value widens into a pointer-union Map.get result, a TypeError for a non-callable callee names the callee as V8 does, and the unwinding ABI check no longer counts a source-frame push as a pending-flag test.
    • The LLVM context is declared before the target machine in every partition compiler, which removes the intermittent aarch64 macOS dev build crash and timeout.
    • An own property written through a checked view of an Error subclass is kept, and a member read that only an asserted intersection declares answers what the object carries.
    • The child handle wrapper is installed only when the program links the child-process unit, and the stdio slot order program spawns /bin/sh so it runs on macOS.
    • SC3004 reports the LLVM helper's exit status or signal together with the output it wrote.
    • Reflection on a Proxy value answers its own keys again, and builtin prototypes and array-like concat match Node.

Don't miss a new scriptc release

NewReleases is sending notifications on new releases.