Skip to content

Fix Fannkuch benchmark: thunk lazy let-bindings - #64

Merged
omelkonian merged 2 commits into
masterfrom
orestis/fix-fannkuch
Sep 26, 2026
Merged

omelkonian merged 2 commits into
masterfrom
orestis/fix-fannkuch

Conversation

@omelkonian

@omelkonian omelkonian commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

The treeless pipeline happens to introduce some that are only sound under lazy semantics, e.g. ToTreeless.updateCatchall hoists the body of a catch-all/overlapping clause above the pattern match.

That's the reason Fannkuch benchmark failed on the Agda frontend: the recursive call was placed at the start before the pattern match and under strict evaluation loops forever.

A more proper solution would be to emit actual tLazy/tForce λ-box terms, but this seems to break things downstream in Peregrine and its backends, so leave a proper lazy-evaluation solution for the future, and now settle for this naive call-by-name fix.

    | tLazy t => EAst.tLambda nAnon (lift 1 0 (implement_lazy_force t))
    | tForce t => EAst.tApp (implement_lazy_force t) tBox.
      -- `let x = t in e` is compiled to `(x => e[x()/x])(() => t)` so that `t`
      -- is only evaluated inside the body
      T.TLet t e -> do
        t' <- Lambda 0 <$> go t
        e' <- substShift 1 1 [Apply (Local (LocalId 0)) []] <$> go e
        return $ Apply (Lambda 1 e') [t']
  • @mattam82 informed me that Peregrine's OCaml/Malfunction backend properly does it justice, and actually gives a memoised implementation of tLazy/tForce.

@omelkonian
omelkonian merged commit 67df572 into master Sep 26, 2026
1 check passed
@omelkonian
omelkonian deleted the orestis/fix-fannkuch branch September 26, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant