You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Polymorphic hot code bottoms out in opaque curried foreigns, and the optimizer cannot finish the specialization it starts: greaterThanOrEq(ordInt)(a)(b) stays a dictionary chain through compare and Ordering tables instead of a >= b — measured 42x slower (PUC 5.1, 5e6 comparisons); arithmetic through inlined foreign lambdas is 8.3x on the Fibonacci golden. Existing rules (beta, inlining, #177) stop where foreign bodies begin, because to the IR those bodies are text.
The original plan was a hand-written registry mapping qualified names to IR semantics (the purs-backend-es foreignSemantics approach for JS). The own-parser decision (#173) enables something stronger for Lua: derive the semantics from the actual fork source, so registry drift against the package set is impossible by construction. Lua's tiny grammar is what makes lifting realistic where it is not for JS.
Approach
Primop nodes in the IR, defined as the corresponding Lua operators so lowering is the identity and lifting is semantics-preserving by construction: binary PrimAdd/Sub/Mul/Div/Mod/Concat, comparisons PrimLt/Le/Gt/Ge, logic PrimAnd/Or/Not. Equality already exists as the Eq node; the lifter maps == onto it. The correctness burden falls only on rules over primops: constant folding of literals must follow Lua 5.1 semantics (double arithmetic), consistent with the existing constantFolding.
A lifter at mergeForeigns time: for each foreign export on the allowlist, take its parsed Lua AST (Replace the lexical foreign splitter with a real Lua 5.1 parser producing the Lua AST #173) and translate the pure return-tree subset — nested function literals to Abs, if/return trees to IfThenElse, operators to primops, literals to literals, calls only to a whitelist of pure stdlib functions (math.floor, ...). Loops, mutation, varargs do not lift; the export stays an opaque ForeignImport (correct for e.g. foldlArray).
The allowlist is a hard contract: an allowlisted export that fails to lift (a fork update changed the implementation shape) is a compile error, not a silent performance cliff.
Scope of this issue: primop nodes, the lifter, and the initial allowlist — the arithmetic/comparison/boolean/concat core of the prelude (intAdd/intMul/intSub, ordIntImpl/ordCharImpl, boolConj/boolDisj/boolNot, the refEq family, concatString) — exactly the measured 42x case. The *.Uncurried modules (which need an n-ary call node, #179) and the broader allowlist are follow-up work.
Prerequisites / Relations
Depends on #173 (the foreign parser supplies the Lua AST the lifter translates) and on #177 (case-of-known-constructor completes the cascade so lifted primops reduce all the way). Landing it in turn unblocks the dictionary-method inlining of #180. The n-ary call node the *.Uncurried follow-up needs lands in #179.
Verification / Measurement
Measured by #172: the dictionary-comparison microbenchmark (greaterThanOrEq(ordInt)(a)(b)) drops from 42x slower than native toward near 1x, and the Fibonacci arithmetic golden from 8.3x.
Problem
Polymorphic hot code bottoms out in opaque curried foreigns, and the optimizer cannot finish the specialization it starts:
greaterThanOrEq(ordInt)(a)(b)stays a dictionary chain throughcompareandOrderingtables instead ofa >= b— measured 42x slower (PUC 5.1, 5e6 comparisons); arithmetic through inlined foreign lambdas is 8.3x on the Fibonacci golden. Existing rules (beta, inlining, #177) stop where foreign bodies begin, because to the IR those bodies are text.The original plan was a hand-written registry mapping qualified names to IR semantics (the purs-backend-es
foreignSemanticsapproach for JS). The own-parser decision (#173) enables something stronger for Lua: derive the semantics from the actual fork source, so registry drift against the package set is impossible by construction. Lua's tiny grammar is what makes lifting realistic where it is not for JS.Approach
PrimAdd/Sub/Mul/Div/Mod/Concat, comparisonsPrimLt/Le/Gt/Ge, logicPrimAnd/Or/Not. Equality already exists as theEqnode; the lifter maps==onto it. The correctness burden falls only on rules over primops: constant folding of literals must follow Lua 5.1 semantics (double arithmetic), consistent with the existingconstantFolding.Abs, if/return trees toIfThenElse, operators to primops, literals to literals, calls only to a whitelist of pure stdlib functions (math.floor, ...). Loops, mutation, varargs do not lift; the export stays an opaqueForeignImport(correct for e.g.foldlArray).greaterThanOrEq ordInt a bdown tonot (a < b).Scope of this issue: primop nodes, the lifter, and the initial allowlist — the arithmetic/comparison/boolean/concat core of the prelude (
intAdd/intMul/intSub,ordIntImpl/ordCharImpl,boolConj/boolDisj/boolNot, therefEqfamily,concatString) — exactly the measured 42x case. The*.Uncurriedmodules (which need an n-ary call node, #179) and the broader allowlist are follow-up work.Prerequisites / Relations
Depends on #173 (the foreign parser supplies the Lua AST the lifter translates) and on #177 (case-of-known-constructor completes the cascade so lifted primops reduce all the way). Landing it in turn unblocks the dictionary-method inlining of #180. The n-ary call node the
*.Uncurriedfollow-up needs lands in #179.Verification / Measurement
Measured by #172: the dictionary-comparison microbenchmark (
greaterThanOrEq(ordInt)(a)(b)) drops from 42x slower than native toward near 1x, and the Fibonacci arithmetic golden from 8.3x.