-
Notifications
You must be signed in to change notification settings - Fork 243
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
inline algebra typeclass methods #1310
base: main
Are you sure you want to change the base?
inline algebra typeclass methods #1310
Conversation
Note, I cannot inline Just leaving |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! In the PR description/comments would you mind sharing a small program whose bytecode would be optimized because of these changes?
trait DoubleIsField extends Field[Double] { | ||
override inline def minus(a: Double, b: Double): Double = a - b | ||
inline def negate(a: Double): Double = -a | ||
inline def one: Double = 1.0 | ||
inline def plus(a: Double, b: Double): Double = a + b | ||
override inline def pow(a: Double, b: Int): Double = Math.pow(a, b) | ||
override inline def times(a: Double, b: Double): Double = a * b | ||
inline def zero: Double = 0.0 | ||
|
||
override def fromInt(n: Int): Double = n | ||
|
||
override def fromDouble(n: Double): Double = n | ||
inline def div(a: Double, b: Double): Double = a / b | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the only code that changed? If so, can we avoid duplicating all the code below by leaving it under scala
? We can have a doubleVersionSpecific.scala
file in scala-{2,3}
directories.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also am planning on doing the rest of the methods (except conversions)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also at least for Int, Long and Float
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I need to duplicate DoubleAlgebra
and DoubleInstances
however, I could move those back under scala
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am being kind of iterative and experimental on this first one, since the full CI sometimes catches things I miss locally. The others should be faster.
Yes, I can provide an example that invokes at least one method from each instance (Field, NRoot, Trig, etc). I can exercise all the inlined methods if you want. |
Thanks, just one is sufficient! Essentially a manual test just to demonstrate that this PR actually works, and to demonstrate how to take advantage of it. Since typeclasses are often used in dynamic dispatch situations, I don't think the inlining can work in those cases. Probably it can only work when the typeclass instance itself is inlined. |
That is what my experiments show - if the calling code itself is not Here is a code example that demonstrates in-lining of methods from "all" the Double typeclasses. If you compile this in scala-3, you should see that any references to actual typeclass methods have been elided by inlining: object inlineTest {
import spire.implicits.*
type DoubleTypeClasses = Field[Double]
& NRoot[Double]
& Trig[Double]
& Order[Double]
& Signed[Double]
& IsRational[Double]
& TruncatedDivisionCRing[Double]
inline def inlinedCalls(x: Double, y: Double)(using alg: DoubleTypeClasses): Double =
val t1 = alg.plus(x, y)
val t2 = alg.sqrt(x)
val t3 = alg.exp(y)
val t4 = alg.compare(x, y)
val t5 = alg.abs(x)
val t6 = alg.tmod(x, y)
val t7 = alg.floor(y)
t1 + t2 + t3 + t4 + t5 + t6 + t7
val x = 1.0
val y = 2.0
// with scala-3 inlined typeclasses, the resulting bytecode should
// show no reference to actual typeclass methods, only raw double
// operations or calls to 'Math.xxx' methods or other java methods.
val z = inlinedCalls(x, y)
} |
@armanbilge as you can see, this change breaks MIMA. Before we proceed, is this going to be a problem? I can limit the ABI damage to scala-3 only, since this doesn't really apply to scala-2 anyway |
@armanbilge that seems to work 🎉 The methods are all inlined. It seems to be inserting a call to implicit function, although its result is unused in the subsequent bytecode:
Maybe it gets removed in linking? I'm a bit puzzled by it, because in my experiments described on #1309, the implicit object never appeared in the byte code at all, but that was an actual object, not an implicit function. |
If by "linking" you mean the JVM optimizes it away at runtime, then maybe? Scala.js and Scala Native have an explicit linking step, but Scala JVM does not. The reason that static call may be there is to force the companion object to initialize. Not sure. |
@armanbilge The four native types are complete. I think that is where I wanted to get for this. One minor dangling mystery is that mima check failed for |
It should be okay to leave The reason it failed for |
@armanbilge the only question I have left is whether there is a way to get rid of the call to implicit function in the byte-code. I doubt I will have a second chance to mess with the signatures without failing MIMA. |
I suspect that might be required for semantics, but I'm not sure. Might be a question for the compiler team (I wouldn't be surprised if there was an existing issue on this topic). |
@armanbilge I think I have worked out what is going on. Basically, scala compiles I don't think it's currently feasible to use I could try filing a compiler issue about this, but even if it is feasible to get the compiler to understand when it can elide this call, I doubt it would be a high priority for the compiler devs. |
@armanbilge given the above, I think this is the best possible solution for spire, so I'm OK to merge if you are. I suppose it might be feasible to add some new import that uses |
It looks like a bug to me, and specifically a bug with |
@armanbilge the behavior in your reproducer definitely seems like a bug. |
@armanbilge so we should consider |
I experimented with
|
I don't think any |
If we can get |
fix #1309