Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transformation for return keyword #923

Closed
wants to merge 1 commit into from

Conversation

jad-hamza
Copy link
Contributor

This implements @romac transformation idea (with suggestions from @samarion) for the return keyword.

Feedback is welcome! (Loops are not supported yet, I can add them to this PR or a different one)

Here are the examples we discussed (added to imperative benchmarks in this PR):

SimpleReturn.scala
[ Debug  ] Symbols before ReturnElimination
[ Debug  ] 
[ Debug  ] -------------Functions-------------
[ Debug  ] def example1: Int = {
[ Debug  ]   return 0
[ Debug  ]   1
[ Debug  ] }
[ Debug  ] 
[ Debug  ] def example2((x: Int) @pure): Int = {
[ Debug  ]   val a: Int = if (x > 0) {
[ Debug  ]     return 1
[ Debug  ]   } else {
[ Debug  ]     2
[ Debug  ]   }
[ Debug  ]   3
[ Debug  ] }
[ Debug  ] 
[ Debug  ] def example3((cond: Boolean) @pure): Boolean = {
[ Debug  ]   val a: Int = if (cond) {
[ Debug  ]     return true
[ Debug  ]   } else {
[ Debug  ]     1
[ Debug  ]   }
[ Debug  ]   false
[ Debug  ] }
[ Debug  ] 
[ Debug  ] def example4: Boolean = {
[ Debug  ]   val x: Int = return false
[ Debug  ]   true
[ Debug  ] }
[ Debug  ] 
[ Debug  ] def example5((cond: Boolean) @pure): Int = {
[ Debug  ]   val x: Int = if (cond) {
[ Debug  ]     return 100
[ Debug  ]   } else {
[ Debug  ]     5
[ Debug  ]   }
[ Debug  ]   x
[ Debug  ] }
[ Debug  ] 
[ Debug  ] 
[ Debug  ] Symbols after ReturnElimination
[ Debug  ] 
[ Debug  ] -------------Functions-------------
[ Debug  ] def example1: Int = {
[ Debug  ]   val topLevelCF: ControlFlow[Int, Int] = {
[ Debug  ]     val cf: ControlFlow[Int, Nothing] = Return[Int, Nothing](0)
[ Debug  ]     cf match {
[ Debug  ]       case Return(retValue) =>
[ Debug  ]         Return[Int, Int](retValue)
[ Debug  ]       case Proceed(proceedValue) =>
[ Debug  ]         Proceed[Int, Int](1)
[ Debug  ]     }
[ Debug  ]   }
[ Debug  ]   topLevelCF match {
[ Debug  ]     case Return(retValue) =>
[ Debug  ]       retValue
[ Debug  ]     case Proceed(proceedValue) =>
[ Debug  ]       proceedValue
[ Debug  ]   }
[ Debug  ] }
[ Debug  ] 
[ Debug  ] def example2((x: Int) @pure): Int = {
[ Debug  ]   val topLevelCF: ControlFlow[Int, Int] = {
[ Debug  ]     val cf: ControlFlow[Int, Int] = if (x > 0) {
[ Debug  ]       Return[Int, Int](1)
[ Debug  ]     } else {
[ Debug  ]       Proceed[Int, Int](2)
[ Debug  ]     }
[ Debug  ]     cf match {
[ Debug  ]       case Return(retValue) =>
[ Debug  ]         Return[Int, Int](retValue)
[ Debug  ]       case Proceed(proceedValue) =>
[ Debug  ]         Proceed[Int, Int](3)
[ Debug  ]     }
[ Debug  ]   }
[ Debug  ]   topLevelCF match {
[ Debug  ]     case Return(retValue) =>
[ Debug  ]       retValue
[ Debug  ]     case Proceed(proceedValue) =>
[ Debug  ]       proceedValue
[ Debug  ]   }
[ Debug  ] }
[ Debug  ] 
[ Debug  ] def example3((cond: Boolean) @pure): Boolean = {
[ Debug  ]   val topLevelCF: ControlFlow[Boolean, Boolean] = {
[ Debug  ]     val cf: ControlFlow[Boolean, Int] = if (cond) {
[ Debug  ]       Return[Boolean, Int](true)
[ Debug  ]     } else {
[ Debug  ]       Proceed[Boolean, Int](1)
[ Debug  ]     }
[ Debug  ]     cf match {
[ Debug  ]       case Return(retValue) =>
[ Debug  ]         Return[Boolean, Boolean](retValue)
[ Debug  ]       case Proceed(proceedValue) =>
[ Debug  ]         Proceed[Boolean, Boolean](false)
[ Debug  ]     }
[ Debug  ]   }
[ Debug  ]   topLevelCF match {
[ Debug  ]     case Return(retValue) =>
[ Debug  ]       retValue
[ Debug  ]     case Proceed(proceedValue) =>
[ Debug  ]       proceedValue
[ Debug  ]   }
[ Debug  ] }
[ Debug  ] 
[ Debug  ] def example4: Boolean = {
[ Debug  ]   val topLevelCF: ControlFlow[Boolean, Boolean] = {
[ Debug  ]     val cf: ControlFlow[Boolean, Int] = Return[Boolean, Int](false)
[ Debug  ]     cf match {
[ Debug  ]       case Return(retValue) =>
[ Debug  ]         Return[Boolean, Boolean](retValue)
[ Debug  ]       case Proceed(proceedValue) =>
[ Debug  ]         Proceed[Boolean, Boolean](true)
[ Debug  ]     }
[ Debug  ]   }
[ Debug  ]   topLevelCF match {
[ Debug  ]     case Return(retValue) =>
[ Debug  ]       retValue
[ Debug  ]     case Proceed(proceedValue) =>
[ Debug  ]       proceedValue
[ Debug  ]   }
[ Debug  ] }
[ Debug  ] 
[ Debug  ] def example5((cond: Boolean) @pure): Int = {
[ Debug  ]   val topLevelCF: ControlFlow[Int, Int] = {
[ Debug  ]     val cf: ControlFlow[Int, Int] = if (cond) {
[ Debug  ]       Return[Int, Int](100)
[ Debug  ]     } else {
[ Debug  ]       Proceed[Int, Int](5)
[ Debug  ]     }
[ Debug  ]     cf match {
[ Debug  ]       case Return(retValue) =>
[ Debug  ]         Return[Int, Int](retValue)
[ Debug  ]       case Proceed(proceedValue) =>
[ Debug  ]         Proceed[Int, Int](proceedValue)
[ Debug  ]     }
[ Debug  ]   }
[ Debug  ]   topLevelCF match {
[ Debug  ]     case Return(retValue) =>
[ Debug  ]       retValue
[ Debug  ]     case Proceed(proceedValue) =>
[ Debug  ]       proceedValue
[ Debug  ]   }
[ Debug  ] }
ControlFlow2.scala
[ Debug  ] Symbols before ReturnElimination
[ Debug  ] 
[ Debug  ] -------------Functions-------------
[ Debug  ] def foo((x: Option[BigInt]) @pure, (a: Boolean) @pure, (b: Boolean) @pure): BigInt = {
[ Debug  ]   (if (a && b) {
[ Debug  ]     return 1
[ Debug  ]   } else {
[ Debug  ]     ()
[ Debug  ]   })
[ Debug  ]   val y: BigInt = x match {
[ Debug  ]     case None() =>
[ Debug  ]       return 0
[ Debug  ]     case Some(x) if a =>
[ Debug  ]       return x + 1
[ Debug  ]     case Some(x) if b =>
[ Debug  ]       return x + 2
[ Debug  ]     case Some(x) =>
[ Debug  ]       x
[ Debug  ]   }
[ Debug  ]   -y
[ Debug  ] }
[ Debug  ] 
[ Debug  ] 
[ Debug  ] Symbols after ReturnElimination
[ Debug  ] 
[ Debug  ] -------------Functions-------------
[ Debug  ] def foo((x: Option[BigInt]) @pure, (a: Boolean) @pure, (b: Boolean) @pure): BigInt = {
[ Debug  ]   val topLevelCF: ControlFlow[BigInt, BigInt] = {
[ Debug  ]     val cf: ControlFlow[BigInt, Unit] = if (a && b) {
[ Debug  ]       Return[BigInt, Unit](1)
[ Debug  ]     } else {
[ Debug  ]       Proceed[BigInt, Unit](())
[ Debug  ]     }
[ Debug  ]     cf match {
[ Debug  ]       case Return(retValue) =>
[ Debug  ]         Return[BigInt, BigInt](retValue)
[ Debug  ]       case Proceed(proceedValue) =>
[ Debug  ]         val cf: ControlFlow[BigInt, BigInt] = x match {
[ Debug  ]           case None() =>
[ Debug  ]             Return[BigInt, BigInt](0)
[ Debug  ]           case Some(x) if a =>
[ Debug  ]             Return[BigInt, BigInt](x + 1)
[ Debug  ]           case Some(x) if b =>
[ Debug  ]             Return[BigInt, BigInt](x + 2)
[ Debug  ]           case Some(x) =>
[ Debug  ]             Proceed[BigInt, BigInt](x)
[ Debug  ]         }
[ Debug  ]         cf match {
[ Debug  ]           case Return(retValue) =>
[ Debug  ]             Return[BigInt, BigInt](retValue)
[ Debug  ]           case Proceed(proceedValue) =>
[ Debug  ]             Proceed[BigInt, BigInt](-proceedValue)
[ Debug  ]         }
[ Debug  ]     }
[ Debug  ]   }
[ Debug  ]   topLevelCF match {
[ Debug  ]     case Return(retValue) =>
[ Debug  ]       retValue
[ Debug  ]     case Proceed(proceedValue) =>
[ Debug  ]       proceedValue
[ Debug  ]   }
[ Debug  ] }

Copy link
Member

@samarion samarion left a comment

Choose a reason for hiding this comment

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

This is great, thanks for the quick implementation!

One small nit is that the extra let/match construct which extracts the return value from topLevelCF makes the transformation result a bit less readable. WDYT of adding some simple inlining logic that matches this exact structure and pushes the return value extraction into the leaf match expressions in the transformed body?

with utils.SyntheticSorts { self =>

val s: Trees
val t: s.type
Copy link
Member

Choose a reason for hiding this comment

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

Your transformation isn't checking all trees (e.g. a return inside a type refinement wouldn't be rejected).

In general, it's safer to keep s and t types potentially distinct so the (Scala) type checker will make sure you visit all trees in your transformation.

ValDef.fresh("cf", ControlFlowSort.controlFlow(retType, firstType)).setPos(e)
val transformedRest = processBlockExpressions(rest)

if (rest.exists(tc.exprHasReturn)) {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: this would probably be more readable if you pushed the if-expression down to the ControlFlowSort.andThen lambda argument.

}

case e +: rest =>
val unusedVal = ValDef.fresh("unused", e.getType)
Copy link
Member

Choose a reason for hiding this comment

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

You could preserve the Block, something like

case es =>
  val nonReturnEs = es.takeWhile(e => !tc.hasExprReturn(e))
  Block(nonReturnEs, processBlockExpressions(es.drop(nonReturnEs.size()))).copiedFrom(expr)

You'll probably need to handle the case where es.drop(nonReturnEs.size()) is empty specially.

* @param expr The expression to return
*/
sealed case class Return(expr: Expr) extends Expr with CachingTyped {
override protected def computeType(implicit s: Symbols): Type = NothingType()
Copy link
Member

Choose a reason for hiding this comment

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

You should check that expr.isTyped here and return Untyped otherwise.



// ControlFlowSort represents the following class:
// sealed abstract class ControlFlow[Ret, Cur] {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: remove trailing brace

@romac
Copy link
Member

romac commented Mar 1, 2021

Wow, great job @jad-hamza!

Would be great if we could also add a benchmark which involves mutation, to ensure that the transformation plays nicely with the imperative elimination phase. I know you have a benchmark which involves mutation and loops this in #925, but if local mutation already works with this PR it might be good to include it here as well.

@jad-hamza
Copy link
Contributor Author

Thanks for the comments! I updated the PR #925, should I update this one as well or can we close and continue on #925?

(I still have to remove the top level control flow variable)

@jad-hamza
Copy link
Contributor Author

Would be great if we could also add a benchmark which involves mutation, to ensure that the transformation plays nicely with the imperative elimination phase. I know you have a benchmark which involves mutation and loops this in #925, but if local mutation already works with this PR it might be good to include it here as well.

Thanks! I've added a simple example with mutations (and no loops) in the other PR, I can port the changes here if you prefer we merge this separately.

@romac
Copy link
Member

romac commented Mar 1, 2021

Thanks for the comments! I updated the PR #925, should I update this one as well or can we close and continue on #925?

Yeah let's close this one and continue in #925.

@jad-hamza jad-hamza closed this Mar 1, 2021
@jad-hamza jad-hamza deleted the monadic-return branch March 1, 2021 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants