miniboxing / miniboxing-plugin

Miniboxing is a program transformation that improves the performance of Scala generics when used with primitive types. It can speed up generic collections by factors between 1.5x and 22x, while maintaining bytecode duplication to a minimum. You can easily add miniboxing to your sbt project:
http://scala-miniboxing.org
Other
121 stars 17 forks source link

Miniboxing client-side warning: Should compile with the miniboxing-plugin! #185

Open julien-truffaut opened 9 years ago

julien-truffaut commented 9 years ago

I am not sure where is the miniboxing compiler plugin and runtime required, for example, if I annotate a class with miniboxed in module A, I need to add both compiler and runtime for this module. However, if module B has a dependency to module A, does he also need to add miniboxing dependencies? If yes, are the compiler and the runtime required or only one of those.

ps: sorry for spamming you with issues

VladUreche commented 9 years ago

Yes, both the miniboxing runtime and plugin are required to compile any source code that uses or interacts with miniboxed bytecode. This is quite restrictive, and although there are solutions to it (see #115 for example), they are not yet implemented. In principle, it would be best if one could mark a class as @api and the plugin would make sure no special features are added to that class, so its users can be compiled with the vanilla scala compiler.

But you raise a very good point. There should be a warning or an error message when using miniboxed code from generic environments. I do not know how to inject that in the compiler as long as I don't have the plugin in there, but maybe I can find something -- I just need to think hard enough about it.

VladUreche commented 9 years ago

Assigned to milestone 0.5 since it's quite a difficult problem.

ps: sorry for spamming you with issues

Thank you for doing so. How else would the miniboxing plugin become bug-free? :D

VladUreche commented 9 years ago

During a discussion today with @xeno-by I learned about the @copileTimeOnly annotation: https://issues.scala-lang.org/browse/SI-6539, which could be used to implement this feature.

VladUreche commented 9 years ago

Here's a test case:

$ cat gh-bug-185-1.scala 
package miniboxing.tests.compile.bug185

class C[@miniboxed T]

$ mb-scalac gh-bug-185-1.scala 

$ cat gh-bug-185-2.scala 
package miniboxing.tests.compile.bug185

object Test {
  def main(args: Array[String]): Unit = {
    val c = new C[Int] // unless the miniboxing plugin is attached, this is incorrect bytecode
  }
}

$ scalac gh-bug-185-2.scala 

$ scala miniboxing.tests.compile.bug185.Test
java.lang.InstantiationError: miniboxing.tests.compile.bug185.C
    at miniboxing.tests.compile.bug185.Test$.main(gh-bug-185-2.scala:5)
    at miniboxing.tests.compile.bug185.Test.main(gh-bug-185-2.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at scala.reflect.internal.util.ScalaClassLoader$$anonfun$run$1.apply(ScalaClassLoader.scala:70)
    at scala.reflect.internal.util.ScalaClassLoader$class.asContext(ScalaClassLoader.scala:31)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.asContext(ScalaClassLoader.scala:101)
    at scala.reflect.internal.util.ScalaClassLoader$class.run(ScalaClassLoader.scala:70)
    at scala.reflect.internal.util.ScalaClassLoader$URLClassLoader.run(ScalaClassLoader.scala:101)
    at scala.tools.nsc.CommonRunner$class.run(ObjectRunner.scala:22)
    at scala.tools.nsc.ObjectRunner$.run(ObjectRunner.scala:39)
    at scala.tools.nsc.CommonRunner$class.runAndCatch(ObjectRunner.scala:29)
    at scala.tools.nsc.ObjectRunner$.runAndCatch(ObjectRunner.scala:39)
    at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:65)
    at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:87)
    at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:98)
    at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:103)
    at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)
VladUreche commented 9 years ago

The idea is:

scala> import scala.reflect.internal.annotations.compileTimeOnly
import scala.reflect.internal.annotations.compileTimeOnly

scala> @compileTimeOnly("use the miniboxing plugin!") class C
defined class C

scala> new C
<console>:10: error: use the miniboxing plugin!
              new C
                  ^

So the solution is simple. Here are the phases in Scala:

$ mb-scalac -Xshow-phases
      phase name  id  description
      ----------  --  -----------
             ...  ..  ...
         pickler  11  ... <== this is where signatures are saved
       refchecks  12  ... <== this is where compileTimeOnly is checked
             ...  ..  ...

So the steps that need to be done in the miniboxing plugin are:

Then, we get this:

There are 2 ideas on further developing this: