Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ object RemoveTailResumptions {
case Stmt.Var(ref, init, capture, body) => tailResumptive(k, body) && !freeInExpr(init)
case Stmt.Get(ref, annotatedCapt, tpe, id, body) => tailResumptive(k, body)
case Stmt.Put(ref, annotatedCapt, value, body) => tailResumptive(k, body) && !freeInExpr(value)
case Stmt.Reset(BlockLit(tparams, cparams, vparams, bparams, body)) => tailResumptive(k, body) // is this correct?
case Stmt.Reset(BlockLit(tparams, cparams, vparams, bparams, body)) => false
case Stmt.Shift(prompt, body) => stmt.tpe == Type.TBottom
case Stmt.Resume(k2, body) => k2.id == k // what if k is free in body?
case Stmt.Hole() => true
Expand All @@ -55,7 +55,7 @@ object RemoveTailResumptions {
Stmt.Region(removeTailResumption(k, body))
case Stmt.Alloc(id, init, region, body) => Stmt.Alloc(id, init, region, removeTailResumption(k, body))
case Stmt.Var(id, init, capture, body) => Stmt.Var(id, init, capture, removeTailResumption(k, body))
case Stmt.Reset(body) => Stmt.Reset(removeTailResumption(k, body))
case Stmt.Reset(body) => stmt
case Stmt.Resume(k2, body) if k2.id == k => body

case Stmt.Resume(k, body) => stmt
Expand Down
13 changes: 7 additions & 6 deletions effekt/shared/src/main/scala/effekt/cps/Contify.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,13 @@ object Contify {
given Substitution = Substitution(conts = Map(k -> ContVar(k2)))
Stmt.LetCont(id, ContLam(vparams, ks, substitute(rewrittenBody)), contify(id, rewrittenRest))

case Some(cont: ContLam) if returnsUnique && !isRecursive =>
val k2 = Id("k")
given Substitution = Substitution(conts = Map(k -> ContVar(k2)))
LetCont(k2, cont,
Stmt.LetCont(id, ContLam(vparams, ks, substitute(rewrittenBody)),
contify(id, rewrittenRest)))
// leads to `k_6 is not defined` when disabling optimizations on issue861.effekt
// case Some(cont: ContLam) if returnsUnique && !isRecursive =>
// val k2 = Id("k")
// given Substitution = Substitution(conts = Map(k -> ContVar(k2)))
// LetCont(k2, cont,
// Stmt.LetCont(id, ContLam(vparams, ks, substitute(rewrittenBody)),
// contify(id, rewrittenRest)))
Comment on lines +88 to +94
Copy link
Collaborator Author

@b-studios b-studios Mar 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, we do not have a regression test since we need to disable optimizations.

Maybe a variant of #851 where we selectively run tests without optimizations would be useful (here JS backend so a lot of the reported segfaults etc. do not matter here.)

The same holds for issue842.effekt which is a reproduction for #842 that we also should once run without optimizations.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the present PR I comment this out, however this "might" have performance implications...

Copy link
Contributor

@jiribenes jiribenes Mar 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like we're hitting the limits of the way we currently do example-based regression tests...
We have multiple axes now, but we really only cleanly account for two of them (1 & 3):

  1. by backend:
    • JS
    • LLVM
    • ...
  2. by opts:
    • with opts
    • no opts
  3. stdlib vs compiler:
    • stdlib
    • compiler
  4. by backend-specific details:
    • with valgrind
    • without valgrind

I don't really know how to neatly account for also 2 and 4 without making things too confusing 🤔

case _ =>
Stmt.LetDef(id, BlockLit(vparams, Nil, ks, k, rewrittenBody), rewrittenRest)
}
Expand Down
206 changes: 206 additions & 0 deletions effekt/shared/src/main/scala/effekt/cps/PrettyPrinter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package effekt
package cps

import core.Id

import kiama.output.ParenPrettyPrinter
import effekt.source.FeatureFlag

object PrettyPrinter extends ParenPrettyPrinter {

import kiama.output.PrettyPrinterTypes.Document

override val defaultIndent = 2

def format(t: ModuleDecl): Document =
pretty(toDoc(t), 4)

def format(defs: List[ToplevelDefinition]): String =
pretty(toDoc(defs), 60).layout

def format(s: Stmt): String =
pretty(toDoc(s), 60).layout

def format(t: Block): String =
pretty(toDoc(t), 60).layout

def format(e: Expr): String =
pretty(toDoc(e), 60).layout

val show: PartialFunction[Any, String] = {
case m: ModuleDecl => format(m).layout
case d: ToplevelDefinition => format(List(d))
case s: Stmt => format(s)
case b: Block => format(b)
case e: Expr => format(e)
case x: Id => x.show
}

val emptyline: Doc = line <> line

def toDoc(m: ModuleDecl): Doc = {
"module" <+> m.path <> emptyline <>
toDoc(m.definitions)
}

def toDoc(definitions: List[ToplevelDefinition]): Doc =
vsep(definitions map toDoc, semi)

def toDoc(d: ToplevelDefinition): Doc = d match {
case ToplevelDefinition.Def(id, block) =>
"def" <+> toDoc(id) <+> "=" <+> toDoc(block)
case ToplevelDefinition.Val(id, ks, k, binding) =>
"let" <+> toDoc(id) <+> "|" <+> toDoc(ks) <> "," <+> toDoc(k) <+> "=" <+> toDoc(binding)
case ToplevelDefinition.Let(id, binding) =>
"let" <+> toDoc(id) <+> "=" <+> toDoc(binding)
}

def toDoc(e: Expr): Doc = e match {
case DirectApp(id, vargs, bargs) =>
toDoc(id) <> argsToDoc(vargs, bargs)
case Pure.ValueVar(id) => toDoc(id)
case Pure.Literal(()) => "()"
case Pure.Literal(s: String) => "\"" + s + "\""
case Pure.Literal(value) => value.toString
case Pure.PureApp(id, vargs) => toDoc(id) <> argsToDoc(vargs, Nil)
case Pure.Make(data, tag, vargs) => "make" <+> toDoc(data.name) <+> toDoc(tag) <> argsToDoc(vargs, Nil)
case Pure.Box(b) => parens("box" <+> toDoc(b))
}

def toDoc(b: Block): Doc = b match {
case Block.BlockVar(id) => toDoc(id)
case b: Block.BlockLit => toDoc(b)
case Block.Unbox(e) => parens("unbox" <+> toDoc(e))
case Block.New(handler) => "new" <+> toDoc(handler)
}

def toDoc(b: Block.BlockLit): Doc = b match {
case Block.BlockLit(vps, bps, ks, k, body) =>
val params = if (vps.isEmpty && bps.isEmpty) emptyDoc else
parens(hsep(vps.map(toDoc), comma)) <+> hsep(bps.map(toDoc))
braces {
space <> params <+> "|" <+> toDoc(ks) <> "," <+> toDoc(k) <+> "=>" <+>
nest(line <> toDoc(body)) <> line
}
}

def toDoc(s: Stmt): Doc = s match {
case Stmt.Jump(k, vargs, ks) =>
"jump" <+> toDoc(k) <> argsToDoc(vargs, Nil) <+> "@" <+> toDoc(ks)

case Stmt.App(callee, vargs, bargs, ks, k) =>
toDoc(callee) <> argsToDoc(vargs, bargs) <+> "@" <+> toDoc(ks) <> "," <+> toDoc(k)

case Stmt.Invoke(callee, method, vargs, bargs, ks, k) =>
toDoc(callee) <> "." <> method.name.toString <> argsToDoc(vargs, bargs) <+> "@" <+> toDoc(ks) <> "," <+> toDoc(k)

case Stmt.If(cond, thn, els) =>
"if" <+> parens(toDoc(cond)) <+> block(toDoc(thn)) <+> "else" <+> block(toDoc(els))

case Stmt.Match(scrutinee, clauses, default) =>
val cs = braces(nest(line <> vsep(clauses map { case (p, b) =>
"case" <+> toDoc(p) <+> toDoc(b)
})) <> line)
val d = default.map { body =>
space <> "else" <+> block(toDoc(body))
}.getOrElse(emptyDoc)
toDoc(scrutinee) <+> "match" <+> cs <> d

case Stmt.LetDef(id, binding, body) =>
"def" <+> toDoc(id) <+> "=" <+> toDoc(binding) <> line <>
toDoc(body)

case Stmt.LetExpr(id, binding, body) =>
"let" <+> toDoc(id) <+> "=" <+> toDoc(binding) <> line <>
toDoc(body)

case Stmt.LetCont(id, binding, body) =>
"let" <+> toDoc(id) <+> "=" <+> toDoc(binding) <> line <>
toDoc(body)

case Stmt.Region(id, ks, body) =>
"region" <+> toDoc(id) <+> "@" <+> toDoc(ks) <+> block(toDoc(body))

case Stmt.Alloc(id, init, region, body) =>
"var" <+> toDoc(id) <+> "in" <+> toDoc(region) <+> "=" <+> toDoc(init) <> ";" <> line <>
toDoc(body)

case Stmt.Dealloc(ref, body) =>
"dealloc" <> parens(toDoc(ref)) <> ";" <> line <>
toDoc(body)

case Stmt.Var(id, init, ks, body) =>
"var" <+> toDoc(id) <+> "=" <+> toDoc(init) <+> "@" <+> toDoc(ks) <> ";" <> line <>
toDoc(body)

case Stmt.Get(ref, id, body) =>
"let" <+> toDoc(id) <+> "=" <+> "!" <> toDoc(ref) <> ";" <> line <>
toDoc(body)

case Stmt.Put(ref, value, body) =>
toDoc(ref) <+> ":=" <+> toDoc(value) <> ";" <> line <>
toDoc(body)

case Stmt.Reset(prog, ks, k) =>
"reset" <+> toDoc(prog) <+> "@" <+> toDoc(ks) <> "," <+> toDoc(k)

case Stmt.Shift(prompt, body, ks, k) =>
"shift" <> parens(toDoc(prompt)) <+> toDoc(body) <+> "@" <+> toDoc(ks) <> "," <+> toDoc(k)

case Stmt.Resume(r, body, ks, k) =>
"resume" <> parens(toDoc(r)) <+> toDoc(body) <+> "@" <+> toDoc(ks) <> "," <+> toDoc(k)

case Stmt.Hole() =>
"<>"
}

def toDoc(clause: Clause): Doc = clause match {
case Clause(vparams, body) =>
parens(hsep(vparams.map(toDoc), comma)) <+> "=>" <+> block(toDoc(body))
}

def toDoc(impl: Implementation): Doc = {
val handlerName = toDoc(impl.interface.name)
val clauses = impl.operations.map { op =>
"def" <+> toDoc(op.name) <> paramsToDoc(op.vparams, op.bparams, op.ks, op.k) <+> "=" <+>
nested(toDoc(op.body))
}
handlerName <+> block(vsep(clauses))
}

def toDoc(k: Cont): Doc = k match {
case Cont.ContVar(id) => toDoc(id)
case Cont.ContLam(results, ks, body) =>
braces {
space <> parens(hsep(results.map(toDoc), comma)) <+> "|" <+> toDoc(ks) <+> "=>" <+>
nest(line <> toDoc(body)) <> line
}
case Cont.Abort => "abort"
}

def toDoc(ks: MetaCont): Doc = toDoc(ks.id)

def toDoc(s: symbols.Symbol): Doc = s.show

def argsToDoc(vargs: List[Pure], bargs: List[Block]): Doc = {
val vargsDoc = if (vargs.isEmpty && !bargs.isEmpty) emptyDoc else parens(vargs.map(toDoc))
val bargsDoc = if (bargs.isEmpty) emptyDoc else hcat(bargs.map { b => braces(toDoc(b)) })
vargsDoc <> bargsDoc
}

def paramsToDoc(vps: List[Id], bps: List[Id], ks: Id, k: Id): Doc = {
val vpsDoc = if (vps.isEmpty && !bps.isEmpty) emptyDoc else parens(vps.map(toDoc))
val bpsDoc = if (bps.isEmpty) emptyDoc else hcat(bps.map(toDoc))
vpsDoc <> bpsDoc <+> "|" <+> toDoc(ks) <> "," <+> toDoc(k)
}

def nested(content: Doc): Doc = group(nest(line <> content))

def parens(docs: List[Doc]): Doc = parens(hsep(docs, comma))

def braces(docs: List[Doc]): Doc = braces(hsep(docs, semi))

def block(content: Doc): Doc = braces(nest(line <> content) <> line)

def block(docs: List[Doc]): Doc = block(vsep(docs, line))
}
1 change: 1 addition & 0 deletions effekt/shared/src/main/scala/effekt/util/Debug.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ val show: PartialFunction[Any, String] =
TypePrinter.show orElse
core.PrettyPrinter.show orElse
generator.js.PrettyPrinter.show orElse
cps.PrettyPrinter.show orElse
showGeneric

inline def debug[A](inline value: A): A =
Expand Down
1 change: 1 addition & 0 deletions examples/pos/issue842.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
n
14 changes: 14 additions & 0 deletions examples/pos/issue842.effekt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type Answer { Yes(); No() }

def println(ans: Answer): Unit = ans match {
case Yes() => println("y")
case No() => println("n")
}

def join(left: Answer, right: Answer): Answer = (left, right) match {
case (Yes(), Yes()) => Yes()
case (No() , _) => No()
case (_ , No() ) => No()
}

def main() = println(join(Yes(), No()))
4 changes: 4 additions & 0 deletions examples/pos/issue861.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
true true
true false
false true
false false
18 changes: 18 additions & 0 deletions examples/pos/issue861.effekt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
effect flip(): Bool
effect choice(): Bool

// this is a reproduction for #861
def main() = {
try {
val x = do choice()
val y = do choice()
println(x.show ++ " " ++ y.show)
} with choice {
try {
resume(do flip())
} with flip {
resume(true)
resume(false)
}
}
}