Skip to content

ensure fastForInline does not make closures - #1111

Merged
armanbilge merged 5 commits into
typelevel:mainfrom
bishabosha:patch-1
Jan 7, 2022
Merged

ensure fastForInline does not make closures#1111
armanbilge merged 5 commits into
typelevel:mainfrom
bishabosha:patch-1

Conversation

@bishabosha

Copy link
Copy Markdown
Contributor

make the parameters of fastForInline inline, which will beta reduce them when applied.
Currently examining produced bytecode, closures are created for test, next and body

@armanbilge

Copy link
Copy Markdown
Member

Brilliant, thank you! I remember asking about this in #1067 (comment) but seems I forgot to create an issue to track it 😅

We're not fussed about bincompat at the moment, but for my own education: what are the bincompat implications of a change like this?

If you feel so motivated, feel free to run the FastForBenchmark :)

@armanbilge

armanbilge commented Nov 17, 2021

Copy link
Copy Markdown
Member

Hmm, this seems to have broken the semantics somehow 🤔

I only looked quickly, but it's also possible you managed to restore the cfor semantics for this test.

// This test distinguishes fastFor from cfor
test("doesn't capture value in closure") {
val b1 = collection.mutable.ArrayBuffer.empty[() => Int]
fastFor(0)(_ < 3, _ + 1) { x =>
b1 += (() => x)
}
val b2 = collection.mutable.ArrayBuffer[() => Int]()
(0 until 3).foreach { x =>
b2 += (() => x)
}
assertEquals(b1.map(_.apply()).toList, b2.map(_.apply()).toList)
}

If that's the case, then maybe we can implement cfor on Scala 3 with identical semantics to Scala 2 cfor, which would be excellent :)

@cquiroz

cquiroz commented Nov 17, 2021

Copy link
Copy Markdown
Collaborator

This seems great, I wonder why the tests broke

@armanbilge armanbilge added this to the v0.18.0 milestone Nov 18, 2021
make the parameters of fastForInline `inline`, which will beta reduce them when applied

update FastForSuite
Comment thread tests/shared/src/test/scala/spire/syntax/FastForSuite.scala Outdated
@bishabosha

Copy link
Copy Markdown
Contributor Author

I have updated the tests to reflect the fact that any of the arguments to fastFor / fastForRange can capture the loop variable.

This does mean that the semantics of the scala 2 versions does not match, so the test will probably fail again.

@armanbilge
armanbilge requested a review from cquiroz December 29, 2021 15:01

@armanbilge armanbilge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 hooray, all tests passing! Thank you so much 😁

I have a question below.

Comment thread core/src/main/scala-3/spire/syntax/macros/cforMacros.scala Outdated
@bishabosha
bishabosha force-pushed the patch-1 branch 2 times, most recently from 0584c18 to 1dd5586 Compare January 4, 2022 17:28
@bishabosha

bishabosha commented Jan 4, 2022

Copy link
Copy Markdown
Contributor Author

@armanbilge I have optimised the implementation for both fastFor and fastForRange so that side effects will not be inlined in either. I am still unsure about the status of having fastForRange for Long as well, as that is not implemented in Scala 2. Please could I also have approval for the CI?

@bishabosha
bishabosha requested a review from armanbilge January 4, 2022 17:32
@armanbilge

Copy link
Copy Markdown
Member

Wow! I see you were able to fix the semantics so that they match cfor again. That's fantastic, thank you!

I think this means we can rename fastFor back to cfor, share the tests across 2/3, and remove any reference to fastFor (we only introduced it due to the semantic changes). Doesn't have to be in this PR (I can do this refactoring in a follow-up). Then maybe we can release 0.18.0 final 😁

Right now the Scala 2 fastFor reference implementation is failing the CI. To land your PR, maybe let's make the fastFor test suite Scala 3-only.

I'll take a closer look later today. Thanks again for your efforts here :)

@bishabosha

Copy link
Copy Markdown
Contributor Author

@armanbilge I have made the FastForSuite scala 3 only :)

@armanbilge armanbilge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM and not too hard to wrap my head around, although letFunc went a bit over my head 😅 Regarding the Long implementation, I think it we should definitely include it 👍

I hope @cquiroz can take a look as well :)

${ Expr.betaReduce(body) }(index)
index += $stride
}
private def letFunc[A, B, C](using Quotes)(name: String, rhs: Expr[A => B])(in: Expr[A => B] => Expr[C]): Expr[C] =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind writing a comment(s) to explain a little of the macro magic here? For curious users and future contributors/maintainers alike :)

@bishabosha bishabosha Jan 6, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added some doc comments, and also found a pattern extractor I can use to simplify function literal detection

@cquiroz cquiroz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks a lot for this fix


import quoted._
import collection.immutable.NumericRange
import scala.quoted.*

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why the scala part wasn't needed earlier

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still not needed, I thought it just made it more clear where quoted comes from

ValDef.let(Symbol.spliceOwner, name, f.asTerm)(ref => in(ref.unsafeAsExpr[A]).asTerm).unsafeAsExpr[B]

end fastForRangeMacro
if isFunctionLiteral(rhs) then in(Expr.betaReduce(rhs))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, in the old scala2 code there was a differentiation for literals as well that I didn't manage to port to scala 3

@bishabosha

Copy link
Copy Markdown
Contributor Author

@armanbilge @cquiroz I have pushed another commit to simplify a bit and document

@armanbilge armanbilge left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks great! I hope that as you find time, you may continue to contribute—I think spire has an interesting journey ahead as we figure out how to work in a language without specialization :)

@armanbilge
armanbilge merged commit f717e9d into typelevel:main Jan 7, 2022
@bishabosha
bishabosha deleted the patch-1 branch January 7, 2022 07:56
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.

3 participants