Skip to content

LuaJIT blacklists loopified self-recursion while the loop body still allocates closures #204

Description

@Unisay

Problem

Loopification (#181) lowers self-recursive tail calls to while true do loops. Under LuaJIT, in the current state of the performance series, this is a measured regression on the curried_step macro benchmark (n=2e6, medians from bench/tools/run_macro.lua):

runtime recursive worker (pre-#181) loopified worker ideal
PUC Lua 5.1 0.363s 0.348s 0.0075s
LuaJIT 0.196s 0.366s 0.0008s

PUC improves by the expected constant factor; LuaJIT gets ~1.9x slower.

Approach

This issue implements no optimisation of its own; it tracks a regression whose fix falls out of the rest of the series. The mechanism: the loop body still allocates closures on every iteration — intAdd(acc)(n) and eqIntImpl(n)(0) are curried foreign functions, so each iteration executes FNEW bytecodes. FNEW is NYI for the trace recorder, so every attempt to record the hot loop aborts, and the retry penalty ends in a blacklist. The accepted trace report (bench/goldens/trace_curried_step.txt) shows exactly this: the worker's blacklisted function entry (IFUNCF) is replaced by a blacklisted loop (ILOOP), while the small foreign closures keep their compiled JFUNCF traces.

Why the loop form is slower than the recursive form under the same FNEW aborts is not fully pinned down. The plausible reading: trace attempts and their abort/retry penalties are anchored differently (LOOP vs FUNCF sites), and the recursive shape happened to leave more of the per-iteration work inside compiled JFUNCF traces. The report's abort site set is unchanged, so the difference is in where the interpreter/compiled boundary falls, not in new abort causes.

Prerequisites / Relations

The FNEW sources inside loop bodies are exactly what the rest of the series removes: #178 (IR primops for the pure foreign subset), #180 (budgeted call-site inlining of dict methods), #186 (fork FFI audit for uncurried shapes). Blocked by those three. The loopification being measured landed in #181 (closed). Once a loopified body stops allocating per iteration, the loop is the shape the trace compiler wants, which is the point of #181.

Verification / Measurement

Re-measure curried_step after each of #178 / #180 / #186 lands. Closes when the loop trace-compiles (JLOOP in the end state) and the LuaJIT number beats the pre-#181 baseline of 0.196s. If the tail of the series lands and the loop still blacklists, loopification needs a second look (for example a LuaJIT-aware veto while the body allocates).

Metadata

Metadata

Assignees

No one assigned

    Labels

    OptimisationA Compiler Optimisationarea: codegenLua code generation / printing

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions