Repository navigation
Releases
Releases · vercel-labs/scriptc
v0.4.12
0.4.12
Highlights
- Faster programs:
- A promise reaction that answers
unknownoranyruns without a fiber and its 512 KB stack: a.then,.catchor.finallyhandler of that result costs 111 ns instead of 2,060 ns a reaction,finally605 ns instead of 8,464 and anawaitof anunknown133 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-drain4.6%,value-records3.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||overboolean | undefinedornumber | undefinedbuild their result in a stack box like??: the hot function of Queens calls neitherscr_union_new_boolnorscr_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
varthat every read sees stored takes the union of the sequence types its writes store, and a documentedArray.<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.
- A promise reaction that answers
- 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 arenoinline, so LLVM no longer inlines the whole program intomainand 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.
- The call of the program entry from
- 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 frompartialtosupported. zlib.constantsis 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 declaredZlibConstantsinterface accepts the object; the row moves frompartialtosupported.- 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
partialtosupported, andBrotliOptionsandZstdOptionsstay partial until theinfooption lands. http.Server.maxHeadersCount,http.Server.maxRequestsPerSocket,http.Server'dropRequest',http.ClientRequest.maxHeadersCountandhttps.Server.maxHeadersCountfollow Node 24's limits: the 431 at the inclusive header-line cap, the client's response-header truncation, the 503 withConnection: closea dropped request gets while the socket stays open, and theKeep-Alive: max=<value>hint; the five rows move fromnot-implementedtopartial.- In JavaScript,
globalThis.X = class X {}(orglobal.X = …, in any module) makesX,new X(),X()andtypeof Xuse that value instead of throwingReferenceError: X is not defined, andvar c = void 0compiles where it failed with SC2001; a PNG decode loop that constructsImageDatathrough 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 varordeclare constin a script-style declaration file, or in adeclare globalblock) resolves through the global object, so a value assigned toglobalThis.documentbefore the read is whatdocumentevaluates 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'sRangeErrorfor 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.inover a brand-record union arm answers from the class's declared members, so a hoistedTextEncoderreportsencodeInto, andstdinandstdoutimported fromnode:processbind the streamsreadline.createInterfaceexpects.
- 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
- 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 functionTypeError, and an untyped lookup answers the nearest implementation (below, then above the re-declaration) orundefined. - 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
.lengthon 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 coversnumber[]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 throughthiscompiles 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 asthis === exports, a write throughthis, or a member added toexportsby 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 thatObject.defineProperty,assign,freezeorsetPrototypeOfreturns, 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 ownthis: 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 owncall,applyorbind, and a prototype that definescall, are refused by name.
- 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
- 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, soObject.keys,valuesandentries,for…in,JSON.stringify,util.inspect,console.logand 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.serverOnDropRequestas may-throw because it fires'newListener'before the add. - An
Immutableannotation 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
Immutabledata, and the result offilterover anImmutablearray, are accepted as fresh for a store into anImmutablefield (SC7004), and each checked element read is read as the array passed at its call site, so a plain array's `....
v0.4.11
0.4.11
Highlights
- Faster programs:
- A read through an untyped value of a typed record or array no longer copies it: three React render loops execute 31% to 57% fewer instructions per render (294,339 to 197,439, 6,765,442 to 4,653,659 and 6,648,657 to 2,880,208 by callgrind), and the
untyped-record-probesworkload runs 72.8% faster, about 0.7 s instead of 0.18 s against 0.13 s in Node. Of 74 run-time workloads, 73 are neutral, one is faster and none is slower. - A write by a name known only at run time (
v[k] = x) costs 134 instructions instead of 300 when the receiver is a class instance, 379 instead of 534 for a typed record, 349 instead of 528 for a parsed object and 319 instead of 501 for an object literal;k in vcosts 112 instead of 278 for a class instance, an element read 109 instead of 174, atypeoftest 87 instead of 125, and an object spread of three members 1,324 and 3,160 instead of 9,364. Executables that link the changed functions grow by 256 to 512 bytes. - A typed array read through an untyped value reads the element from the array itself:
v[i]costs 508 instructions an item instead of 1,189 for an array of class instances and 346 instead of 1,026 for an array of numbers, with identical compiled code. - A property copied from a layout-mode object onto an array view takes the ordinary writable, enumerable, configurable write instead of the descriptor path: 300,000 Array subclass instances with three fields, each read once through a view, take 206 ms instead of 559 ms, against 18 ms in Node.
- A type that carries the collector header only for the weak analysis is emitted without cycle-capable children, so it nominates nothing to the collector: a program of 200 plain records a round filed in a
WeakMap<object, …>over 25 rounds costs 242,083 instructions a round instead of 349,827, against 217,117 before the weak analysis needed the header. - An
Object.freeze,Object.sealorObject.preventExtensionscall that can never run, underif (false), aconstthat isfalseor a comparison--definedecides, no longer sets the program-wide integrity flag: a loop of five field stores and one object goes from 156 to 97 instructions an iteration, and its executable from 382,520 to 188,864 bytes. - A
switchon the code unit inside a scanning loop and aforwithout an initializer take the UTF-16 units copy, at 7.7 instructions a character for non-ASCII text instead of 26.7 and 8.0 instead of 22.0 for a counter declared before thefor, and a chain of fivereplaceAllcalls with one-character literals becomes one scan at 410 instructions instead of 637. - A question about one member of a Node stream held in an untyped value no longer builds the stream's member view when the name is absent or the question is
inorReflect.has:typeof h.flushon aWritablecosts 541 instructions instead of 33,952, and asked twice 867 instead of 73,587, with every answer unchanged. - The own-key questions a serializer asks about a value held under a closed record type, whether its prototype is
Object.prototype, how many own keys it has and whether a key is a given name, are answered from the record's static layout and one runtime fact: 44 instructions a question instead of 985 for one member withObject.keys, 41 instead of 4,777 withReflect.ownKeysandObject.keys(r).length, and 78 instead of 20,205 for 11 members. A record that kept a source object, a linked copy or a live capsule falls back to building the key array. - A write to an untyped record by a name known only at run time costs 100 instructions instead of 361, against 129 for the same write with the name in the source. A program with no such write is identical to its previous build.
- A field append (
this.text += s,obj.text += s, a record field) is linear instead of quadratic: at 10,000 characters an append costs 126 instructions instead of 2,225, and at 1,000,000 characters it costs 125 where the previous build does not finish. A localtext += piececosts about 80 instructions instead of 146. - The first
.lengthof a finished JSON document scans it 32 bytes a step and marks it ASCII instead of walking it through the index, at 155 additional instructions instead of 563. parts.join(sep)on astring[]costs 21.5 instructions an element instead of 53.0 with an empty separator and 22.5 instead of 63.0 with a comma, andArray.from<string>({ length: 2000 })14.8 instead of 33.0. The join is a typed runtime entry of its own: the 1,745 programs that join a string array grow by 720 to 856 bytes, and the 3,138 programs that never call it shrink by 640 to 704 bytes.- A
string | null,string | undefinedor three-state union keyed in aMapor held in aSetstays the string pointer, so one such collection no longer boxes the union in every position of the program: astring | nullfield makes 0 union boxes per 1,000 iterations instead of 750,Set.hascosts 91 instructions instead of 226,Map.get90 instead of 251,Set.add118 instead of 335 andSet.delete112 instead of 286. - A chain of
setImmediatecallbacks no longer grows the immediate queue: 200,000 turns of a chain peak at 1,660 KB of maximum resident size instead of 6,524 KB, and a turn that queues nine immediates peaks at 1,644 KB instead of 48,508 KB.
- A read through an untyped value of a typed record or array no longer copies it: three React render loops execute 31% to 57% fewer instructions per render (294,339 to 197,439, 6,765,442 to 4,653,659 and 6,648,657 to 2,880,208 by callgrind), and the
- Faster builds:
- A statement that holds many values across steps that can throw compiles in linear time instead of quadratic release calls: a dynamic object literal of 494 members builds in 15.9 s instead of 52 minutes 43 s, and generated tables of 100, 200 and 400 strings take 0.17 s, 0.28 s and 0.67 s of native code generation instead of 6.6 s, 60.4 s and 535 s.
- The TypeScript 7.1 symbol table keeps one parsed descriptor per source file instead of one per symbol: the compiler's own build holds 3,404 MB of live heap at 137 bytes per source byte instead of 3,572 MB at 144, and among the 1,443,719 references that carry a descriptor for 889 files no two descriptors for one node ID differ.
- Every uncompiled
import()in one statement is decided in one island round, so 29 programs take fewer lowerings (142 to 102 in all) and a large program's build time grows 1.0% instead of about 49%.
- Node.js parity:
writeFileSync,appendFileSync, thefs/promiseswrites, the descriptor form andFileHandle.writeFileandappendFileaccept anyArrayBufferView, including a view over WebAssembly memory, a subarray at an offset, aDataViewand a typed array of any element, and write the bytes it covers; other data throws Node'sERR_INVALID_ARG_TYPEwith the argument checks in Node's order. 200,000 descriptor-form writes of a 4 KiB view take 25.5 ms against 74.5 ms in Node.new TextEncoder(label)evaluates and ignores its arguments as Node does, including spread arguments, sonew TextEncoder("utf-8")compiles, and construction runs 1.63x faster than Node's (373 ms against 610 ms for 20 million encoder and decoder pairs).Buffer.alloc(size, fill, encoding)andbuf.fill(value, offset, end, encoding)with arguments that have no static type are filled by the kinds the values have at run time instead of being refused withSC2020.- Terminal members of a stream (
isTTY,columns,rows,isRaw) read through an optional chain, off an alias ofprocess.stdinor bound by a pattern answer the declared type plusundefinedon a terminal, a pipe, a file, the null device and a closed descriptor: 501 of 619 cells match Node instead of 283, and the 34 cells that differed now match. getColorDepth()andhasColors()on a process stream answer by Node's rules on a terminal and on a pipe: 115 of 132 rows match Node instead of 5.- A builtin global read by name off a stored global object (
const root = globalThis; root.Headers) reads the value the bare name reads (2,687 of 3,480 cells match Node instead of 1,055), and a program that asks the global object about itself gets Node's answers. process.hrtimereadsmach_continuous_timescaled by the time base on macOS, the clock Node reads, so a Node island's load-time reading and the program's own reading share one origin.
- TypeScript parity:
instanceofanswers for every kind of untyped value instead of throwing: aUint8Arrayor aURLtested against a program class answersfalsewhere the previous build reportedSC2020, andv instanceof Errorinside a chain of class tests answers instead of reportingSC1090. Over 1,294 cells of four corpus programs, 181 cells differed from Node on the previous build, every one a throw, and none differs now.- The
argumentsobject answers as Node does forArray.isArray,Object.prototype.toString.call,arguments instanceof ArrayandJSON.stringify, and the builtinconcatover it is refused by name: over a family of 147 cells, 114 match Node instead of 75. Object.assigncopies in its source's key order and aconstbound to its result is its target,Object.getPrototypeOfof an instance held as its base class answers the instance's own class, and a record type whose objects create their keys in orders the compiler does not follow per object is reported once as the warningSC6014, which--no-key-order-warningleaves out.- A generic function over an interface held as an untyped value gets one instance per set of type arguments (292 of 292 cells match Node instead of 186, where the previous build threw
TypeError: expected number at $, got string). - A generator, async or async generator function reports
GeneratorFunction,AsyncFunctionorAsyncGeneratorFunction, and a handled rejection is not reported when the same promise also crossed into an untyped position: 150 of 336 cells that ended in a build refusal or a run-time error become matches. - A function's body lowers when a destructured parameter's binding has no static type, and a
Promise.allposition whose type has no st...
v0.4.10
0.4.10
Highlights
- Faster programs:
- A function literal written where a function with more parameters or other parameter types is expected is lowered once with that slot's parameter list instead of being wrapped in a signature adapter: a loop that calls a two-parameter callback 12 million times takes 154 ms instead of 1,544 ms.
- Typed-array conversion between integer kinds stores each element directly instead of routing it through a double:
set()of 131,072 elements from aUint8Arrayinto aUint16Arraytakes 1.9 ms instead of 61.2 ms, and thetyped-array-convertworkload runs at 0.65x Node's time instead of 1.29x. - Loop-bound versioning covers loops that write record fields, so
advanceinn-bodydrops its double counter and its double-to-integer round trip per index: scale 1 under--layouts=4 --runs=16takes 132.5 ms instead of 158.4 ms. new Error(message),Error(message)andsuper(message)into a builtin error base use the typed constructor when the message is a native string orundefined: a throw-and-catchnew Error("too big")executable is 96,760 bytes instead of 307,392, and 774 of the 779 changed corpus executables are smaller.- A same-shape clone snapshots its source's own keys only when the source keeps keys the plain copy would not reproduce: a tree rewriter over
Exprvalues built by literals runs in 311 ms instead of 3,769 ms once the program also parses oneExprand prints it. - A boxed function's name lookup sorts the module's closure-name table once and binary-searches it: with 5,178 named functions, 600,000 boxes take 166 ms instead of 1,349 ms.
- A regular expression's character class is collected, sorted and merged into the class once: six patterns of two properties and 60 ranges each compile in 1.19 ms instead of 7.20 ms.
- A start of a program with Node islands no longer parses the manifest or reads and hashes the island module tree: a warm
--versionof pi takes 560 ms and 157.7 MB instead of 1,050 ms and 171.6 MB, and makes 14,000 system calls instead of 232,000. - A regular expression anchored at the start is searched with one attempt when every path through the pattern meets a non-multiline
^first, a non-ASCII subject is matched on the bytes its string holds instead of being converted to UTF-16, ands.slice(k)on a non-ASCII string no longer reads the whole string to find its end: a tokenizer over non-ASCII text takes 12.9 ms instead of 4,876 ms against Node's 90.3 ms, a loop over a 600,000-character text takes 0.30 s instead of 381 s, a slice of one takes 10.3 µs instead of 59.1 µs, and a regular-expression call on a new non-ASCII string takes 0.04 µs instead of 64 µs at 100,000 characters. The 260 programs that link the regular-expression unit grow by 832 to 18,880 bytes. x instanceof C,new C()andC.memberwithCan untyped value, and a computed read on an untyped value, index a table from eight class values, sixty-four for computed reads, instead of walking every class of the program: a 240-class program takes 696 ms instead of 2,183 ms.- A list
Object.valuesmakes over a checked-dynamic object stays a native list throughmap,filter,flatMap,reduceRightandfindLastIndex, and the callbacks are called directly: thecatalog-tablesworkload takes 95.0 ms instead of 186.7 ms. - A record that is one arm of a union stays the same object in a checked conversion, so a write through the union value reaches the record: collecting a 300-record table takes 0.223 ms instead of 37.6 ms, and the
stream-buffered-drainworkload takes 44.7 ms instead of 59.8 ms. - A loop over rows of numbers or numeric class instances runs the rows as masked lanes, each lane running its own inner loop with the instance methods inlined: the
value-objectsmandelbrot workload takes 0.7571 of its former wall time, and every other runtime workload's generated code is unchanged. - A loop's carried values are packed only when the pack gains more than it costs, so a break-even pack stays scalar instead of adding insertion and extraction to the loop's carried dependency chain: a Mandelbrot sampling loop whose periodicity test is
|dx| + |dy| < epsruns 17% faster with the same output, and the runtime benchmark set is neutral. - A program's start-up code is emitted first, ahead of everything else in the image, and its symbols and the runtime functions they call are ordered first at link time: a program that uses fx-ts reports 186 to 191 minor faults instead of 227 to 230, touches 16 text windows instead of 37, keeps 229 to 234 resident text pages instead of 538, peaks at 3.0 to 3.1 MB instead of 4.2 to 4.5 MB and takes 0.877 to 0.886 of its former wall time.
- A program with island packages it does not use starts in 2.2 ms instead of 75.1 ms, one that uses them starts in 71.6 ms instead of 87.7 ms and cold in 60.5 ms instead of 76.0 ms, for executables 1.15% larger.
Math.random()draws from a per-thread xorshift128+ generator seeded once per thread from the system's secure source instead of asking the secure source for every number: 5,000,000 calls take 20 ms instead of 184 ms, a call takes 3.4 ns instead of 36 ns against Node's 11 ns, and themisc:math-randomworkload takes 3.0 ms instead of 36 ms.node:cryptoand WebCrypto keep the secure source for every call.- A captured binding that is filled once before a closure over it is created and that nothing rebinds afterwards is captured by value instead of through a heap box, and a binding whose every capture is of that kind has no box: the compiler's own emitted module has 10,422 boxed captured bindings instead of 43,472,
cyclic-graphsruns 12.3% faster, and the compiler's own executable is 106,875,664 bytes instead of 108,383,576. - A typed call of a method that an untyped write somewhere could replace is the declared call, direct or virtual, while the receiver has no own dynamic property and no prototype object of its class family exists: a loop of one-line method calls takes 4.3 to 4.4 ns per call and 58 instructions instead of 93.7 to 102.4 ns and 1,215, and its executable is 545,432 bytes instead of 572,528.
- Faster builds:
- A comment or whitespace edit reuses the cached frontend only when every recorded text read answers the same: a comment appended to a typed CLI that imports commander rebuilds in 0.372 s instead of 8.53 s. A build with a literal
require.resolveis cached, so the compiler's unchanged rebuild takes 11.8 s instead of 1,143.6 s, andSCRIPTC_TIMING=1reports why a build is not cached. - A build whose
tsconfig.jsonlists the output's directory restores the executable from the cache, so the second and third builds take 0.39 s instead of 1.33 to 1.38 s. - A call chain of 12 links lowers in 37 lowerings and 1.2 s instead of 24,584 lowerings and 3.7 s, and a 30-link chain builds in 1.5 s where it previously exceeded 60 s at 19 links.
--emit-lowered <file>writes the lowered program a build produced, and--from-lowered <file>builds from it without starting a type checker or lowering anything: the compiler's own source builds in 311.7 s instead of 770.8 s, and a changed source, option or compiler build is refused by name withSC3002.- The canonical-type walk maps each object node once per depth instead of once per path, and the type mapper answers each type once per depth:
scriptc coverageon json-schema-to-typescript finishes in 113 s at 2.0 GB where the previous build was stopped at 420 s, and pi's first lowering fixpoint takes 9.4 s instead of 147 to 153 s. - Lowering drops lifted functions the module no longer reaches: about 12,600 of the 54,700 functions in the self-hosted compiler's module had no caller, the phases after lowering take 8% less, and its executable is 41,520 bytes smaller.
- A run of more than 64 consecutive literal items that differ only in numbers, strings and booleans is built from columns in the program image: a 20,000-row record table builds in 15.5 s instead of 1,114 s with a 1,465 KB executable instead of 7,519 KB.
- A lowering pass that a later pass replaces ends where it asks for the next stream mode instead of lowering the rest of the program: the compiler's own lowering falls from a mean 656 s to 471 s, and its self-hosted build is 19.26% faster. A class that extends
Arraykeeps itsarrayFieldsandarraySpeciesfunctions on the pending flag, so 20 corpus programs that emitted twice now emit once. - Lowering interns each finished function's long string literals, so one string is held per failure-message text: the compiler compiling its own source has a largest heap of 4,105 MB instead of 6,424 MB and builds 2.7% faster.
- A typed program with a generator records the ambient receiver only when the module reads a generator's
this, which removes a dynamic receiver read, 3 KB and 15 symbols from every such program: 111 of the 239 changed executables in a corpus build shrank, up to 4,920 bytes. - An npm-static build no longer computes diagnostics nothing reads, so its frontend loads fall by 19 to 56 s in five of six measured pairs (median 31 s).
- A build sends the LLVM codegen helper's
version,emitandmerge-profilescommands to a resident server over a Unix socket and falls back to a process per build when no server answers; cold native builds of the Benchmarks Game programs run 3.9% to 8.8% faster, with executables identical to those of a build without the server. - Ending a stream program's push-down pass once both of its stream flags are set shortens a build of
packages/coding-agent/src/cli.tswith 15--npm-staticpackages from 1070.3 s to 882.5 s, 17.5% faster. - Nursery bypass statistics are kept per closure body, in a per-thread table keyed by the closure's function, instead of one family each for closures and for boxes holding closures, so a program's long-lived closures no longer keep its short-lived closure cycles in the nur...
- A comment or whitespace edit reuses the cached frontend only when every recorded text read answers the same: a comment appended to a typed CLI that imports commander rebuilds in 0.372 s instead of 8.53 s. A build with a literal
v0.4.9
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()andTypedArray.from(view)between typed arrays move same-width integer pairs with onememmoveand convert every other pair in one loop. AUint8ClampedArrayover 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
charCodeAtfrom 172.0 to 90.2 ns. Buffer.writewith 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)andprocess.stderr.write(text)calls reach one runtime call: 10,000,000 writes to/dev/nulltake 1.38 s instead of 3.13 s. The opt-in--stdout-bufferingkeeps console output in the 64 KiB buffer when stdout is not a terminal: 10,000,000console.loglines 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-dispatchcopies only the functions whose body has a loop: 1.305x on mandelbrot and 1.068x on n-body against the default build.
- Typed-array and dense
- 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,getServersand 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 defaultcert/keypair. - node:readline interfaces have
prependListener,prependOnceListener,removeListener,off,removeAllListeners,listenerCountandeventNames, with removal by the registered function, andquestion(query, { signal }, callback)cancels on abort. process.allowedNodeEnvironmentFlagsis the target'sNODE_OPTIONSflag names,processemits'beforeExit'and hasabort(), andChildProcessreportsspawnfileandspawnargs.- child_process accepts stdio arrays past fd 2,
detached,options.signalandstdio: "inherit", and spawnSync's error carries Node's errno, code, syscall, path and spawnargs fields. Buffer#toJSONreturns Node's snapshot,Blob#stream()andBlob#textStream()return real ReadableStreams,URL.createObjectURL,URL.revokeObjectURLandbuffer.resolveObjectURLshare Node's table, and fetch sends Blob and File bodies as their bytes with the blob's type.url.Urlis Node's legacy constructor, strict-mode property writes name bigint and symbol receivers,util.formatwalks Error values for%j, and typed emits reach listeners registered through untyped values witherrorMonitorfirst.- Native handles accept own properties, and a class instance's getter-backed fields read through its checked view.
process.reportis answered by the Node island, node:test's context hast.runOnlyandt.waitFor, and static builds read/etc/passwdforos.homedirandos.userInfoand throw Node's catchableSystemError.- The compatibility ledger records the implemented web globals, the
Headersclass and the node:diagnostics_channel surface against the programs that exercise them.
- node:dns lowers
- 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
deleteworks and an omitted field is absent toin,Object.hasOwn,Object.keys,util.inspect,JSON.stringifyand spread. new Set(x)andnew 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.allover a mixed armed-union array literal lowers, andObject.assigntakes 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'scall,applyandbind,RegExp.prototype'sexec,testandtoString,DataView.prototype, builtin functions carrying Node'snameandlength, and anhttp.requestalias 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()andimport()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, anddeletethrough an erased assertion. - Five diagnostics are admitted for JavaScript sources, and a project that checks its JavaScript still reports them.
- 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
- CommonJS and islands:
- A constant-decided top-level
ifexports what its branch exports, a failed CommonJS initializer retries on the nextrequire(), 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.
- A constant-decided top-level
- Fixes:
- A nested marker table is a brand marker, so branded primitives stay
Mapkeys. A parameter that shadows a builtin module alias is no longer reported as a patched module, and a generator declared to returnanylowers instead of reportin...
- A nested marker table is a brand marker, so branded primitives stay
v0.4.8
0.4.8
Highlights
- Faster programs:
- Counted loops run two iterations at a time where the iterations are independent: a single-file mandelbrot port is 39% faster.
- A class whose instances form a tree, each node owned by one parent, keeps its nodes in the group arena that holds the rest of the tree, at 16 bytes per node instead of 24: a class-based binary-trees program is 20% faster and uses 33% less peak memory.
- A dictionary update fuses its second hash probe into the write that follows (4.5% faster on dictionary walks), and number and identity-word map keys hash with one multiply instead of two (6.9% faster on number-key maps).
- A run of dynamic property reads on one object shares one guard, untyped code calls typed records' dynamic parameters through specialized calls, and dynamic class tables dispatch on an indexed class key.
- Compact class fields store 32-bit references to arrays, maps, sets and instances, so programs that keep many objects use less memory.
- musl executables use an allocator built for the program they link into: across the runtime benchmarks they are 2.9% faster, line-by-line reading is 39% faster, and an HTTP server's peak memory falls from 34.6 MiB to 2.4 MiB. musl executables also use musl's string comparison and a faster
getenv. - x86-64-v3 builds keep baseline copies of the program's functions.
- Faster builds:
- The thin-LTO pre-link runs at the build's optimization level: a cold build of a large program is 2.4% faster, with the LLVM stage 10% faster and the same runtime speed.
- The partition cache also covers builds that name their LLVM helper with
SCRIPTC_LLVM_HELPER: a warm one-function edit of a large program rebuilds 13% faster in that setup. - A build shares one frontend services scope across its loads, memoizes type mappings per generic binding context, shares type-memo entries that read no type-parameter binding, and computes its preflight diagnostics once. Island rounds share their program facts across lowering passes.
- LLVM partitions are sized from the host's threads as well as the module, the emission passes read their node scans from one recorded walk, and IR functions decode without a whole-document number reviver.
- The LLVM helper's identity is checked once per install, the installed compiler package digest is reused, and edit builds keep less per-build bookkeeping. The native compiler's optimization profile is refreshed.
- Node.js parity:
- node:path, node:url and node:os match Node 24. node:buffer matches Node 24's Buffer members, encodings and module surface. node:zlib adds the zstd API.
util.inspectsupportsshowHidden,numericSeparator,maxStringLength,maxArrayLengthand thegettersoption,console.dirsupportsshowHidden, and errors print from their stack inutil.format,util.inspectandconsole.log.- child_process lowers the shell forms, typed
spawnSyncoptions includingcwdandinput, thecwdoption of the exec and execFile callback forms, and child stdio pipes typed as node:stream streams. - node:http applies Node's HTTP/1 parser checks, answers ClientRequest listeners, reports late Transfer-Encoding errors, and lowers the server
'clientError'event and the URL-plus-options request form withprotocoland shorthandheaders. process.stdinhas its readable surface, readline acceptsprocess.stderroutput and runtime question options, andgetEventListenersanswers for EventTarget receivers.- ArrayBuffer transfer, detach and resizable or growable buffers compile. A cgroup CPU quota below one CPU is floored at one.
- Node.js is pinned to 24.22.1.
- JavaScript semantics (Test262):
- More early errors are reported,
RegExpcoerces its constructor arguments, and the async-generatoryield*delegation protocol is implemented. - Class values honor
Symbol.hasInstance. Promise statics accept a foreign constructor. Iterator helpers run on a liveIterator.prototype. - BigInt typed arrays work in the checked-dynamic tier, and builtin global functions and classes have values in JavaScript sources.
- Generator parameters bind at the call, higher-order functions honor
thisArg, pattern defaults take their binding's name, and object literals accept__proto__with any prototype value.
- More early errors are reported,
- Node islands: coverage lowers once with the islands the build proves, packages that are not opted in keep their JavaScript out of the inferred surface, and npm-static packages are classified only from real loads.
- Correctness: a record reached through an alias keeps writes made through either name when its shape widens under the shape overlay.
- TypeScript: more record, union and generic patterns compile, among them overload sets held as values, class overload declarations, unions of a class and its subclasses,
Object.assignover a narrowed record arm, and partial record views that gain keys through dynamic writes.
v0.4.7
0.4.7
Highlights
- Node.js parity:
Intl.Segmentersplits text into graphemes and words, andtoLocaleStringuses the default locale, on Linux x64 (ICU 78.3). Malformed UTF-8 in directory entries and command-line arguments decodes to U+FFFD, as in Node.- node:http emits Node's abort events, matches Node's outgoing header behavior, and checks
http.requestoptions before a socket opens. Sockets aftersetEncodingpipe decoded strings, untyped listen ports are validated, and net errors carry Node's system properties. - node:querystring and node:string_decoder match Node 24 for values known only at run time, and node:path matches Node 24's path tests.
TextDecodermatches Node's errors, options and printing. structuredClonecopies Maps and Sets.SharedArrayBuffer,DataView,BigInt64Array,BigUint64Array,ProxyandIntlcan be read as values.process.memoryUsage()counts ArrayBuffer storage.import()of a specifier that cannot be resolved rejects withERR_MODULE_NOT_FOUND. fs reads return Buffers in programs that can observe the difference. Values held fromprocess.envread the live environment.- EventEmitter and stream listeners are called with the emitter as
this, listeners that readargumentsreceive every argument, and the leak warning is delivered at exit.'error'events accept non-Error payloads. - readline accepts listener values and ends lines at U+2028 and U+2029.
fs.watchnames the changed entry.assert.throwsreports validation-object failures as Node does. - Executables ignore SIGPIPE and SIGXFSZ, as Node does. Timer deadlines hold on macOS, and intervals re-arm as in Node. Timeout delay warnings are emitted, and untyped timer delays convert with ToNumber.
process.emitWarning,process.kill,process.chdirandos.userInfobehave as in Node. Replacingprocess.stdout.writeorprocess.stderr.writereceives the arguments, Buffers and whole writes that Node passes.- JavaScript semantics from Test262: each evaluation of a class expression creates its own class, private static members check their brand, wrapper objects for strings, numbers and booleans work in JavaScript sources, and Set, Map and weak-collection methods can be read as values.
for...inruns over a value that may be null or undefined. A null or undefined destructuring source throws Node's TypeError. Functions held in records and arrays keep their names.
- Node islands:
- Sloppy-mode modules and modules that patch builtins run in Node islands automatically.
- Islands can
requireCommonJS modules and hold builtin classes, and garbage cycles are collected across the program's heap and Node's. - Island modules whose load is deterministic are deferred until first use, and shared modules resolve by real path, so caches reached through symbolic links work.
- Faster programs:
- Untyped code calls methods of compiled class instances inline.
- Integer arithmetic: sums built on unsigned shifts stay in 32 bits, and loops are versioned on the range of locals they write as remainders. Saturating loops that run together test their exit every fourth iteration.
- Number-key map lookups test the direct index once and inline the probe in loops.
- Streams: taking from a Readable's buffered queue is constant time, object-mode stream callbacks do less work per chunk and keep typed records unboxed, and a line goes to a single line listener directly.
- fs streams read and write cached regular files on the loop thread, worker threads that allocate large blocks no longer reread the process's memory map, and constructors store their own fields without releasing a previous value.
--cpu-dispatchkeeps x86-64-v3 code from running slower than the baseline.- Profile-guided builds compile the functions a profile never ran as cold code, and weigh profile matches by the profile's counts. The runtime profile is refreshed.
- x86_64 musl executables use SSE2 memory routines and the runtime's rounding and libm definitions.
- Faster builds:
- The native compiler's optimization profile is refreshed: its code is 63.0 MB instead of 73.9 MB, so builds use less memory and run faster.
- Optimized partitions of builds without a profile run a lighter simplification before they are linked, and builds that use a profile keep the full one. Partition placement stays stable when an edit adds a small helper, so more partitions stay cached.
- A compile reads and stats each file once and reuses its type checker across module loads, and finished checker processes no longer stay as zombies.
- The type checker executable is written once, and builds with an empty cache no longer hash the runtime pack's artifacts again.
- Large modules decide in one pass whether fs reads must return Buffers.
- Memory and size:
- Programs return freed memory sooner: large strings are freed when they are released, writes of large shared views go out in chunks, and the main thread returns free pages when its event loop waits.
- An HTTP response whose socket closes releases its completion callbacks, and the web view of an abandoned fetch body is released while its transfer runs.
- Intl executables ship ICU data without the categories no Intl form reads (21.5 MB smaller), and the runtime pack is 24 MB smaller on Linux x64.
- TypeScript: an
import sourceclause from TypeScript 7.1 is reported as unsupported instead of failing the build, and more record, union and generic patterns compile: spreads of unions with an absent arm, literal-union keys of one field type, partial record assertions, records cast fromunknown[]elements, andentries().filterin generic bodies.
v0.4.6
0.4.6
Highlights
- Node islands: programs can load Node.js in-process on first use and run npm packages that scriptc doesn't compile in Node islands.
- Native values, closures and promises pass into islands by reference, and island handles, classes and collections can be used from compiled code.
- Modules are shared between a program and its islands, and island modules are evaluated only when the program uses them.
- Once Node loads, the runtime's event loop runs on Node's libuv, and programs exit cleanly with Node loaded.
- New options:
--exceptions=rareunwinds through recursive functions instead of checking a status after each call.--staticbuilds static Linux executables (opt-in).--large-pagesmoves executable code onto huge pages;--cpu-dispatchadds x86-64-v3 copies of hot functions.- Type patches replace a package's declaration files or supply missing declarations.
- The shape overlay types JavaScript from facts the compiler proves (opt-in).
- Faster programs:
- Exceptions raise through cached unwind plans on x86-64 Linux, and every native function has an unwind table entry.
- Ordinary objects are stored as a layout plus value slots; unions of classes are the instance pointer itself; class instances that stay in their frame use frame storage.
- Object groups that die together are allocated in arenas, and the cycle collector does cheaper trial deletion and adapts its nursery trigger.
- The speed runtime is optimized with a committed runtime profile, and Math functions are computed as V8 does, with libm linked only when needed.
- Loops: short constant-count loops run together, for and while loops are versioned on the ranges their locals enter with, counted loops unroll by the period of a guard, and small leaf functions called from loops are inlined.
- Typed arrays: header fields are read once at a speculative loop's entry, products under bitwise operators stay in 32 bits, and shared typed-array elements are read and written with plain accesses at a counted loop's counter.
- Strings: UTF-16 indexes of long non-ASCII strings map in constant time, ASCII case converts eight bytes at a time, template literals allocate at their exact length, and strings iterate over their bytes.
- Untyped code: class instance fields are read inline at dynamic property sites with two inline-cache entries, native arrays iterate without type-name scans, and numbers pass to directly called untyped functions as JavaScript values.
- I/O: fs thread-pool completions and pool-thread writes make fewer syscalls; sockets, clients and agent entries register in constant time; fetch() reuses keep-alive connections; web streams, WritableStream and TransformStream run natively.
- Large executables map their text with 2 MiB pages, and freed heap pages are returned to the system after the frontend is disposed.
- Faster builds:
- Small release and speed programs compile in balanced partitions, the entry module's top-level code gets its own partition, and unchanged partitions stay cached across edits.
- Optimized executables that stay one partition generate code concurrently, and emission's workers stay busy.
- The native compiler is laid out by context-sensitive profiles collected in two phases.
- Type checking uses a shared tsc-ts server by default on Linux and macOS, and lib diagnostics are reused across options and module sets that cannot change them.
- Rebuilds skip the helper version probe and the digests of unchanged files, and the CLI links without huge text pages, so cold builds use less memory.
- Node.js parity:
Error.captureStackTrace, Node's navigator, builtin globals read through the global object, and unhandled rejections reported in programs without async work.- node:zlib is built on the zlib Node bundles;
Response.cloneandRequest.clonework on typed values; fetch bodies have the members of a web ReadableStream; redirects whose chunked body has not ended are followed. - Classes that extend Array compile; strings spread into their code points; frozen checked records stay frozen; sloppy-mode
thisis the global object. util.formatruntime format strings,inspect.customand colors, and untypedpromisify.
- TypeScript: scriptc reads TypeScript 7.1's binary AST format and API server beside 7.0's, and tsc-ts is updated to 0.0.10.
v0.4.5
0.4.5
Highlights
- Fixes:
- Release builds read a typed array's length without a header load that could read a freed view.
net.Socketwrite()returns Node's backpressure boolean and fires'drain'.- Super calls in JavaScript method values call the declared base method.
- Calling a non-function through an asserted callee throws Node's TypeError.
- Faster programs:
- Exceptions propagate by unwinding from directly called functions on Linux, instead of status checks after every call.
- Typed-array loops prove their bounds and keep their views disjoint in more cases, including views over shared memory in worker programs; integer loops stay in integers across nested counted loops, pure helper calls, powers of two and byte reads.
- Lone array callbacks run as loops with their bodies inlined, and row sums run two rows per pass.
- Number-key maps look keys up on their integer view and write updates through the entry the read found; small number-key maps use a direct index.
- Strings: base64 decodes eight characters at a time; Buffers fill, swap byte order and decode
utf16lea word at a time; readline splits input in one scan; string scans over non-ASCII strings use their UTF-16 units. - Untyped code: property caches also cache absent properties, object literal members go through the store caches, calls on plain arrays skip the method ladder, and dynamic values dispose without key-cache walks.
- JSON parses into discriminated unions, and dynamic values convert into nested unions, in one pass.
- BigInt division settles quotients from the top limbs, and limb shifts are vectorized.
- Faster builds:
- Function bodies are emitted on up to eight workers, including in dev builds, and repeated work in LLVM module emission is cut.
- Linux executables link with lld when the driver finds it, and unchanged executables are reused without selecting the runtime pack.
- Dev builds reuse their frontend output after edits that keep token positions, and unchanged frontend inputs are checked by file identity before hashing.
- Lowering fetches only the syntax trees it reads, and the large compiler tables are built on first use.
- Release builds of the native compiler use a profile collected on the release commit.
- Type checking can use a tsc-ts server shared by later builds (opt-in).
- Node.js parity:
- Crypto: RSA key generation lengths, RSA-PSS keys, encrypted private keys,
X509Certificatefields and methods, and TLS certificates andtls.Servercontexts. - zlib: Brotli compression and decompression.
- Dates are mutable objects with Node's setters and conversions; URL hosts and paths parse as the URL Standard does, including non-ASCII hosts with UTS #46.
Math,JSONandReflectare real objects in JavaScript; objects can inherit from JavaScript class prototypes; typed arrays held as untyped values support named properties andSymbol.species.- Error messages:
JSON.parsesyntax errors use V8's messages and positions;Object.getPrototypeOfof an Error answersError.prototype. util.inspectof dynamic classes, functions and%oarguments, andSymbol.toStringTagfields and getters, match Node.
- Crypto: RSA key generation lengths, RSA-PSS keys, encrypted private keys,
- npm packages: more npm and CommonJS dependencies compile statically, including namespace-style packages with guarded declarations and
varexports, and untyped Maps, Sets and arrays are typed from their uses.
v0.4.4
0.4.4
Highlights
- Fixes for 0.4.3:
- Unchanged rebuilds hit the frontend cache again. An unchanged rebuild of a 23-module program takes 93 ms instead of 1.26 s, and of tsc-ts (dev) 1.6 s instead of 58.2 s.
- The native compiler builds programs with several typed-array accesses at one masked index in a loop again, including tsc-ts's release build, which failed with SC3004.
x!returned from a function, used as an arrow function's result, or passed as an argument keeps null or undefined when the destination type holds it, as JavaScript does.x!operands of+,+=and other conversions that hold undefined or null convert as Node does.- Effect 4 programs whose generators yield clients built by other generators run again.
- The cache for untyped property access in programs that declare classes is reverted, fixing a crash in Effect 4 observability programs.
- Faster:
- Number compares and typed-array indexes use the exact integer of sums like
i + 1: a heap sift-down over anInt32Arrayruns 17.5% faster. - glibc's per-thread heaps use huge pages: the VS Code type-check runs 3.4% faster.
- Mid-size executables (over 1 MiB of LLVM text) build in at least eight partitions: the LLVM backend phase takes 34 to 74% less time for three.js programs of 1.4 to 7.8 MB.
- JavaScript class instances are boxed at their exact class, and stores into nested string-keyed dictionaries keep class method calls direct.
--target-cpuselects an x86-64 microarchitecture level.
- Number compares and typed-array indexes use the exact integer of sums like
- Node.js parity:
- X25519 keys and
crypto.diffieHellman;existsSyncandfs.promisesacceptfile:URLs;Buffer.fromranges over ArrayBuffers, the deprecatedBuffer()constructor andBufferas a value. - Dates are objects with identity;
Promise.allover different entry types settles on Node's turns; completion callbacks run in their call's AsyncLocalStorage context. - TypedArray methods run on typed arrays held as untyped values, and unions of typed-array classes compile. Borrowed
TypedArray.prototype.setcalls compile and throw Node's TypeError for other targets. - Split CommonJS
requirebindings,__-prefixed exports, string-key destructuring ofanyvalues, and record key order for values JavaScript built.
- X25519 keys and
- npm packages: zod 4 and
@hono/zod-validatorvalues are projected at the declaration boundary.
v0.4.3
0.4.3
Highlights
- Faster checking and smaller builds with tsc-ts 0.0.9: scriptc checks with tsc-ts 0.0.9, and builds reuse the bundled declaration files' diagnostics instead of checking them again. Small IR builds are 10.6 to 16.0% faster and small executable builds 5.5 to 7.6% faster, with identical output. A native build of tsc-ts with this release checks 0.4 to 6.6% faster than with 0.4.2 and peaks 4 to 16% lower.
- Integer arrays and loops:
- Number arrays that only hold int32 values use 4-byte slots in locals, across direct calls and in module globals, and loop-carried locals written from their elements stay int32. A workload that walks arrays of cursors runs 54.5% faster.
- Loops with two counters closing in on each other (
for (let i = lo, j = hi; i < j; i++, j--)) and element copies througha[i]!run without per-element checks, and two-counterwhileloops run on integer counters. - Loops over typed arrays and charAt scans are versioned on their bounds, and ASCII strings are scanned as bytes.
- Less work for untyped and dynamic code:
- String-keyed record reads under
??and!no longer allocate: a dictionary-walking workload makes no union boxes instead of 363,359 and runs 12 to 15% faster. - Checked-dynamic method calls dispatch on method ids, observed class methods call natively while their slot is unchanged, and calls on bag-tracked class instances skip building their views.
- String-keyed record reads under
- Node.js parity:
crypto.getCipherInfo, AES-CCM, AES key wrap, the legacycreateCipherivmodes,checkPrime/generatePrime, and Node's argument checks forgenerateKeyPair.- Streams:
construct,writev,decodeStringsanddefaultEncoding, includingwritevonTransformandPassThrough. FinalizationRegistry,new.target,String.prototype.aton untyped values,Error.stackTraceLimit,spawnSync'senvoption, and class constructors called throughcall/applythrow Node's TypeError.- Records enumerate in the key order their object literal wrote, and objects converting to a union with an empty-record arm keep every field.
- npm packages: zod 3 compiles natively and zod 4's core runs, minified packages such as marked compile, chalk 5 and hot-shots 8 build from built-in shims, and
@hono/node-server2.x is built in. User code types npm package declarations at a checked, identity-preserving boundary. - Builds: the cached build is reused after whitespace and comment edits, function bodies are emitted on forked workers, and the LLVM emitter reads its analyses from one recorded walk of each module.