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

Optimize tailcalls even when called with different type arguments. #6065

Merged
merged 1 commit into from Sep 28, 2017

Conversation

Projects
None yet
9 participants
@TomasMikula
Contributor

TomasMikula commented Sep 8, 2017

This PR turns off the check that the recursive tail-call is made with the same type arguments, if the tailcall optimization is requested explicitly via @tailrec.

This allows to tail-optimize, for example, operations on type-aligned sequences.

I believe the check was there for a reason. However, the tests pass and I can't think of a case where it would be unsafe. Please let me know if I'm wrong.

@scala-jenkins scala-jenkins added this to the 2.12.4 milestone Sep 8, 2017

@TomasMikula TomasMikula changed the title from Optimize tailcalls even called with different type arguments. to Optimize tailcalls even when called with different type arguments. Sep 8, 2017

@sjrd

This comment has been minimized.

Member

sjrd commented Sep 9, 2017

The check might have been there because it badly interacts with specialization.

@TomasMikula

This comment has been minimized.

Contributor

TomasMikula commented Sep 9, 2017

Indeed, thank you. I will add a check that differing type arguments must not be specialized.

@TomasMikula TomasMikula changed the title from Optimize tailcalls even when called with different type arguments. to [WIP] Optimize tailcalls even when called with different type arguments. Sep 9, 2017

@smarter

This comment has been minimized.

Contributor

smarter commented Sep 9, 2017

@DarkDimius : Do you know why this restriction exist? Dotty seems to have a similar one (test/files/pos/t9647.scala from this PR does not compile in Dotty because of https://github.com/lampepfl/dotty/blob/851c3c2212b7457dedc9ad1e59ff688b17458db5/compiler/src/dotty/tools/dotc/transform/TailRec.scala#L275)

@TomasMikula

This comment has been minimized.

Contributor

TomasMikula commented Sep 9, 2017

I added a check that @specialized type parameters are unchanged in the recursive call, and both negative and positive tests involving specialized type parameters.

@hrhino

This comment has been minimized.

Member

hrhino commented Sep 9, 2017

Why do we only do this in case the method is @tailrec-annotated? The documentation claims that it's

A method annotation which verifies that the method will be compiled with tail call optimization.

and that

If it is present, the compiler will issue an error if the method cannot be optimized into a loop.

Moreover, there are plenty of places on the Internet which claim that tail-call optimization is automatic.

I can't think of a reason why I'd want to write a tail-recursive method, and have it not be optimized because I forgot the @tailrec. For extra fun, when I go to put the annotation on to check, I'll get it optimized after all!

@TomasMikula

This comment has been minimized.

Contributor

TomasMikula commented Sep 9, 2017

@hrhino I guess I just wanted to keep the change conservative.

@TomasMikula

This comment has been minimized.

Contributor

TomasMikula commented Sep 9, 2017

When I enable it wholesale, this test fails. The method invocation it is looking for is optimized away. How should I update that test so that it still checks for scala/bug#8062?

@johnynek

This comment has been minimized.

Contributor

johnynek commented Sep 9, 2017

Thanks for doing this! I have wanted to take a stab at this. I hope we can make it enabled by default.

@TomasMikula

This comment has been minimized.

Contributor

TomasMikula commented Sep 10, 2017

I checked with Scala 2.10.3 that scala/bug#8062 is reproducible even with the recursive call in non-tail position. I updated the test accordingly so that it doesn't interact with this PR.

(Aside: where does scalac-hash come from?)

@retronym

This comment has been minimized.

Member

retronym commented Sep 11, 2017

The check might have been there because it badly interacts with specialization.

The restriction to only elim tail calls when type args are forwarded came in a4e5d4a, one day after the genesis of the tailcalls phase. This predated the specialization phase by four years.

@dragos do you recall if the restriction was due to astounding prescience about future interactions with specialization? Or maybe just caution?

I updated the test accordingly so that it doesn't interact with this PR.

Thanks for checking before modifying the test. Adding -g:notailcalls to the compiler options in that test case would also have worked.

Aside: where does scalac-hash come from?

https://github.com/retronym/libscala

The scripts are somewhat out of date because of changes to the format and repository of our per-commit builds. I've got new versions in https://gist.github.com/retronym/6b5cea4d94d51fbcf5d7a0587404492e

@TomasMikula TomasMikula changed the title from [WIP] Optimize tailcalls even when called with different type arguments. to Optimize tailcalls even when called with different type arguments. Sep 11, 2017

@TomasMikula

This comment has been minimized.

Contributor

TomasMikula commented Sep 12, 2017

Thanks, @retronym. I saw it used in issue descriptions and didn't know where it came from.

@adriaanm

This comment has been minimized.

Member

adriaanm commented Sep 18, 2017

@dragos do you recall if the restriction was due to astounding prescience about future interactions with specialization? Or maybe just caution?

Not him, but my guess would be that at some point we also compiled to a platform that didn't erase types :-) (MSIL)

@Atry

This comment has been minimized.

Contributor

Atry commented Sep 19, 2017

Looks like related to scala/bug#10493

@adriaanm adriaanm self-requested a review Sep 21, 2017

@adriaanm

This comment has been minimized.

Member

adriaanm commented Sep 25, 2017

Maybe we should push this phase back until after lambdalift? That would fix both bugs (different polymorphic instantiations of both the method and the receiver), since we're looking at erased (and specialized) types.

@adriaanm adriaanm added the prio:hi label Sep 26, 2017

@TomasMikula

This comment has been minimized.

Contributor

TomasMikula commented Sep 28, 2017

Just changing tailcalls to run right after lambdalift breaks:

  • 9 tests in pos,
  • 16 tests in neg,
  • 31 test in run,
  • 5 tests in jvm,
  • 15 tests in specialized.
@adriaanm

This comment has been minimized.

Member

adriaanm commented Sep 28, 2017

That's pretty good :-) Thanks for trying it out!

@adriaanm

This comment has been minimized.

Member

adriaanm commented Sep 28, 2017

We can start with this PR and take that investigation as a separate one for 2.13.0-M4

@adriaanm adriaanm merged commit 3de8033 into scala:2.12.x Sep 28, 2017

5 checks passed

cla @TomasMikula signed the Scala CLA. Thanks!
Details
integrate-ide [5769] SUCCESS. Took 17 s.
Details
validate-main [6503] SUCCESS. Took 86 min.
Details
validate-publish-core [6289] SUCCESS. Took 6 min.
Details
validate-test [5560] SUCCESS. Took 74 min.
Details
@adriaanm

This comment has been minimized.

Member

adriaanm commented Sep 28, 2017

Good stuff @TomasMikula!

@TomasMikula

This comment has been minimized.

Contributor

TomasMikula commented Sep 28, 2017

🎉

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