Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

StackOverflow with Fable but not with fsc in release #2392

Open
TysonMN opened this issue Feb 20, 2021 · 4 comments
Open

StackOverflow with Fable but not with fsc in release #2392

TysonMN opened this issue Feb 20, 2021 · 4 comments

Comments

@TysonMN
Copy link

TysonMN commented Feb 20, 2021

Description

Consider PR hedgehogqa/fsharp-hedgehog#314. Our tests pass when using the standard F# complier (fsc) when the configuration is Release, which means the compiler will perform tail call elimination/optimization. The same test fails when compiled with Fable and executed via npm.

Repro code

Here is a link to one of the specific tests that fails for Fable+npm but passes with fsc.

Expected result

All tests, including that one, pass. For example, see these logs from our GitHub Action and executed those tests compiled with the fsc.

Actual results

Some tests, including that one, fail due to a StackOverflow. See these logs from our GitHub Action that executed that test compiled with Fable using npm.

Related information

  • Fable version
    • 3.1.1
  • Operating system
    • Microsoft Windows Server 2019
    • 10.0.17763
    • Datacenter
  • Node
    • 14.15.4
    • x64
@alfonsogarciacaro
Copy link
Member

Hi @TysonMN, thanks for the report! It's great you're working to support Fable with Hedgehog, I'd love to see this library in Fable projects. It'd be great if you could isolate a case that fails only with Fable, but please note that unfortunately tail-call optimization is not as advanced as in .NET so it may be not possible to fix those cases. First, as far as I know, JS engines haven't implemented tail-call optimization natively yet, so we're restricted to the cases that can be converted to a while loop at compile time.

Also, we don't have support for tail-call optimization with mutually recursive functions atm, sometimes you can resolve this by inlining. At some point, Tomas Petricek had a draft to support this so it may be possible, but I'm not sure how much work would entail.

@ncave
Copy link
Collaborator

ncave commented Feb 22, 2021

As @alfonsogarciacaro said, most JS engines unfortunately have not implemented TCO yet.
There are some JS workarounds that vary in performance, but Fable is not using any of them:

  • Accumulate function parameters in a wrapper.
  • Explicit tail calls: fext.js (can also handle mutual recursion).

@TysonMN
Copy link
Author

TysonMN commented Feb 22, 2021

Thanks for the information and links.

Yes, my understanding was that one should not typically expect TCO from Fable or the downstream JS compilers.

What about Fable's trampoline? Would you recommend that we use a similar trampoline in our code to avoid overflowing the stack?

That seems like a great idea and abstraction. We might be able to make use of it in F#-Hedgehog even for some cases that are overflowing when using the fsc.

[...] we're restricted to the cases that can be converted to a while loop at compile time.

Are you saying that Fable is typically able to compile a recursive function into a loop (so support by downstream JS compilers is not needed)?

@ncave
Copy link
Collaborator

ncave commented Feb 22, 2021

@TysonMN Trampolines may work but have some performance cost, which may or may not be acceptable to you.

Yes, Fable is able to optimize (rewrite into a loop) some self-referencing tail-recursive functions.
But not in the more general case of mutually recursive functions, or continuation passing style.

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

No branches or pull requests

3 participants