Lowering Language Features
Closures, coroutines, async, exceptions and match expressions are all ordinary control flow after the compiler is done with them. This module does the transformation.
Sugar changes how a program is written without changing what the language can express. That makes it cheap to add and easy to underrate — the cost is not in the semantics, it is in the grammar, the diagnostics and the number of ways to say the same thing.
The pass that rewrites high-level syntax into a core language every later phase can assume. Doing it early makes every later phase simpler; doing it early is also how compilers end up reporting errors about code nobody wrote.
A function value that refers to a variable from an enclosing scope keeps that variable alive after the enclosing frame is gone. The language question is what the closure captures; the compiler question is where the captured variable now lives.
The transformation that turns a closure into an explicit pair of code and environment record. The whole design rests on one question — does the environment hold the values or the bindings — and the classic JavaScript loop bug is what that question looks like when you get it wrong.
The other way to remove a nested function: turn its free variables into extra parameters and lift it to the top level. No environment, no allocation — and it only works when the function does not escape.
A function that can pause and resume cannot keep its locals in a stack frame, because the frame does not survive the pause. The compiler splits the function at every suspension point and moves the surviving locals into a heap object, turning the body into a resumable state machine.
An async function is a coroutine whose resumptions are driven by completing operations rather than by a consumer asking for the next value. The transformation is the same state machine, plus a continuation: something has to know what to call when the awaited thing finishes.
At the source level, a non-local jump out of an arbitrary depth of calls. At the implementation level, a choice between paying nothing until a throw and looking the answer up in a table, or paying a little on every entry and jumping straight there.
The mechanism underneath exceptions: walk the physical stack, and for each frame use compiler-emitted tables to restore the caller's registers, run that frame's cleanups, and decide whether it handles the exception. This is where "zero-cost" is paid for.
An ordered list of match arms is semantics, not implementation. The compiler turns it into a decision tree that tests each discriminant once — which is why a match is not a chain of comparisons, and why the naive reading of it is quadratic in the wrong place.