A page scheduler that stalled now runs, try / finally and spreads compile, and deleting thousands of properties is 180 times faster
A break or continue that left a try block skipped its finally block. That broke more than tidy-up code: a widely used UI library's scheduler clears its "busy" flag in a finally that it leaves with break, so in Floati the flag stayed set after the first batch of work and every later batch waited forever. Pages built with that library looked right but never ran their later work (effects, an embedded code editor). break and continue now run every finally they leave, innermost first, as the standard says, and those pages do all their startup work, as in the reference browser. try / finally, object and array spread, destructuring from a Set, a Map or a generator, unary plus and regular-expression literals now run on the plan interpreter instead of the slowest tier, and so do for (let x of ..) loops over anything that is not an array. Deleting 64,000 properties one by one went from 5,993 to 33 ms, a function with try / finally called 2 million times from 430 to 243 ms, and on x86-64 Thermal keeps values in registers across calls in rarely run code (a float loop 315 -> 160 ms). Object spread now runs getters before converting a later computed key, and spreading null into an array throws a TypeError, as the standard says.
break, continue and finally
A finally block runs however its try block ends: normally, by return, by a throw, or by break and continue. Floati handled the first three, but a break or continue out of a try jumped straight to its target and skipped the finally. That mattered on real pages. A widely used UI library runs queued work inside a labelled block, sets a "busy" flag, and clears it in a finally it leaves with break. In Floati the flag was never cleared, so after the first batch the scheduler believed it was still working and never started another: effects never ran, and an embedded code editor never parsed its text. The pages still looked complete, which is why the page checks did not catch it. Now a break or continue that crosses finally blocks runs each one, innermost first, with its own state restored, then lands where it was going. Each finally body resumes its own completion, so a try / finally inside a finally no longer takes an outer return or throw at its end, and a return routed into a finally closes the try's handlers first. The two pages built with that library now spend more time in JavaScript, because they do the work the reference browser does. The code they now run is compiled: a for (let x of ..) loop over a Set, a Map, a generator or a string used to be refused a plan because of the jump that ends it when the iterator is done, and now it is planned.
More of every page compiles, and deletes are linear
The plan interpreter runs most hot functions. Several common constructs kept a function out of it, in the bytecode interpreter: try / finally, object and array spread ({ ...a, ...b }, [...set]), destructuring from anything iterable, unary plus on objects, and regular-expression literals (the most common refusal on a news site's load). Each is now a plan operation, checked against the interpreter in every tier on new test maps. Deleting a property from a large object shifted every property after it, so deleting n properties took time proportional to n squared. Deletes now leave a marker that is cleaned up in bulk, so the remaining properties keep their order and deleting 64,000 takes 33 ms instead of 5,993. On x86-64, Updraft IR's register allocator now keeps values in registers that a call may change, across calls in rarely run blocks, saving them only around those calls; a float loop with a rare slow path went from 315 to 160 ms.
What changed 7 changes
Try it in your browser
Each check runs live in the browser you are reading with. Nothing is sent anywhere.