Problem
The length exports across the forks are one-line wrappers around the unary # operator: Data.Array.length = (function(xs) return #xs end), with the same shape behind Data.Array.ST.lengthImpl and Data.String.CodeUnits.length. To the IR these are opaque foreigns, so a saturated length xs call site pays a field read off the foreign table plus a call frame for what the VM executes as a single length opcode. Unlike the show-formatting shims, length sits on hot paths: array traversals and bounds logic call it per iteration, and in a LuaJIT trace the wrapper is a call where #xs would be a TLEN.
ForeignLift (#178) cannot lift these bodies today because the translatable subset has no representation of #: the sole unary primop is PrimNot, and the lifter's unary case handles LogicalNot only.
Approach
Add a unary PrimLen node mirroring PrimNot: the node is the Lua operator, so lowering maps it onto the existing HashOp and lifting maps HashOp back onto the node, both directions correct by construction (Note [IR primops]). Extend the lifter's unary case and allowlist Data.Array.length, Data.Array.ST.lengthImpl, and Data.String.CodeUnits.length. Nothing else is needed: # is an operator rather than a name, so the global-registry and shadowing questions that make lifting globals like tostring expensive do not arise. No constant folding over PrimLen ships with this; carrying Lua's length semantics (string bytes, table borders) in the folder buys nothing the lift needs.
While touching the module header, repoint its allowlist follow-up cross-reference at this issue; it currently points at #187, which is the closing audit of the optimisation series, and has since 9da225a.
Verification / Measurement
Focused lifter tests mirroring the runFn ones: the three length exports lift, and a # body with extra statements declines. Golden churn where lengths are called: saturated sites collapse to #xs after inlining and beta reduction, and the lifted rows drop out of the emitted FFI tables like the uncurried wrappers did. Eval goldens unchanged. The per-iteration call-frame effect becomes measurable through the #172 harness once it exists.
Prerequisites / Relations
Independent. Extends the #178 lifter the same way #198 and #227 did for the uncurried wrappers. Complements the fork-side audit #186.
Problem
The length exports across the forks are one-line wrappers around the unary
#operator:Data.Array.length = (function(xs) return #xs end), with the same shape behindData.Array.ST.lengthImplandData.String.CodeUnits.length. To the IR these are opaque foreigns, so a saturatedlength xscall site pays a field read off the foreign table plus a call frame for what the VM executes as a single length opcode. Unlike the show-formatting shims, length sits on hot paths: array traversals and bounds logic call it per iteration, and in a LuaJIT trace the wrapper is a call where#xswould be a TLEN.ForeignLift (#178) cannot lift these bodies today because the translatable subset has no representation of
#: the sole unary primop isPrimNot, and the lifter's unary case handlesLogicalNotonly.Approach
Add a unary
PrimLennode mirroringPrimNot: the node is the Lua operator, so lowering maps it onto the existingHashOpand lifting mapsHashOpback onto the node, both directions correct by construction (Note [IR primops]). Extend the lifter's unary case and allowlistData.Array.length,Data.Array.ST.lengthImpl, andData.String.CodeUnits.length. Nothing else is needed:#is an operator rather than a name, so the global-registry and shadowing questions that make lifting globals liketostringexpensive do not arise. No constant folding overPrimLenships with this; carrying Lua's length semantics (string bytes, table borders) in the folder buys nothing the lift needs.While touching the module header, repoint its allowlist follow-up cross-reference at this issue; it currently points at #187, which is the closing audit of the optimisation series, and has since 9da225a.
Verification / Measurement
Focused lifter tests mirroring the
runFnones: the three length exports lift, and a#body with extra statements declines. Golden churn where lengths are called: saturated sites collapse to#xsafter inlining and beta reduction, and the lifted rows drop out of the emitted FFI tables like the uncurried wrappers did. Eval goldens unchanged. The per-iteration call-frame effect becomes measurable through the #172 harness once it exists.Prerequisites / Relations
Independent. Extends the #178 lifter the same way #198 and #227 did for the uncurried wrappers. Complements the fork-side audit #186.