flix / flix

The Flix Programming Language
https://flix.dev/
Other
2.1k stars 150 forks source link

ClassCastException with select #5014

Closed paulbutcher closed 1 year ago

paulbutcher commented 1 year ago

The following:

def recvWithDefault(rx: Receiver[Int32, r]): Int32 \ { Read(r), Write(r) } =
    select {
        case x <- Channel.recv(rx) => x
        case _                     => 1
    }

def main(): Unit \ IO = region r {
  let (_tx1, rx1) = Channel.buffered(r, 1);
  recvWithDefault(rx1) |> println
}

Gives:

java.lang.ClassCastException: class List%Nil%Obj cannot be cast to class dev.flix.runtime.Unit (List%Nil%Obj and dev.flix.runtime.Unit are in unnamed module of loader ca.uwaterloo.flix.language.phase.jvm.FlixClassLoader @126675fd)
        at Def%main%.invoke(Unknown Source)
magnus-madsen commented 1 year ago

Is this "new" or old?

paulbutcher commented 1 year ago

Not sure yet.

paulbutcher commented 1 year ago

OK, this is very curious. It works just fine when run like this:

java -jar flix.jar run
1

But not when run from the REPL:

java -jar flix.jar
     __   _   _
    / _| | | (_)            Welcome to Flix v0.34.0
   | |_  | |  _  __  __
   |  _| | | | | \ \/ /     Enter an expression to have it evaluated.
   | |   | | | |  >  <      Type ':help' for more information.
   |_|   |_| |_| /_/\_\     Type ':quit' or press 'ctrl + d' to exit.

flix> :eval main()
java.lang.ClassCastException: class List%Nil%Obj cannot be cast to class dev.flix.runtime.Unit (List%Nil%Obj and dev.flix.runtime.Unit are in unnamed module of loader ca.uwaterloo.flix.language.phase.jvm.FlixClassLoader @701a32)
    at Def%main%.invoke(Unknown Source)
    at Cont%Obj.unwind(Cont%Obj)
    at Ns.m_main%(Unknown Source)
    at Main.main(Main)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.mainFunction$1(Bootstrap.scala:79)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.$anonfun$bootstrap$9(Bootstrap.scala:88)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.$anonfun$bootstrap$9$adapted(Bootstrap.scala:88)
    at ca.uwaterloo.flix.runtime.shell.Shell.run(Shell.scala:384)
    at ca.uwaterloo.flix.runtime.shell.Shell.execEval(Shell.scala:311)
    at ca.uwaterloo.flix.runtime.shell.Shell.execReloadAndEval(Shell.scala:326)
    at ca.uwaterloo.flix.runtime.shell.Shell.execute(Shell.scala:173)
    at ca.uwaterloo.flix.runtime.shell.Shell.loop(Shell.scala:124)
    at ca.uwaterloo.flix.tools.SimpleRunner$.run(SimpleRunner.scala:62)
    at ca.uwaterloo.flix.Main$.main(Main.scala:125)
    at ca.uwaterloo.flix.Main.main(Main.scala)

Not immediately sure what to make of that 🤔

paulbutcher commented 1 year ago

And it seems to have been introduced somewhere between 0.32.0 and 0.33.0

paulbutcher commented 1 year ago

I think I may need some help from someone who understands code generation better than I do, as I'm not really sure how to get started with this. This is the simplest form I've found which reproduces the error:

def recvWithDefault(rx: Receiver[Int32, r]): Int32 \ { Read(r), Write(r) } =
    select {
        case x <- Channel.recv(rx) => x
        case _                     => 1
    }

def main(): Int32 \ IO = 
  let (_, rx) = Channel.buffered(Static, 1);
  recvWithDefault(rx)

In particular, this, which on the face of things should behave identically, doesn't reproduce the error:

def main(): Int32 \ IO = 
  let (_, rx) = Channel.buffered(Static, 1);
  select {
      case x <- Channel.recv(rx) => x
      case _                     => 1
  }
paulbutcher commented 1 year ago

@mlutze do you have any insight?

paulbutcher commented 1 year ago

OK, so the shell wraps main up in an unsafe_cast and println. If I bring that in, then I can see the error without the REPL:

def recvWithDefault(rx: Receiver[Int32, r]): Int32 \ { Read(r), Write(r) } =
    select {
        case x <- Channel.recv(rx) => x
        case _                     => 1
    }

def mainx(): Int32 \ IO = 
  let (_, rx) = Channel.buffered(Static, 1);
  recvWithDefault(rx)

def main(): Unit \ IO =
  unsafe_cast println(mainx()) as \ IO
java -jar flix.jar run
java.lang.ClassCastException: class List%Nil%Obj cannot be cast to class dev.flix.runtime.Unit (List%Nil%Obj and dev.flix.runtime.Unit are in unnamed module of loader ca.uwaterloo.flix.language.phase.jvm.FlixClassLoader @46f699d5)
    at Def%main%.invoke(Unknown Source)
    at Cont%Obj.unwind(Cont%Obj)
    at Ns.m_main%(Unknown Source)
    at Main.main(Main)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.mainFunction$1(Bootstrap.scala:79)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.$anonfun$bootstrap$9(Bootstrap.scala:88)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.$anonfun$bootstrap$9$adapted(Bootstrap.scala:88)
    at ca.uwaterloo.flix.tools.Packager$.$anonfun$run$2(Packager.scala:321)
    at scala.Option.map(Option.scala:242)
    at ca.uwaterloo.flix.tools.Packager$.$anonfun$run$1(Packager.scala:319)
    at scala.Option.flatMap(Option.scala:283)
    at ca.uwaterloo.flix.tools.Packager$.run(Packager.scala:318)
    at ca.uwaterloo.flix.Main$.main(Main.scala:149)
    at ca.uwaterloo.flix.Main.main(Main.scala)
paulbutcher commented 1 year ago

So I guess that means that what's actually returned from mainx in this case is a List%Nil%Obj, not Unit? But I don't really know where to go looking for the error (or even whether it is an error for it to be doing so)?

JonathanStarup commented 1 year ago

I've seen this before somewhere but I cannot remember what the context was 🤔

Oh here https://github.com/flix/flix/issues/4840, not that I know what the issue is. I can take a dive in the bytecode later

JonathanStarup commented 1 year ago

@paulbutcher does it still persist if you disable the optimizer? (maybe that has to be done in code)

paulbutcher commented 1 year ago

@paulbutcher does it still persist if you disable the optimizer? (maybe that has to be done in code)

No, disabling the optimiser makes no difference:

java -jar flix.jar run --Xno-optimizer
java.lang.ClassCastException: class List%Nil%Obj cannot be cast to class dev.flix.runtime.Unit (List%Nil%Obj and dev.flix.runtime.Unit are in unnamed module of loader ca.uwaterloo.flix.language.phase.jvm.FlixClassLoader @4c51bb7)
    at Def%recvWithDefault%141262.invoke(Unknown Source)
    at Cont%Int32.unwind(Cont%Int32)
    at Def%main.invoke(Unknown Source)
    at Cont%Obj.unwind(Cont%Obj)
    at Def%main%.invoke(Unknown Source)
    at Cont%Obj.unwind(Cont%Obj)
    at Ns.m_main%(Unknown Source)
    at Main.main(Main)
    at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    at java.base/java.lang.reflect.Method.invoke(Method.java:578)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.mainFunction$1(Bootstrap.scala:79)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.$anonfun$bootstrap$9(Bootstrap.scala:88)
    at ca.uwaterloo.flix.language.phase.jvm.Bootstrap$.$anonfun$bootstrap$9$adapted(Bootstrap.scala:88)
    at ca.uwaterloo.flix.tools.Packager$.$anonfun$run$2(Packager.scala:321)
    at scala.Option.map(Option.scala:242)
    at ca.uwaterloo.flix.tools.Packager$.$anonfun$run$1(Packager.scala:319)
    at scala.Option.flatMap(Option.scala:283)
    at ca.uwaterloo.flix.tools.Packager$.run(Packager.scala:318)
    at ca.uwaterloo.flix.Main$.main(Main.scala:149)
    at ca.uwaterloo.flix.Main.main(Main.scala)
JonathanStarup commented 1 year ago

The error is in the generated match error case of select (in the generated code, not saying that's the source of the issue)

if (0 == var12) {
    IList%Obj var14 = (IList%Obj)var13;
    Def%unsafeGetAndUnlock%141564 var26 = new Def%unsafeGetAndUnlock%141564();
    var26.arg0 = (IMpmc%Int32)var10;
    var26.arg1 = (IList%Obj)var14;
    int var15 = var26.unwind();
    var27 = var15;
} else {
    int var16 = ((Tuple2%Int32%Obj)var11).field0;
    Unit var17 = (Unit)((Tuple2%Int32%Obj)var11).field1;
    if (-1 != var16) {
        throw new MatchError(new ReifiedSourceLocation("main\\src\\personal\\test.flix", 2, 5, 5, 6));
    }

    var27 = 1;
}
magnus-madsen commented 1 year ago

Thanks for chipping in @JonathanStarup