Syncleus / aparapi

The New Official Aparapi: a framework for executing native Java and Scala code on the GPU.
http://aparapi.com
Apache License 2.0
465 stars 59 forks source link

Codegen wrongfully fails on Scala-written kernels #160

Open yuvaldeg opened 4 years ago

yuvaldeg commented 4 years ago

Consider this simple test case that has no Java objects in the Kernel:

  @Test
  def failingCodeGen1(): Unit = {
    val size = 10
    var result: Int = 1
    val kernel = new Kernel() {
      override def run(): Unit = {
        val test: Int = 1
        if (test == 1) {
          result = 0
        }
      }
    }
    kernel.execute(size)
  }

Code generation still fails, saying: "Using java objects inside kernels is not supported", with this stacktrace:

com.aparapi.internal.exception.ClassParseException: Using java objects inside kernels is not supported
    at com.aparapi.internal.model.Entrypoint.getFieldFromClassHierarchy(Entrypoint.java:187)
    at com.aparapi.internal.model.Entrypoint.<init>(Entrypoint.java:706)
    at com.aparapi.internal.model.ClassModel.computeBasicEntrypoint(ClassModel.java:3059)
    at com.aparapi.internal.model.ClassModel$6.compute(ClassModel.java:3040)
    at com.aparapi.internal.model.ClassModel$6.compute(ClassModel.java:3038)
    at com.aparapi.internal.model.ValueCache.computeIfAbsent(ValueCache.java:50)
    at com.aparapi.internal.model.ClassModel.getEntrypoint(ClassModel.java:3048)
    at com.aparapi.internal.model.ClassModel.getEntrypoint(ClassModel.java:3067)
    at com.aparapi.internal.kernel.KernelRunner.executeInternalInner(KernelRunner.java:1475)
    at com.aparapi.internal.kernel.KernelRunner.fallBackToNextDevice(KernelRunner.java:1361)
    at com.aparapi.internal.kernel.KernelRunner.fallBackToNextDevice(KernelRunner.java:1323)
    at com.aparapi.internal.kernel.KernelRunner.executeInternalInner(KernelRunner.java:1479)
    at com.aparapi.internal.kernel.KernelRunner.executeInternalOuter(KernelRunner.java:1383)
    at com.aparapi.internal.kernel.KernelRunner.execute(KernelRunner.java:1374)
    at com.aparapi.Kernel.execute(Kernel.java:2897)
    at com.aparapi.Kernel.execute(Kernel.java:2854)
    at com.aparapi.Kernel.execute(Kernel.java:2829)
...
freemo commented 4 years ago

Scala is relatively new, will investigate

tarsa commented 3 years ago

In reality it has an object (i.e. the kernel accesses an object). I've created following simple test program:

package boxed

class Logic {
  def run(): Unit = {
    var result = 5
    val closure = () => {
      result += 1
    }
    closure()
    println(result)
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    new Logic().run()
  }
}

Here's the decompiled output:

$ javap -c target/scala-2.12/classes/boxed/Logic.class 
Compiled from "Main.scala"
public class boxed.Logic {
  public void run();
    Code:
       0: iconst_5
       1: invokestatic  #21                 // Method scala/runtime/IntRef.create:(I)Lscala/runtime/IntRef;
       4: astore_1
       5: aload_1
       6: invokedynamic #42,  0             // InvokeDynamic #0:apply$mcV$sp:(Lscala/runtime/IntRef;)Lscala/runtime/java8/JFunction0$mcV$sp;
      11: astore_2
      12: aload_2
      13: invokeinterface #46,  1           // InterfaceMethod scala/Function0.apply$mcV$sp:()V
      18: getstatic     #52                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
      21: aload_1
      22: getfield      #56                 // Field scala/runtime/IntRef.elem:I
      25: invokestatic  #62                 // Method scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      28: invokevirtual #66                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
      31: return

  public static final void $anonfun$run$1(scala.runtime.IntRef);
    Code:
       0: aload_0
       1: aload_0
       2: getfield      #56                 // Field scala/runtime/IntRef.elem:I
       5: iconst_1
       6: iadd
       7: putfield      #56                 // Field scala/runtime/IntRef.elem:I
      10: return

  public boxed.Logic();
    Code:
       0: aload_0
       1: invokespecial #76                 // Method java/lang/Object."<init>":()V
       4: return
}

As you see, it created an instance of IntRef and uses it as a container for var result: Int. The reason is that in Java platform you can't access different stack frame. In Java language you can do something that looks like accessing different stack frame, but you're restricted to effectively final variables. The way it works is that Java copies the values to new object, so then they can be accessed safely. In case of mutable variables you can't use the same trick, as modifying one copy would render the other copy stale and invalid. Therefore you need to box any stack allocated value and place it on managed heap. Then you can safely share an immutable copy of reference to that mutable object.

If you look here https://github.com/scala/scala/tree/2.13.x/src/library/scala/runtime then you'll see plenty of mutable or lazy boxed primitive types. Maybe you could detect that and provide more informative exception message? Like, detect that object type is from scala.runtime package and then show message like: "Detected object of class from scala.runtime package. Did you use boxed primitive from within kernel?"