Recursion makes a fraction of the calls, loops keep objects in registers, and element writes stop searching
This release makes three kinds of everyday JavaScript faster. Recursive functions now answer their simplest cases where they are called and run every other level inside their parent, so walking a tree makes a fraction of the calls, and recursion whose last step returns a property now compiles at all. Long loops that walk a list, keep the best object seen so far, copy objects, compare them or ask typeof now run in Floati's fastest loop tier, 20 to 40 times faster than before. Setting a property on page elements no longer searches every element prototype each time, so building elements is almost three times faster, and a getter a page puts on an element prototype now answers reads as it does in the reference browser. One crash is fixed: freeing a long linked list made inside a function no longer overflows the stack.
Recursion makes a fraction of the calls
A recursive function usually starts with its simplest case: if the tree is empty, return 0; if n is below 2, return n. Floati's recursion tier now runs that test where the call is made, so a call that would only answer the simple case is never made, and it copies one level of the function into its parent, so every other level of a tree runs without a call at all (a technique from research on divide-and-conquer programs). Each copy still counts toward the call-depth limit and the work budget exactly as a call would, so a runaway recursion stops where it did. Once a recursive function has run, its property reads are compiled again with the object layouts they saw built in. Recursion whose last step returns a property, like the depth of a tree that returns a leaf's value, used to fall back to slower code because one path returned a number and the other a value of any type; now the two paths meet as a number, and anything else hands the work back to the interpreter.
Loops keep objects in registers
Floati's fastest loop tier kept numbers in registers, and objects only when the loop never changed which object a variable held. A loop like for (n = head; n !== null; n = n.next), or one that remembers the largest object seen so far, was refused and ran in a slower tier. Now such a variable holds the object directly, with no counting or copying, because these loops write nothing but their own variables: nothing they point at can be freed while they run. They can read properties, compare with null and with each other, copy, ask typeof and test truthiness; anything unusual, a getter, a missing link or a string compared by identity, hands that iteration back to the interpreter, which finishes it exactly. These are long loops the fastest tier now takes; a function that is already hot ran them in another tier and is unchanged.
Element writes stop searching
Every el.className = v or el.textContent = v checked whether a page script had defined that property as an accessor on one of the element's prototypes: it built two names and searched four prototypes, every time, to learn that nothing was there. On a page that builds elements this was most of its script time. Floati now remembers the answer per element type until a page changes a prototype, which makes element-building three times faster. Reads use the same memory, which fixed a gap: a getter a page defines on an element prototype, as some monitoring and compatibility scripts do, now answers reads of that property as it does in the reference browser; before, Floati answered from its own value.
What changed 8 changes
Try it in your browser
Each check runs live in the browser you are reading with. Nothing is sent anywhere.