github vercel-labs/scriptc v0.4.12

59 minutes ago

0.4.12

Highlights

  • Faster programs:
    • A promise reaction that answers unknown or any runs without a fiber and its 512 KB stack: a .then, .catch or .finally handler of that result costs 111 ns instead of 2,060 ns a reaction, finally 605 ns instead of 8,464 and an await of an unknown 133 ns instead of 2,007, and a round no longer maps and unmaps a stack (112 calls before). Of the 1,017 programs that hold such a body or link the dynamic promise unit, 943 are smaller by a median of 3,776 bytes and at most 4,336; 74 are larger, four of them by more than 3%, because a stackless body is larger than the fiber it replaces.
    • A union box that dies is freed without reporting to the dispose hook, and the dispose filter and the queued-disposal pointer are tested once per release: the 39 fully typed benchmarks are all smaller by 240 to 72 bytes (median 112), the 5,772 runtime-only programs are 144 bytes smaller at the median, and of 75 run-time workloads 73 are neutral, two are faster (stream-buffered-drain 4.6%, value-records 3.1%) and none is slower.
    • Final fields and int32 slots stay in a class's static layout while no dynamic code in the program can observe the object.
    • && and || over boolean | undefined or number | undefined build their result in a stack box like ??: the hot function of Queens calls neither scr_union_new_bool nor scr_union_release, where it called three of each, and Queens runs 1.86 times faster (2,081.7 ms to 1,116.6 ms). The repository runtime suite's 52 measured workloads are byte-identical in size, print what Node prints and are 1.0% faster in geometric mean, and compile time is 0.97 times.
    • A nullable element array read is one guard and a direct null map instead of an out-of-line helper: towers runs 1.39 times faster with 223.0 instead of 315.9 instructions a move, and richards 1.06 times faster with 5.9% fewer instructions; every other runtime workload, compile time and program output are unchanged.
    • An unannotated JavaScript var that every read sees stored takes the union of the sequence types its writes store, and a documented Array.<number> parameter of a compiled npm package binds the storage its argument holds, so bcryptjs keeps its Blowfish key schedule in typed arrays instead of the checked-dynamic tier: 13.6 s to 3.6 s on the repro, with output identical to Node, and the runtime benchmark workloads are byte-identical.
  • Faster builds:
    • The call of the program entry from main, the entry's calls of the file initializers and an initializer's once-per-process calls are noinline, so LLVM no longer inlines the whole program into main and re-optimizes it once per level of the chain: the backend's time per program drops on sieve (1.49 times faster), mandelbrot (1.37), nbody (1.35) and json-build (1.17), and run time and output are unchanged.
  • Node.js parity:
    • All 14 zlib one-shot codecs and the stream writable side accept strings, Buffers, any TypedArray as its byte range, DataViews and ArrayBuffers, with Node's Buffer.from(buffer.buffer, …) conversion and its "chunk" rejection of a bare ArrayBuffer; 48 zlib rows move from partial to supported.
    • zlib.constants is one record per program, built from the same table the member literals bake, so identity, typeof, key order, spread, destructuring and JSON match Node, and the declared ZlibConstants interface accepts the object; the row moves from partial to supported.
    • Every Brotli encoder and decoder parameter, zstd parameters with a dictionary and a pledged source size, and their validation errors are covered through the one-shots, callbacks, promises and streams; 24 zlib rows move from partial to supported, and BrotliOptions and ZstdOptions stay partial until the info option lands.
    • http.Server.maxHeadersCount, http.Server.maxRequestsPerSocket, http.Server 'dropRequest', http.ClientRequest.maxHeadersCount and https.Server.maxHeadersCount follow Node 24's limits: the 431 at the inclusive header-line cap, the client's response-header truncation, the 503 with Connection: close a dropped request gets while the socket stays open, and the Keep-Alive: max=<value> hint; the five rows move from not-implemented to partial.
    • In JavaScript, globalThis.X = class X {} (or global.X = …, in any module) makes X, new X(), X() and typeof X use that value instead of throwing ReferenceError: X is not defined, and var c = void 0 compiles where it failed with SC2001; a PNG decode loop that constructs ImageData through a polyfilled global runs 1.99 times faster than Node (151 to 171 ms against 337 ms for 2,000 decodes).
    • A bare reference to a global that TypeScript declares and erases (declare var or declare const in a script-style declaration file, or in a declare global block) resolves through the global object, so a value assigned to globalThis.document before the read is what document evaluates to, as in Node; a declared global the program never writes keeps its previous static lowering and its compiled code is unchanged.
    • a.length++, a.length-- and compound assignment on a native array's length grow and truncate the array and throw Node's RangeError for a length that is not a valid array length, with the receiver and the length read saved before the right side runs; a postfix form yields the old length and a prefix form the new one.
    • in over a brand-record union arm answers from the class's declared members, so a hoisted TextEncoder reports encodeInto, and stdin and stdout imported from node:process bind the streams readline.createInterface expects.
  • TypeScript parity:
    • new ArrayBuffer(byteLength, { maxByteLength }) lowers from TypeScript as it does from JavaScript.
    • An abstract declaration follows prototype semantics: a direct call with no implementation below evaluates its receiver and arguments and throws Node's <callee> is not a function TypeError, and an untyped lookup answers the nearest implementation (below, then above the re-declaration) or undefined.
    • A discarded negation of a void-typed call in unchecked JavaScript lowers as its operand, while consumed, checked and TypeScript forms keep their diagnostics; a JavaScript setter parameter that is optional only in JSDoc is admitted, while executable optional, default, rest and multiple parameters keep TS1051.
    • The refusal descriptions of TS2629, TS2630 and TS2739 state the JavaScript behaviour the program has, without a change to the codes or to which programs are admitted.
    • A literal array with a run of 65 or more equal items builds as a typed table: each row is a fresh value, and a column whose rows all agree is left out of the table and read from the row itself. A spread of empty array literals takes the target literal's element type, including within a conditional arm and two levels down.
    • Element reads, writes and .length on a JavaScript value that is a union of an array and a typed array dispatch on the storage the value holds, and the undefined-or-NaN element-read analysis covers number[] receivers.
  • CommonJS and islands:
    • A call of a module's own export through the exports object passes a receiver that holds the current value of every export the called function reads through this, so a CommonJS module whose namespace functions reach their siblings through this compiles where the previous build refused the call by name (SC1090) or, inside a package, threw where it ran; a detached call or .call(other) behaves as before, and the shapes the proof cannot settle, such as this === exports, a write through this, or a member added to exports by another module, stay refused by name.
    • The proof that a module export ignores its receiver follows a namespace member, an alias, a conditional, ??, || and &&, the last operand of a comma, the value that Object.defineProperty, assign, freeze or setPrototypeOf returns, a re-export, a default export, an object or array literal member and a destructured binding, and refuses the call by name where the function it finds uses its own this: of 139 probes, 103 are refused by name and 29 match Node, against 66 and 29 with 42 answers that differed on the previous build, and no probe that matched Node is refused. A statement that gives a function its own call, apply or bind, and a prototype that defines call, are refused by name.
  • Fixes:
    • A read of a field that several union arms declare reads over the stored union at the site's common type instead of a narrowed arm that traps, so a negated inexact type predicate matches Node where the previous build traps; the 0.4.11 native binary traps on the first read of the corpus program.
    • { ...a, ...b } into one declared record enumerates in Node's order: each source keeps its own key order, a key that two sources set keeps its first position, and later keys append, so Object.keys, values and entries, for…in, JSON.stringify, util.inspect, console.log and a spread copy of the result agree with Node; a program that never observes the order of such a shape compiles to the same code.
    • The six http header-limit getters and setters are classified as never throwing, since they are plain storage with no accessor, coercion or listener, and http.serverOnDropRequest as may-throw because it fires 'newListener' before the add.
    • An Immutable annotation is recognized through a re-export, an alias or a path alias of @scriptc/types, and a marker whose import cannot be followed is refused with SC7006 instead of compiling as if the annotation were absent.
    • An array built by pushing values read from Immutable data, and the result of filter over an Immutable array, are accepted as fresh for a store into an Immutable field (SC7004), and each checked element read is read as the array passed at its call site, so a plain array's .map is no longer refused (SC7005) because an unrelated Immutable table of the same element type is indexed elsewhere.

Don't miss a new scriptc release

NewReleases is sending notifications on new releases.