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

Full tailcall support on Unix #2556

Open
jkotas opened this issue Jan 7, 2016 · 10 comments · May be fixed by #26418

Comments

@jkotas
Copy link
Member

commented Jan 7, 2016

It is important to have uniform managed tailcall support accross platforms for F#.

theme:tail-call

@jkotas

This comment has been minimized.

Copy link
Member Author

commented Jan 7, 2016

The "slow" helper based tailcalls are missing - see #703.

The existing design the "slow" helper based tailcalls has a strong dependency on how varargs work on Windows (it assumes that va_list is castable to void* that does not hold on Unix), and it is super complex (requires ~500 line handwritten assembly thunk emitter). I believe the contract between JIT and VM for slow tailcalls needs to be redesigned to make the Unix implementation feasible.

@dsyme

This comment has been minimized.

Copy link

commented Jan 7, 2016

Super, thanks Jan.

If possible, could you add some notes about when the slow tailcall helper is used (which AFAIU should correspond to when tailcalls will currently not be taken for CoreCLR on Unix?). It would be useful to have docs on when the slow helper is used on Windows in any case.

@jkotas

This comment has been minimized.

Copy link
Member Author

commented Jan 7, 2016

when the slow tailcall helper is used

This is implemented in fgCanFastTailCall method https://github.com/dotnet/coreclr/blob/master/src/jit/morph.cpp#L5715.

The simple rule that should hold across platforms over time is that the fast tailcall will be used if both of the following are true:

  • Return value and call target arguments are all either primitive types, reference types, or valuetypes with a single primitive type or reference type fields
  • The aligned size of call target arguments is less or equal to aligned size of caller arguments

There are additional platform-specific and implementation-specific situations where the fast tailcall will be used that can be found by consulting fgCanFastTailCall sources.

@DemiMarie

This comment has been minimized.

Copy link

commented Jul 20, 2016

I had a rather wild thought: what about using a different calling convention (callee-pops) for tail calls?

This would break lots of stuff on Windows, but as I understand it DWARF unwinding is more flexible.

@janvorli

This comment has been minimized.

Copy link
Member

commented Jul 20, 2016

Managed code unwinding is not using DWARF on Unix. We use the same unwind info format as on Windows. Also, having a completely different calling convention for selected functions would complicate a lot of stuff for runtime (e.g. for reflection) and JIT.

@DemiMarie

This comment has been minimized.

Copy link

commented Aug 10, 2016

@janvorli What about using explicit frame pointers? I think that LLVM can do this too, even on Windows (source: LLVM supports tail calls on Windows, and I can't see any other way that this could be implemented).

Why does the calling convention impact reflection?

@janvorli

This comment has been minimized.

Copy link
Member

commented Aug 10, 2016

@DemiMarie the reflection prepares arguments for a function call based on the calling convention - putting register arguments into appropriate slots in a transition block structure and the others on the stack. It then transitively calls assembler helper CallDescrWorkerInternal that loads registers from the transition block, prepares stack, calls the actual method invoked via reflection and after it returns, stores its result into the CallDescrData. See RuntimeMethodHandle::InvokeMethod here:
https://github.com/dotnet/coreclr/blob/master/src/vm/reflectioninvocation.cpp#L1322

Regarding a different calling convention, there is another problem. Any method can be called via tail call in general and the same method can be called from another place using regular call. So having a different calling convention for tail calls would mean you'd have to have two jitted versions of the same function in case a function was called both ways.

As for the explicit frame pointers - can you please be more specific on your idea on taking advantage of these?

@DemiMarie

This comment has been minimized.

Copy link

commented Aug 11, 2016

@janvorli yes, I know that some methods would be JITed twice. My hypothesis is that this would be an acceptable overhead for the use cases (as an alternative to slow tail call).

Regarding explicit frame pointers, my basic idea is to figure out exactly what LLVM does, and do that. I don't know the details, but according to this source a frame pointer can be used to allow the stack pointer to be manipulated. I know (from the LLVM documentation) that LLVM can support tail calls on Windows/x64, and I strongly suspect that it supports unwinding, as otherwise debugging would be impossible.

Regarding reflection: under my proposal, this would be done using the version of the method with the standard calling convention.

@cartermp

This comment has been minimized.

Copy link

commented May 23, 2019

We're now hitting this more prominently with F# on Unix being far more widespread. We're also going to have a .NET Core-based editing experience in VSCode and eventually VS for Mac.

@BruceForstall

This comment has been minimized.

Copy link
Member

commented Aug 23, 2019

@jakobbotsch jakobbotsch referenced a pull request that will close this issue Sep 11, 2019
45 of 57 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants
You can’t perform that action at this time.