Skip to content

Tail-recursion optimization and @tailrec annotation fails in important cases #435

Description

@LPTK

The hkmc2/shared/src/test/mlscript-compile/apps/parsing/Lexer.mls file currently has this defintion:

  fun scan(idx: Int, acc: Stack[TokenType]): Stack[TokenType] =
    fun go(idx: Int, tok: TokenType) = if
      options.noWhitespace and tok is
        Token.Comment then scan(idx, acc)
        Token.Space then scan(idx, acc)
      else scan(idx, tok :: acc)
    
    if char(idx) is
      None then reverse of acc
      Some(ch) and ch is...
      Char.Whitespace and whitespace(idx) is idx' then
        go(idx', Token.space(idx, idx'))
      "\"" then go(...string(idx + 1))
      Bracket as b then go(idx + 1, Token.symbol(b, idx))
      "/" then go(...comment(idx + 1))
      Operator as ch then go(...operator(idx + 1, ch))
      Char.Digit as ch then go(...number(idx + 1, ch))
      IdentifierStart as ch then go(...identifier(idx + 1, ch))
      IdentifierQuote as quote
        and char(idx + 1) is Some(IdentifierStart as ch)
        and identifier(idx + 2, quote + ch) is [idx', token] and
          token is Token.Identifier(name, _) then
            go(idx', Token.identifier(name, idx))
          else go(idx + 1, Token.error(idx, idx + 1))
      else
        print("Unrecognized character: '" ~ ch ~ "'")
        go(idx + 1, Token.error(idx, idx + 1))
  
  scan(0, Nil)

This should clearly be rewritten as a loop by tail-call optimization.

Currently, there are a couple of things preventing that:

  • The functions are not lifted by default, and tail-call opt only deals with lifted functions.
    Either we should generalize the tail-call optimizer, or we should enable lifting by default, or we should always selectively lift functions that need tail-call optimization.
  • The UCS tries to share common branches by introducing labelled block which move some calls to non-tail positions, a known issue that really needs fixing.
    It should be possible to share common branches while keeping tail-calls in tail position.

Compounded with that is the strange issue that annotating the scan and go functions above as @tailred does not report any error (defeating the point of the annotation). How come?

The result is that big-enough inputs break it, as can be seen by using slightly larger inputs in hkmc2/shared/src/test/mlscript/apps/parsing/CamlLightTest.mls.


From our discussion in the meeting, we want the tail-rec optimizer to:

  • Handle spreads
  • Handle not just purely top-level fucntions, but also static module-paths to function

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions