Thermal runs on one code generator, building strings is up to 8 times faster, and two slow paths that grew with the square of their input are linear
Thermal, the optimizing compiler Floati writes for itself, now builds every function through Updraft IR on Apple silicon too, so the separate ARM64 code generator it started with is retired (1,400 lines fewer, a smaller browser). Updraft IR was measured faster on every benchmark first: a tiny function called from a hot loop went from 98 to 43 ms, because compiled callers now enter Thermal code directly, and integer loops keep their values in integer registers. Building strings got much faster: a CSS-in-JS class-name builder in a page went from 383 to 83 ms, summing string lengths in a loop from 91 to 11 ms, and appending a template to a string no longer copies the whole string each time. Two slow paths that grew with the square of their input are now linear: testing a prefix at a position in a long text (a WebIDL parser went from 2,670 to 592 ms) and appending templates in compiled functions. Floati also follows the standard in more places: iterating null or a plain object throws a TypeError, delete on a sealed object fails, generator objects inherit their function's prototype, and the v flag's set operations in regular expressions work.
One code generator for Thermal
Thermal compiles the hottest loops with their numbers held in registers. It used to have two code generators: one that wrote ARM64 instructions directly, and Updraft IR, Floati's own compiler core, which writes ARM64 and x86-64. Updraft IR now beats the direct generator on every benchmark measured, so it is Thermal's code generator everywhere and the direct one is gone. Three changes got it there. 32-bit integer operations use the processor's 32-bit instruction forms, so no extra masking instruction follows each one. Remainder, multiplying by a constant and Math.imul stay in integer registers, with a check that falls back if a result would overflow or be -0, so a hash such as h = (h * 31 + c) % m keeps h an integer across its loop. And code that calls a function Thermal compiled now jumps straight into it: before, every such call went through a slow general call path. On x86-64 the same changes took a generator loop from 419 to 244 ms and a tiny hot callee from 630 to 279 ms.
Building strings, and two quadratic paths made linear
A string built piece by piece (s = s + ' ' + a + '-' + b) used to make a new string at every +. The whole chain is now one join, an append of a short piece in a compiled loop goes to the end of the string being built, and short joins copy their bytes once. Two cases were much worse than slow. s += `${k}=${v};` in a compiled function flattened the whole string being built on every pass, so a million appends allocated 8.3 GB; it is 236 MB now. And text.startsWith(p, i) copied the rest of a long text on every call, so a tokenizer testing punctuation at each position was quadratic: the WebIDL parser the test suite runs went from 2,670 to 592 ms. Compiled loops also read a string's length directly (in UTF-16 units, so accented and emoji text count correctly) and read strings from arrays and objects without leaving machine code: summing the lengths of a thousand keys 2,000 times went from 91 to 11 ms.
More of the standard, in every tier
Each fix below makes Floati match the reference engine, checked in every compiler tier. Iterating something that cannot be iterated (for...of over null, a number or a plain object, spreading {} or destructuring 5) throws a TypeError instead of running zero times; new Map rejects entries that are not objects, and Promise.all accepts a Set. delete on a sealed object fails, and throws in strict code; x instanceof an arrow function throws; a generator object inherits its generator function's prototype; a let inside a try or catch block has a dead zone from the start of the block; toFixed prints 1e21 and above the way String does; and regular expressions with the v flag support nested classes, set difference and intersection. Plans, Floati's fast interpreter for hot functions, now also cover methods inside a bundle that calls eval, closures made inside blocks, and functions that reuse a name in an inner block.
What changed 12 changes
Try it in your browser
Each check runs live in the browser you are reading with. Nothing is sent anywhere.