effekt-lang / effekt

A language with lexical effect handlers and lightweight effect polymorphism
https://effekt-lang.org
MIT License
334 stars 24 forks source link

Refactor/core delimited control #669

Closed b-studios closed 2 weeks ago

b-studios commented 2 weeks ago

The following is a minimal example that segfaults in LLVM

effect Get() : Int

def run(n: Int) = {
  try {
    do Get()
  } with Get { () =>
    val x = 4;
    resume(x)
  }
}

def main() = println(run(5))

LLDB tells me it is a return-continuation (the one of the reset, I guess)

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x100003758)
    frame #0: 0x000000010000375c countdown`returnAddress_27 + 12
countdown`returnAddress_27:
->  0x10000375c <+12>: str    x9, [x2, #0x8]
    0x100003760 <+16>: ldur   x2, [x8, #-0x18]
    0x100003764 <+20>: br     x2

Maybe not surprisingly removing the resume from the example (which will drop the return-continuation of the reset) fixes the issue.

More surprisingly, changing it to try { 42 } ... also fixes the issue, while it needs to run the return continuation. Potentially shift-resume corrupts the stack and the frame on it...

Furthermore, the modified version:

effect Get() : Int

def run(n: Int) = {
  println("a")
  try {
    println("b")
    do Get()
    println("c")
  } with Get { () =>
    println("d")
    val x = 4;
    resume(x)
    println("e")
  }
}

def main() = println(run(5))

prints

a
b
d
fish: Job 1, './out/countdown 1' terminated by signal SIGBUS (Misaligned address error)

suggesting that resuming is broken.

serkm commented 2 weeks ago

Here is the generated machine code from your example

def main_3675 = {
  push { (run_3810 : Int) =>
    subst [v_r_3697_3772 !-> run_3810];
    let pureApp_3809 = show_14(v_r_3697_3772);
    let pureApp_3808 = println_1(pureApp_3809);
    subst [v_r_32232_3784 !-> pureApp_3808];
    return v_r_32232_3784
  };
  let p3_3788 = reset { (returned_3811 : Int) =>
    return returned_3811
  };
  push { (v_coe_3770117_3801 : Positive) =>
    let pureApp_3812 = unboxInt_297(v_coe_3770117_3801);
    return pureApp_3812
  };
  let k732_3804 = shift0p p3_3788;
  let longLiteral_3813 = 4;
  subst [x1065_3806 !-> longLiteral_3813];
  resume k732_3804;
  return x1065_3806
};
jump main_3675

It tries to unbox an integer that isn't boxed in the first place.

b-studios commented 2 weeks ago

Ok, I think this is ready to go.

We should do three things in the future:

  1. disable the optimizer by always returning tree here: https://github.com/effekt-lang/effekt/blob/dc9149737c9d713ce04a26591d77d8cdfccf50e7/effekt/shared/src/main/scala/effekt/core/Optimizer.scala#L25 and observe that many tests fail in LLVM (maybe @phischu can look into this?)
  2. investigate why the two interesting use cases of bidirectional handlers still need to be commented out (maybe @serkm can look into this?)
  3. desugar resume(42) into resume { return 42 } early on and always treat handlers as bidirectional. However, this has some implications on error messages and type-on-hover over resume...

However, this is still a big step forwards to supporting bidirectional handlers uniformly across backends! Thanks for the help @serkm, @marzipankaiser, and @phischu!