mirror of
https://github.com/enso-org/enso.git
synced 2024-11-23 08:08:34 +03:00
Implement demand analysis (#658)
This commit is contained in:
parent
44e5341278
commit
16b24d58e7
@ -1,3 +1,18 @@
|
|||||||
# Enso Documentation
|
# Enso Documentation
|
||||||
This folder contains the documentation for the implementation of the Enso
|
This folder contains the documentation for the implementation of the Enso
|
||||||
programming language.
|
programming language. The documentation is generally broken up into three
|
||||||
|
categories: design, specification, and implementation.
|
||||||
|
|
||||||
|
It is broken up into categories as follows:
|
||||||
|
|
||||||
|
- **[Language Server](language-server/):** Documentation pertaining to the Enso
|
||||||
|
language server.
|
||||||
|
- **[Runtime](runtime/):** Documentation pertaining to the Enso runtime.
|
||||||
|
- **[Semantics](semantics/):** Documentation pertaining to the Enso language
|
||||||
|
semantics, insofar as it is seperable from the other categories.
|
||||||
|
- **[Style Guides](style-guides/):** Style guides for the various languages with
|
||||||
|
which we work.
|
||||||
|
- **[Syntax](syntax/):** Documentation pertaining to the syntax of Enso.
|
||||||
|
- **[Types](types/):** Documentation pertaining to Enso's type system.
|
||||||
|
- **[Enso Philosophy](enso-philosophy.md):** Information on the design
|
||||||
|
philosophy that underlies the development of Enso.
|
||||||
|
@ -352,10 +352,12 @@ You can find out more about the various components that make up Enso by having a
|
|||||||
read of the design documents listed below. These documents are highly technical,
|
read of the design documents listed below. These documents are highly technical,
|
||||||
and are not intended to be user-facing documentation.
|
and are not intended to be user-facing documentation.
|
||||||
|
|
||||||
- **[Syntax](syntax/syntax.md):** A description of Enso's syntax, with a focus
|
- **[Semantics](semantics/):** A description of Enso's semantics, with a focus
|
||||||
on both the main principles behind its design and its nitty gritty details.
|
on both the main principles and the details.
|
||||||
- **[The Type System](types/types.md):** A description of Enso's type system,
|
- **[Syntax](syntax/):** A description of Enso's syntax, with a focus on both
|
||||||
starting at a very high level and slowly getting into more detail as the
|
the main principles behind its design and its nitty gritty details.
|
||||||
design evolves.
|
- **[The Type System](types/):** A description of Enso's type system, starting
|
||||||
- **[The Runtime](runtime/runtime.md):** A description of the design for the
|
at a very high level and slowly getting into more detail as the design
|
||||||
runtime that is evolving in tandem with the runtime's development.
|
evolves.
|
||||||
|
- **[The Runtime](runtime/):** A description of the design for the runtime that
|
||||||
|
is evolving in tandem with the runtime's development.
|
||||||
|
59
doc/runtime/specification/demand-analysis.md
Normal file
59
doc/runtime/specification/demand-analysis.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Demand Analysis
|
||||||
|
Demand analysis is the process of deciding when to compute the value of a
|
||||||
|
suspended computation in a language which supports suspended computation.
|
||||||
|
|
||||||
|
<!-- MarkdownTOC levels="2,3" autolink="true" -->
|
||||||
|
|
||||||
|
- [Determining When To Force](#determining-when-to-force)
|
||||||
|
- [Avoiding Pathological Force-Thunk Chains](#avoiding-pathological-force-thunk-chains)
|
||||||
|
- [The Demand Analysis Algorithm](#the-demand-analysis-algorithm)
|
||||||
|
|
||||||
|
<!-- /MarkdownTOC -->
|
||||||
|
|
||||||
|
## Determining When To Force
|
||||||
|
As Enso supports dynamic dispatch for methods, we cannot always (even in the
|
||||||
|
presence of a typechecker), statically determine whether or not the function
|
||||||
|
that will eventually be called at any given call site. This means that we have
|
||||||
|
to come up with a more efficient way to handle suspended arguments.
|
||||||
|
|
||||||
|
This is done by making the _function application_ responsible for determining
|
||||||
|
when a value passed to it should be evaluated. It works as follows:
|
||||||
|
|
||||||
|
1. _All_ function arguments are passed suspended.
|
||||||
|
2. The function application is provided with knowledge of its argument
|
||||||
|
declarations.
|
||||||
|
3. As a result, the _application_ is responsible for deciding which arguments
|
||||||
|
should be evaluated up front, and which should be deferred.
|
||||||
|
4. In conjunction with this, the suspended arguments to the application need to
|
||||||
|
be explicitly forced at their use sites in the function body.
|
||||||
|
|
||||||
|
## Avoiding Pathological Force-Thunk Chains
|
||||||
|
The above approach does, when done naively, result in a severe performance
|
||||||
|
pathology when passing suspended arguments into functions also expecting
|
||||||
|
suspended arguments. Without intervention, the suspended argument gets wrapped
|
||||||
|
in a chain of thunks that has a significant performance cost to resolve, when
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
In order to avoid this issue, we do the following:
|
||||||
|
|
||||||
|
1. Where a suspended argument is passed into a function, we pass it _without_
|
||||||
|
explicitly forcing it.
|
||||||
|
2. All other uses of that argument are forced.
|
||||||
|
|
||||||
|
## The Demand Analysis Algorithm
|
||||||
|
The algorithm for performing demand analysis on Enso code is as follows.
|
||||||
|
|
||||||
|
```
|
||||||
|
for (node <- ir):
|
||||||
|
if (node is a usage):
|
||||||
|
if (node is not used in a function application):
|
||||||
|
node = force(node)
|
||||||
|
```
|
||||||
|
|
||||||
|
This, however, is not entirely sufficient to support codegen. At the time of
|
||||||
|
generating truffle code, we want to know whether a given usage in an argument to
|
||||||
|
a function application needs to be wrapped in a suspension or left alone (as is
|
||||||
|
the case for a suspended term passed to a function).
|
||||||
|
|
||||||
|
To this end, we instead explicitly mark the arguments to the application with
|
||||||
|
whether or not they should be suspended during codegen.
|
@ -14,9 +14,9 @@ Enso.
|
|||||||
<!-- MarkdownTOC levels="2,3" autolink="true" -->
|
<!-- MarkdownTOC levels="2,3" autolink="true" -->
|
||||||
|
|
||||||
- [Scoping](#scoping)
|
- [Scoping](#scoping)
|
||||||
- [Scoping Rules](#scoping-rules)
|
- [Scoping Rules](#scoping-rules)
|
||||||
- [Strict Evaluation](#strict-evaluation)
|
- [Strict Evaluation](#strict-evaluation)
|
||||||
- [Optional Suspension](#optional-suspension)
|
- [Optional Suspension](#optional-suspension)
|
||||||
- [Bindings](#bindings)
|
- [Bindings](#bindings)
|
||||||
|
|
||||||
<!-- /MarkdownTOC -->
|
<!-- /MarkdownTOC -->
|
||||||
@ -82,6 +82,15 @@ optional _suspension_, through the built-in `Suspended` type.
|
|||||||
- A value of type `Suspended a` may be forced to execute the suspended
|
- A value of type `Suspended a` may be forced to execute the suspended
|
||||||
computation and thereby obtain an `a`.
|
computation and thereby obtain an `a`.
|
||||||
|
|
||||||
|
This forcing can take place in two ways:
|
||||||
|
|
||||||
|
- The user calls the standard library function `force : Suspended a -> a` on the
|
||||||
|
value.
|
||||||
|
- Automatically, at a site where its evaluation is demanded. The algorithm for
|
||||||
|
this is simple. If a value of type `Suspended a` is provided in a location
|
||||||
|
that expects a value of type `a`, the compiler will insert an implicit call to
|
||||||
|
`force` to produce the `a`.
|
||||||
|
|
||||||
> The actionables for this section are:
|
> The actionables for this section are:
|
||||||
>
|
>
|
||||||
> - Make this far better specified.
|
> - Make this far better specified.
|
||||||
|
@ -77,15 +77,15 @@ class RecursionFixtures extends InterpreterRunner {
|
|||||||
"""
|
"""
|
||||||
|main = n ->
|
|main = n ->
|
||||||
| doNTimes = n ~block ->
|
| doNTimes = n ~block ->
|
||||||
| ~block
|
| block
|
||||||
| ifZero n-1 Unit (doNTimes n-1 ~block)
|
| ifZero n-1 Unit (doNTimes n-1 block)
|
||||||
|
|
|
|
||||||
| block =
|
| block =
|
||||||
| x = State.get
|
| x = State.get
|
||||||
| State.put x+1
|
| State.put x+1
|
||||||
|
|
|
|
||||||
| State.put 0
|
| State.put 0
|
||||||
| doNTimes n ~block
|
| doNTimes n block
|
||||||
| State.get
|
| State.get
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
val nestedThunkSum = getMain(nestedThunkSumCode)
|
val nestedThunkSum = getMain(nestedThunkSumCode)
|
||||||
|
@ -9,25 +9,13 @@ import org.enso.compiler.core.IR
|
|||||||
import org.enso.compiler.core.IR.{Expression, Module}
|
import org.enso.compiler.core.IR.{Expression, Module}
|
||||||
import org.enso.compiler.exception.CompilerError
|
import org.enso.compiler.exception.CompilerError
|
||||||
import org.enso.compiler.pass.IRPass
|
import org.enso.compiler.pass.IRPass
|
||||||
import org.enso.compiler.pass.analyse.{
|
import org.enso.compiler.pass.analyse.{AliasAnalysis, ApplicationSaturation, DemandAnalysis, TailCall}
|
||||||
AliasAnalysis,
|
import org.enso.compiler.pass.desugar.{GenerateMethodBodies, LiftSpecialOperators, OperatorToFunction}
|
||||||
ApplicationSaturation,
|
|
||||||
TailCall
|
|
||||||
}
|
|
||||||
import org.enso.compiler.pass.desugar.{
|
|
||||||
GenerateMethodBodies,
|
|
||||||
LiftSpecialOperators,
|
|
||||||
OperatorToFunction
|
|
||||||
}
|
|
||||||
import org.enso.interpreter.Language
|
import org.enso.interpreter.Language
|
||||||
import org.enso.interpreter.node.{ExpressionNode => RuntimeExpression}
|
import org.enso.interpreter.node.{ExpressionNode => RuntimeExpression}
|
||||||
import org.enso.interpreter.runtime.Context
|
import org.enso.interpreter.runtime.Context
|
||||||
import org.enso.interpreter.runtime.error.ModuleDoesNotExistException
|
import org.enso.interpreter.runtime.error.ModuleDoesNotExistException
|
||||||
import org.enso.interpreter.runtime.scope.{
|
import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope, TopLevelScope}
|
||||||
LocalScope,
|
|
||||||
ModuleScope,
|
|
||||||
TopLevelScope
|
|
||||||
}
|
|
||||||
import org.enso.polyglot.LanguageInfo
|
import org.enso.polyglot.LanguageInfo
|
||||||
import org.enso.syntax.text.{AST, Parser}
|
import org.enso.syntax.text.{AST, Parser}
|
||||||
|
|
||||||
@ -53,6 +41,7 @@ class Compiler(
|
|||||||
LiftSpecialOperators,
|
LiftSpecialOperators,
|
||||||
OperatorToFunction,
|
OperatorToFunction,
|
||||||
AliasAnalysis,
|
AliasAnalysis,
|
||||||
|
DemandAnalysis,
|
||||||
ApplicationSaturation(),
|
ApplicationSaturation(),
|
||||||
TailCall
|
TailCall
|
||||||
)
|
)
|
||||||
|
@ -5,7 +5,7 @@ import cats.implicits._
|
|||||||
import org.enso.compiler.core.IR._
|
import org.enso.compiler.core.IR._
|
||||||
import org.enso.compiler.exception.UnhandledEntity
|
import org.enso.compiler.exception.UnhandledEntity
|
||||||
import org.enso.interpreter.Constants
|
import org.enso.interpreter.Constants
|
||||||
import org.enso.syntax.text.{AST, Location}
|
import org.enso.syntax.text.AST
|
||||||
|
|
||||||
// FIXME [AA] All places where we currently throw a `RuntimeException` should
|
// FIXME [AA] All places where we currently throw a `RuntimeException` should
|
||||||
// generate informative and useful nodes in core.
|
// generate informative and useful nodes in core.
|
||||||
@ -129,14 +129,12 @@ object AstToIR {
|
|||||||
val pathStr = pathSegments.map(_.name).mkString(".")
|
val pathStr = pathSegments.map(_.name).mkString(".")
|
||||||
val loc = pathSegments.headOption
|
val loc = pathSegments.headOption
|
||||||
.flatMap(_.location)
|
.flatMap(_.location)
|
||||||
.flatMap(
|
.flatMap(locationStart =>
|
||||||
locationStart =>
|
pathSegments.lastOption
|
||||||
pathSegments.lastOption
|
.flatMap(_.location)
|
||||||
.flatMap(_.location)
|
.flatMap(locationEnd =>
|
||||||
.flatMap(
|
Some(locationStart.copy(end = locationEnd.end))
|
||||||
locationEnd =>
|
)
|
||||||
Some(locationStart.copy(end = locationEnd.end))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
(pathStr, loc)
|
(pathStr, loc)
|
||||||
@ -264,12 +262,11 @@ object AstToIR {
|
|||||||
Literal.Text(fullString, getIdentifiedLocation(literal))
|
Literal.Text(fullString, getIdentifiedLocation(literal))
|
||||||
case AST.Literal.Text.Block.Raw(lines, _, _) =>
|
case AST.Literal.Text.Block.Raw(lines, _, _) =>
|
||||||
val fullString = lines
|
val fullString = lines
|
||||||
.map(
|
.map(t =>
|
||||||
t =>
|
t.text.collect {
|
||||||
t.text.collect {
|
case AST.Literal.Text.Segment.Plain(str) => str
|
||||||
case AST.Literal.Text.Segment.Plain(str) => str
|
case AST.Literal.Text.Segment.RawEsc(code) => code.repr
|
||||||
case AST.Literal.Text.Segment.RawEsc(code) => code.repr
|
}.mkString
|
||||||
}.mkString
|
|
||||||
)
|
)
|
||||||
.mkString("\n")
|
.mkString("\n")
|
||||||
|
|
||||||
@ -380,11 +377,6 @@ object AstToIR {
|
|||||||
translateExpression(context),
|
translateExpression(context),
|
||||||
getIdentifiedLocation(callable)
|
getIdentifiedLocation(callable)
|
||||||
)
|
)
|
||||||
case AstView.ForcedTerm(term) =>
|
|
||||||
Application.Force(
|
|
||||||
translateExpression(term),
|
|
||||||
getIdentifiedLocation(callable)
|
|
||||||
)
|
|
||||||
case AstView.Application(name, args) =>
|
case AstView.Application(name, args) =>
|
||||||
val (validArguments, hasDefaultsSuspended) =
|
val (validArguments, hasDefaultsSuspended) =
|
||||||
calculateDefaultsSuspension(args)
|
calculateDefaultsSuspension(args)
|
||||||
|
@ -133,28 +133,6 @@ object AstView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object ForcedTerm {
|
|
||||||
|
|
||||||
/** Matches a forced term.
|
|
||||||
*
|
|
||||||
* A forced term is one of the form `~t`, where `t` is an arbitrary program
|
|
||||||
* expression. This is temporary syntax and will be removed once we have
|
|
||||||
* the ability to insert these analytically.
|
|
||||||
*
|
|
||||||
* @param ast the structure to try and match on
|
|
||||||
* @return the term being forced
|
|
||||||
*/
|
|
||||||
def unapply(ast: AST): Option[AST] = {
|
|
||||||
ast match {
|
|
||||||
case MaybeParensed(
|
|
||||||
AST.App.Section.Right(AST.Ident.Opr("~"), ast)
|
|
||||||
) =>
|
|
||||||
Some(ast)
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ContextAscription {
|
object ContextAscription {
|
||||||
|
|
||||||
/** Matches a usage of the `in` keyword for ascribing a monadic context to
|
/** Matches a usage of the `in` keyword for ascribing a monadic context to
|
||||||
|
@ -719,57 +719,81 @@ class IRToTruffle(
|
|||||||
* `arg`
|
* `arg`
|
||||||
*/
|
*/
|
||||||
def run(arg: IR.CallArgument, position: Int): CallArgument = arg match {
|
def run(arg: IR.CallArgument, position: Int): CallArgument = arg match {
|
||||||
case IR.CallArgument.Specified(name, value, _, _) =>
|
case IR.CallArgument.Specified(name, value, _, shouldBeSuspended, _) =>
|
||||||
val scopeInfo = arg
|
val scopeInfo = arg
|
||||||
.getMetadata[AliasAnalysis.Info.Scope.Child]
|
.getMetadata[AliasAnalysis.Info.Scope.Child]
|
||||||
.getOrElse(
|
.getOrElse(
|
||||||
throw new CompilerError("No scope attached to a call argument.")
|
throw new CompilerError("No scope attached to a call argument.")
|
||||||
)
|
)
|
||||||
val result = value match {
|
|
||||||
case term: IR.Application.Force =>
|
|
||||||
val childScope =
|
|
||||||
scope.createChild(scopeInfo.scope, flattenToParent = true)
|
|
||||||
new ExpressionProcessor(childScope, scopeName).run(term.target)
|
|
||||||
case _ =>
|
|
||||||
val childScope = scope.createChild(scopeInfo.scope)
|
|
||||||
val argumentExpression =
|
|
||||||
new ExpressionProcessor(childScope, scopeName).run(value)
|
|
||||||
|
|
||||||
val argExpressionIsTail = value
|
val shouldSuspend =
|
||||||
.getMetadata[TailCall.Metadata]
|
shouldBeSuspended.getOrElse(
|
||||||
.getOrElse(
|
throw new CompilerError(
|
||||||
throw new CompilerError(
|
"Demand analysis information missing from call argument."
|
||||||
"Argument with missing tail call information."
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
argumentExpression.setTail(argExpressionIsTail)
|
val childScope = if (shouldSuspend) {
|
||||||
|
scope.createChild(scopeInfo.scope)
|
||||||
|
} else {
|
||||||
|
// Note [Scope Flattening]
|
||||||
|
scope.createChild(scopeInfo.scope, flattenToParent = true)
|
||||||
|
}
|
||||||
|
val argumentExpression =
|
||||||
|
new ExpressionProcessor(childScope, scopeName).run(value)
|
||||||
|
|
||||||
val displayName =
|
val result = if (!shouldSuspend) {
|
||||||
s"call_argument<${name.getOrElse(String.valueOf(position))}>"
|
argumentExpression
|
||||||
|
} else {
|
||||||
val section = value.location
|
val argExpressionIsTail = value
|
||||||
.map(loc => source.createSection(loc.start, loc.end))
|
.getMetadata[TailCall.Metadata]
|
||||||
.orNull
|
.getOrElse(
|
||||||
|
throw new CompilerError(
|
||||||
val callTarget = Truffle.getRuntime.createCallTarget(
|
"Argument with missing tail call information."
|
||||||
ClosureRootNode.build(
|
|
||||||
language,
|
|
||||||
childScope,
|
|
||||||
moduleScope,
|
|
||||||
argumentExpression,
|
|
||||||
section,
|
|
||||||
displayName
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
CreateThunkNode.build(callTarget)
|
argumentExpression.setTail(argExpressionIsTail)
|
||||||
|
|
||||||
|
val displayName =
|
||||||
|
s"call_argument<${name.getOrElse(String.valueOf(position))}>"
|
||||||
|
|
||||||
|
val section = value.location
|
||||||
|
.map(loc => source.createSection(loc.start, loc.end))
|
||||||
|
.orNull
|
||||||
|
|
||||||
|
val callTarget = Truffle.getRuntime.createCallTarget(
|
||||||
|
ClosureRootNode.build(
|
||||||
|
language,
|
||||||
|
childScope,
|
||||||
|
moduleScope,
|
||||||
|
argumentExpression,
|
||||||
|
section,
|
||||||
|
displayName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
CreateThunkNode.build(callTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
new CallArgument(name.map(_.name).orNull, result)
|
new CallArgument(name.map(_.name).orNull, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Note [Scope Flattening]
|
||||||
|
* ~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
* Given that we represent _all_ function arguments as thunks at runtime, we
|
||||||
|
* account for this during alias analysis by allocating new scopes for the
|
||||||
|
* function arguments as they are passed. However, in the case of an argument
|
||||||
|
* that is _already_ suspended, we want to pass this directly. However, we do
|
||||||
|
* not have demand information at the point of alias analysis, and so we have
|
||||||
|
* allocated a new scope for it regardless.
|
||||||
|
*
|
||||||
|
* As a result, we flatten that scope back into the parent during codegen to
|
||||||
|
* work around the differences between the semantic meaning of the language
|
||||||
|
* and the runtime representation of function arguments.
|
||||||
|
*/
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// === Definition Argument Processor ========================================
|
// === Definition Argument Processor ========================================
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.enso.compiler.core
|
package org.enso.compiler.core
|
||||||
|
|
||||||
import org.enso.compiler.core.IR.{Expression, IdentifiedLocation}
|
import org.enso.compiler.core.IR.{Expression, IdentifiedLocation}
|
||||||
|
import org.enso.compiler.exception.CompilerError
|
||||||
import org.enso.syntax.text.ast.Doc
|
import org.enso.syntax.text.ast.Doc
|
||||||
import org.enso.syntax.text.{AST, Debug, Location}
|
import org.enso.syntax.text.{AST, Debug, Location}
|
||||||
|
|
||||||
@ -41,6 +42,18 @@ sealed trait IR {
|
|||||||
this.passData.collectFirst { case data: T => data }
|
this.passData.collectFirst { case data: T => data }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO [AA] Use this throughout the passes
|
||||||
|
/** Gets the metadata of the given type from the node, throwing a fatal
|
||||||
|
* compiler error with the specified message if it doesn't exist
|
||||||
|
*
|
||||||
|
* @param message the message to throw on error
|
||||||
|
* @tparam T the type of the metadata to be obtained
|
||||||
|
* @return the requested metadata
|
||||||
|
*/
|
||||||
|
def unsafeGetMetadata[T <: IR.Metadata : ClassTag](message: String): T = {
|
||||||
|
this.getMetadata[T].getOrElse(throw new CompilerError(message))
|
||||||
|
}
|
||||||
|
|
||||||
/** The source location that the node corresponds to. */
|
/** The source location that the node corresponds to. */
|
||||||
val location: Option[IdentifiedLocation]
|
val location: Option[IdentifiedLocation]
|
||||||
|
|
||||||
@ -161,7 +174,7 @@ object IR {
|
|||||||
* module scope
|
* module scope
|
||||||
*/
|
*/
|
||||||
sealed trait Scope extends IR {
|
sealed trait Scope extends IR {
|
||||||
override def addMetadata(newData: Metadata): Scope
|
override def addMetadata(newData: Metadata): Scope
|
||||||
override def mapExpressions(fn: Expression => Expression): Scope
|
override def mapExpressions(fn: Expression => Expression): Scope
|
||||||
}
|
}
|
||||||
object Scope {
|
object Scope {
|
||||||
@ -187,7 +200,7 @@ object IR {
|
|||||||
|
|
||||||
/** A representation of top-level definitions. */
|
/** A representation of top-level definitions. */
|
||||||
sealed trait Definition extends Scope {
|
sealed trait Definition extends Scope {
|
||||||
override def addMetadata(newData: Metadata): Definition
|
override def addMetadata(newData: Metadata): Definition
|
||||||
override def mapExpressions(fn: Expression => Expression): Definition
|
override def mapExpressions(fn: Expression => Expression): Definition
|
||||||
}
|
}
|
||||||
object Definition {
|
object Definition {
|
||||||
@ -274,7 +287,7 @@ object IR {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def mapExpressions(fn: Expression => Expression): Expression
|
override def mapExpressions(fn: Expression => Expression): Expression
|
||||||
override def addMetadata(newData: Metadata): Expression
|
override def addMetadata(newData: Metadata): Expression
|
||||||
}
|
}
|
||||||
object Expression {
|
object Expression {
|
||||||
|
|
||||||
@ -336,7 +349,7 @@ object IR {
|
|||||||
/** Enso literals. */
|
/** Enso literals. */
|
||||||
sealed trait Literal extends Expression with IRKind.Primitive {
|
sealed trait Literal extends Expression with IRKind.Primitive {
|
||||||
override def mapExpressions(fn: Expression => Expression): Literal
|
override def mapExpressions(fn: Expression => Expression): Literal
|
||||||
override def addMetadata(newData: Metadata): Literal
|
override def addMetadata(newData: Metadata): Literal
|
||||||
}
|
}
|
||||||
object Literal {
|
object Literal {
|
||||||
|
|
||||||
@ -384,7 +397,7 @@ object IR {
|
|||||||
val name: String
|
val name: String
|
||||||
|
|
||||||
override def mapExpressions(fn: Expression => Expression): Name
|
override def mapExpressions(fn: Expression => Expression): Name
|
||||||
override def addMetadata(newData: Metadata): Name
|
override def addMetadata(newData: Metadata): Name
|
||||||
}
|
}
|
||||||
object Name {
|
object Name {
|
||||||
|
|
||||||
@ -449,7 +462,7 @@ object IR {
|
|||||||
/** Constructs that operate on types. */
|
/** Constructs that operate on types. */
|
||||||
sealed trait Type extends Expression {
|
sealed trait Type extends Expression {
|
||||||
override def mapExpressions(fn: Expression => Expression): Type
|
override def mapExpressions(fn: Expression => Expression): Type
|
||||||
override def addMetadata(newData: Metadata): Type
|
override def addMetadata(newData: Metadata): Type
|
||||||
}
|
}
|
||||||
object Type {
|
object Type {
|
||||||
|
|
||||||
@ -514,7 +527,7 @@ object IR {
|
|||||||
/** IR nodes for dealing with typesets. */
|
/** IR nodes for dealing with typesets. */
|
||||||
sealed trait Set extends Type {
|
sealed trait Set extends Type {
|
||||||
override def mapExpressions(fn: Expression => Expression): Set
|
override def mapExpressions(fn: Expression => Expression): Set
|
||||||
override def addMetadata(newData: Metadata): Set
|
override def addMetadata(newData: Metadata): Set
|
||||||
}
|
}
|
||||||
object Set {
|
object Set {
|
||||||
|
|
||||||
@ -737,7 +750,7 @@ object IR {
|
|||||||
val canBeTCO: Boolean
|
val canBeTCO: Boolean
|
||||||
|
|
||||||
override def mapExpressions(fn: Expression => Expression): Function
|
override def mapExpressions(fn: Expression => Expression): Function
|
||||||
override def addMetadata(newData: Metadata): Function
|
override def addMetadata(newData: Metadata): Function
|
||||||
}
|
}
|
||||||
object Function {
|
object Function {
|
||||||
|
|
||||||
@ -822,7 +835,7 @@ object IR {
|
|||||||
/** All function applications in Enso. */
|
/** All function applications in Enso. */
|
||||||
sealed trait Application extends Expression {
|
sealed trait Application extends Expression {
|
||||||
override def mapExpressions(fn: Expression => Expression): Application
|
override def mapExpressions(fn: Expression => Expression): Application
|
||||||
override def addMetadata(newData: Metadata): Application
|
override def addMetadata(newData: Metadata): Application
|
||||||
}
|
}
|
||||||
object Application {
|
object Application {
|
||||||
|
|
||||||
@ -876,7 +889,7 @@ object IR {
|
|||||||
/** Operator applications in Enso. */
|
/** Operator applications in Enso. */
|
||||||
sealed trait Operator extends Application {
|
sealed trait Operator extends Application {
|
||||||
override def mapExpressions(fn: Expression => Expression): Operator
|
override def mapExpressions(fn: Expression => Expression): Operator
|
||||||
override def addMetadata(newData: Metadata): Operator
|
override def addMetadata(newData: Metadata): Operator
|
||||||
}
|
}
|
||||||
object Operator {
|
object Operator {
|
||||||
|
|
||||||
@ -917,8 +930,17 @@ object IR {
|
|||||||
/** The name of the argument, if present. */
|
/** The name of the argument, if present. */
|
||||||
val name: Option[IR.Name]
|
val name: Option[IR.Name]
|
||||||
|
|
||||||
|
/** Whether or not the argument should be suspended at code generation time.
|
||||||
|
*
|
||||||
|
* A value of `Some(true)` implies that code generation should suspend the
|
||||||
|
* argument. A value of `Some(false)` implies that code generation should
|
||||||
|
* _not_ suspend the argument. A value of [[None]] implies that the
|
||||||
|
* information is missing.
|
||||||
|
*/
|
||||||
|
val shouldBeSuspended: Option[Boolean]
|
||||||
|
|
||||||
override def mapExpressions(fn: Expression => Expression): CallArgument
|
override def mapExpressions(fn: Expression => Expression): CallArgument
|
||||||
override def addMetadata(newData: Metadata): CallArgument
|
override def addMetadata(newData: Metadata): CallArgument
|
||||||
}
|
}
|
||||||
object CallArgument {
|
object CallArgument {
|
||||||
|
|
||||||
@ -927,13 +949,16 @@ object IR {
|
|||||||
* @param name the name of the argument being called, if present
|
* @param name the name of the argument being called, if present
|
||||||
* @param value the expression being passed as the argument's value
|
* @param value the expression being passed as the argument's value
|
||||||
* @param location the source location that the node corresponds to
|
* @param location the source location that the node corresponds to
|
||||||
|
* @param shouldBeSuspended whether or not the argument should be passed
|
||||||
|
* suspended
|
||||||
* @param passData the pass metadata associated with this node
|
* @param passData the pass metadata associated with this node
|
||||||
*/
|
*/
|
||||||
sealed case class Specified(
|
sealed case class Specified(
|
||||||
override val name: Option[IR.Name],
|
override val name: Option[IR.Name],
|
||||||
value: Expression,
|
value: Expression,
|
||||||
override val location: Option[IdentifiedLocation],
|
override val location: Option[IdentifiedLocation],
|
||||||
override val passData: ISet[Metadata] = ISet()
|
override val shouldBeSuspended: Option[Boolean] = None,
|
||||||
|
override val passData: ISet[Metadata] = ISet()
|
||||||
) extends CallArgument
|
) extends CallArgument
|
||||||
with IRKind.Primitive {
|
with IRKind.Primitive {
|
||||||
override def addMetadata(newData: Metadata): Specified = {
|
override def addMetadata(newData: Metadata): Specified = {
|
||||||
@ -954,7 +979,7 @@ object IR {
|
|||||||
/** The Enso case expression. */
|
/** The Enso case expression. */
|
||||||
sealed trait Case extends Expression {
|
sealed trait Case extends Expression {
|
||||||
override def mapExpressions(fn: Expression => Expression): Case
|
override def mapExpressions(fn: Expression => Expression): Case
|
||||||
override def addMetadata(newData: Metadata): Case
|
override def addMetadata(newData: Metadata): Case
|
||||||
}
|
}
|
||||||
object Case {
|
object Case {
|
||||||
|
|
||||||
@ -1013,7 +1038,7 @@ object IR {
|
|||||||
/** The different types of patterns that can occur in a match. */
|
/** The different types of patterns that can occur in a match. */
|
||||||
sealed trait Pattern extends IR {
|
sealed trait Pattern extends IR {
|
||||||
override def mapExpressions(fn: Expression => Expression): Pattern
|
override def mapExpressions(fn: Expression => Expression): Pattern
|
||||||
override def addMetadata(newData: Metadata): Pattern
|
override def addMetadata(newData: Metadata): Pattern
|
||||||
}
|
}
|
||||||
object Pattern {
|
object Pattern {
|
||||||
// TODO [AA] Better differentiate the types of patterns that can occur
|
// TODO [AA] Better differentiate the types of patterns that can occur
|
||||||
@ -1025,7 +1050,7 @@ object IR {
|
|||||||
/** Enso comment entities. */
|
/** Enso comment entities. */
|
||||||
sealed trait Comment extends Expression {
|
sealed trait Comment extends Expression {
|
||||||
override def mapExpressions(fn: Expression => Expression): Comment
|
override def mapExpressions(fn: Expression => Expression): Comment
|
||||||
override def addMetadata(newData: Metadata): Comment
|
override def addMetadata(newData: Metadata): Comment
|
||||||
|
|
||||||
/** The expression being commented. */
|
/** The expression being commented. */
|
||||||
val commented: Expression
|
val commented: Expression
|
||||||
@ -1063,7 +1088,7 @@ object IR {
|
|||||||
/** Foreign code entities. */
|
/** Foreign code entities. */
|
||||||
sealed trait Foreign extends Expression {
|
sealed trait Foreign extends Expression {
|
||||||
override def mapExpressions(fn: Expression => Expression): Foreign
|
override def mapExpressions(fn: Expression => Expression): Foreign
|
||||||
override def addMetadata(newData: Metadata): Foreign
|
override def addMetadata(newData: Metadata): Foreign
|
||||||
}
|
}
|
||||||
object Foreign {
|
object Foreign {
|
||||||
|
|
||||||
@ -1095,7 +1120,7 @@ object IR {
|
|||||||
/** A trait for all errors in Enso's IR. */
|
/** A trait for all errors in Enso's IR. */
|
||||||
sealed trait Error extends Expression {
|
sealed trait Error extends Expression {
|
||||||
override def mapExpressions(fn: Expression => Expression): Error
|
override def mapExpressions(fn: Expression => Expression): Error
|
||||||
override def addMetadata(newData: Metadata): Error
|
override def addMetadata(newData: Metadata): Error
|
||||||
}
|
}
|
||||||
object Error {
|
object Error {
|
||||||
|
|
||||||
|
@ -151,13 +151,12 @@ case object AliasAnalysis extends IRPass {
|
|||||||
|
|
||||||
block
|
block
|
||||||
.copy(
|
.copy(
|
||||||
expressions = expressions.map(
|
expressions = expressions.map((expression: IR.Expression) =>
|
||||||
(expression: IR.Expression) =>
|
analyseExpression(
|
||||||
analyseExpression(
|
expression,
|
||||||
expression,
|
graph,
|
||||||
graph,
|
currentScope
|
||||||
currentScope
|
)
|
||||||
)
|
|
||||||
),
|
),
|
||||||
returnValue = analyseExpression(
|
returnValue = analyseExpression(
|
||||||
retVal,
|
retVal,
|
||||||
@ -168,8 +167,10 @@ case object AliasAnalysis extends IRPass {
|
|||||||
.addMetadata(Info.Scope.Child(graph, currentScope))
|
.addMetadata(Info.Scope.Child(graph, currentScope))
|
||||||
case binding @ IR.Expression.Binding(name, expression, _, _) =>
|
case binding @ IR.Expression.Binding(name, expression, _, _) =>
|
||||||
if (!parentScope.hasSymbolOccurrenceAs[Occurrence.Def](name.name)) {
|
if (!parentScope.hasSymbolOccurrenceAs[Occurrence.Def](name.name)) {
|
||||||
|
val isSuspended = expression.isInstanceOf[IR.Expression.Block]
|
||||||
val occurrenceId = graph.nextId()
|
val occurrenceId = graph.nextId()
|
||||||
val occurrence = Occurrence.Def(occurrenceId, name.name)
|
val occurrence =
|
||||||
|
Occurrence.Def(occurrenceId, name.name, isSuspended)
|
||||||
|
|
||||||
parentScope.add(occurrence)
|
parentScope.add(occurrence)
|
||||||
|
|
||||||
@ -188,13 +189,12 @@ case object AliasAnalysis extends IRPass {
|
|||||||
case app: IR.Application =>
|
case app: IR.Application =>
|
||||||
analyseApplication(app, graph, parentScope)
|
analyseApplication(app, graph, parentScope)
|
||||||
case x =>
|
case x =>
|
||||||
x.mapExpressions(
|
x.mapExpressions((expression: IR.Expression) =>
|
||||||
(expression: IR.Expression) =>
|
analyseExpression(
|
||||||
analyseExpression(
|
expression,
|
||||||
expression,
|
graph,
|
||||||
graph,
|
parentScope
|
||||||
parentScope
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,22 +225,21 @@ case object AliasAnalysis extends IRPass {
|
|||||||
scope: Scope
|
scope: Scope
|
||||||
): List[IR.DefinitionArgument] = {
|
): List[IR.DefinitionArgument] = {
|
||||||
args.map {
|
args.map {
|
||||||
case arg @ IR.DefinitionArgument.Specified(name, value, _, _, _) =>
|
case arg @ IR.DefinitionArgument.Specified(name, value, isSusp, _, _) =>
|
||||||
val nameOccursInScope =
|
val nameOccursInScope =
|
||||||
scope.hasSymbolOccurrenceAs[Occurrence.Def](name.name)
|
scope.hasSymbolOccurrenceAs[Occurrence.Def](name.name)
|
||||||
if (!nameOccursInScope) {
|
if (!nameOccursInScope) {
|
||||||
val occurrenceId = graph.nextId()
|
val occurrenceId = graph.nextId()
|
||||||
scope.add(Graph.Occurrence.Def(occurrenceId, name.name))
|
scope.add(Graph.Occurrence.Def(occurrenceId, name.name, isSusp))
|
||||||
|
|
||||||
arg
|
arg
|
||||||
.copy(
|
.copy(
|
||||||
defaultValue = value.map(
|
defaultValue = value.map((ir: IR.Expression) =>
|
||||||
(ir: IR.Expression) =>
|
analyseExpression(
|
||||||
analyseExpression(
|
ir,
|
||||||
ir,
|
graph,
|
||||||
graph,
|
scope
|
||||||
scope
|
)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.addMetadata(Info.Occurrence(graph, occurrenceId))
|
.addMetadata(Info.Occurrence(graph, occurrenceId))
|
||||||
@ -292,7 +291,7 @@ case object AliasAnalysis extends IRPass {
|
|||||||
parentScope: AliasAnalysis.Graph.Scope
|
parentScope: AliasAnalysis.Graph.Scope
|
||||||
): List[IR.CallArgument] = {
|
): List[IR.CallArgument] = {
|
||||||
args.map {
|
args.map {
|
||||||
case arg @ IR.CallArgument.Specified(_, expr, _, _) =>
|
case arg @ IR.CallArgument.Specified(_, expr, _, _, _) =>
|
||||||
val currentScope = expr match {
|
val currentScope = expr match {
|
||||||
case _: IR.Literal => parentScope
|
case _: IR.Literal => parentScope
|
||||||
case _ => parentScope.addChild()
|
case _ => parentScope.addChild()
|
||||||
@ -375,13 +374,12 @@ case object AliasAnalysis extends IRPass {
|
|||||||
case caseExpr @ IR.Case.Expr(scrutinee, branches, fallback, _, _) =>
|
case caseExpr @ IR.Case.Expr(scrutinee, branches, fallback, _, _) =>
|
||||||
caseExpr.copy(
|
caseExpr.copy(
|
||||||
scrutinee = analyseExpression(scrutinee, graph, parentScope),
|
scrutinee = analyseExpression(scrutinee, graph, parentScope),
|
||||||
branches = branches.map(
|
branches = branches.map(branch =>
|
||||||
branch =>
|
branch.copy(
|
||||||
branch.copy(
|
pattern = analyseExpression(branch.pattern, graph, parentScope),
|
||||||
pattern = analyseExpression(branch.pattern, graph, parentScope),
|
expression =
|
||||||
expression =
|
analyseExpression(branch.expression, graph, parentScope)
|
||||||
analyseExpression(branch.expression, graph, parentScope)
|
)
|
||||||
)
|
|
||||||
),
|
),
|
||||||
fallback = fallback.map(analyseExpression(_, graph, parentScope))
|
fallback = fallback.map(analyseExpression(_, graph, parentScope))
|
||||||
) //.addMetadata(Info.Scope.Child(graph, currentScope))
|
) //.addMetadata(Info.Scope.Child(graph, currentScope))
|
||||||
@ -495,8 +493,8 @@ case object AliasAnalysis extends IRPass {
|
|||||||
): Set[Graph.Link] = {
|
): Set[Graph.Link] = {
|
||||||
val idsForSym = rootScope.symbolToIds[T](symbol)
|
val idsForSym = rootScope.symbolToIds[T](symbol)
|
||||||
|
|
||||||
links.filter(
|
links.filter(l =>
|
||||||
l => idsForSym.contains(l.source) || idsForSym.contains(l.target)
|
idsForSym.contains(l.source) || idsForSym.contains(l.target)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,8 +516,8 @@ case object AliasAnalysis extends IRPass {
|
|||||||
linksFor(id).find { edge =>
|
linksFor(id).find { edge =>
|
||||||
val occ = getOccurrence(edge.target)
|
val occ = getOccurrence(edge.target)
|
||||||
occ match {
|
occ match {
|
||||||
case Some(Occurrence.Def(_, _)) => true
|
case Some(Occurrence.Def(_, _, _)) => true
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -730,8 +728,8 @@ case object AliasAnalysis extends IRPass {
|
|||||||
parentCounter: Int = 0
|
parentCounter: Int = 0
|
||||||
): Option[Graph.Link] = {
|
): Option[Graph.Link] = {
|
||||||
val definition = occurrences.find {
|
val definition = occurrences.find {
|
||||||
case Graph.Occurrence.Def(_, n) => n == occurrence.symbol
|
case Graph.Occurrence.Def(_, n, _) => n == occurrence.symbol
|
||||||
case _ => false
|
case _ => false
|
||||||
}
|
}
|
||||||
|
|
||||||
definition match {
|
definition match {
|
||||||
@ -900,7 +898,11 @@ case object AliasAnalysis extends IRPass {
|
|||||||
* @param id the identifier of the name in the graph
|
* @param id the identifier of the name in the graph
|
||||||
* @param symbol the text of the name
|
* @param symbol the text of the name
|
||||||
*/
|
*/
|
||||||
sealed case class Def(id: Id, symbol: Graph.Symbol) extends Occurrence
|
sealed case class Def(
|
||||||
|
id: Id,
|
||||||
|
symbol: Graph.Symbol,
|
||||||
|
isLazy: Boolean = false
|
||||||
|
) extends Occurrence
|
||||||
|
|
||||||
/** A usage of a symbol in the aliasing graph
|
/** A usage of a symbol in the aliasing graph
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,345 @@
|
|||||||
|
package org.enso.compiler.pass.analyse
|
||||||
|
|
||||||
|
import org.enso.compiler.InlineContext
|
||||||
|
import org.enso.compiler.core.IR
|
||||||
|
import org.enso.compiler.exception.CompilerError
|
||||||
|
import org.enso.compiler.pass.IRPass
|
||||||
|
|
||||||
|
/** This pass implements demand analysis for Enso.
|
||||||
|
*
|
||||||
|
* Demand analysis is the process of determining _when_ a suspended term needs
|
||||||
|
* to be forced (where the suspended value is _demanded_). It does the
|
||||||
|
* following:
|
||||||
|
*
|
||||||
|
* This pass needs to be run after [[AliasAnalysis]], and also assumes that
|
||||||
|
* all members of [[IR.IRKind.Primitive]] have been removed from the IR by the
|
||||||
|
* time that it runs.
|
||||||
|
*/
|
||||||
|
case object DemandAnalysis extends IRPass {
|
||||||
|
override type Metadata = IR.Metadata.Empty
|
||||||
|
|
||||||
|
/** Executes the demand analysis process on an Enso module.
|
||||||
|
*
|
||||||
|
* @param ir the Enso IR to process
|
||||||
|
* @return `ir`, transformed to correctly force terms
|
||||||
|
*/
|
||||||
|
override def runModule(ir: IR.Module): IR.Module = {
|
||||||
|
ir.copy(bindings =
|
||||||
|
ir.bindings.map(t => t.mapExpressions(runExpression(_, InlineContext())))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Executes the demand analysis process on an Enso expression.
|
||||||
|
*
|
||||||
|
* @param expression the Enso IR to process
|
||||||
|
* @param inlineContext a context object that contains the information needed
|
||||||
|
* for inline evaluation
|
||||||
|
* @return `ir`, transformed to correctly force terms
|
||||||
|
*/
|
||||||
|
override def runExpression(
|
||||||
|
expression: IR.Expression,
|
||||||
|
inlineContext: InlineContext
|
||||||
|
): IR.Expression =
|
||||||
|
analyseExpression(
|
||||||
|
expression,
|
||||||
|
isInsideApplication = false,
|
||||||
|
isInsideCallArgument = false
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Performs demand analysis on an arbitrary program expression.
|
||||||
|
*
|
||||||
|
* @param expression the expression to perform demand analysis on
|
||||||
|
* @param isInsideApplication whether the current expression occurs _inside_
|
||||||
|
* an application (note that this should not be
|
||||||
|
* set for the application itself)
|
||||||
|
* @param isInsideCallArgument whether the current expression occurs _inside_
|
||||||
|
* a call argument (note that this should not be
|
||||||
|
* set for the call argument itself)
|
||||||
|
* @return `expression`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseExpression(
|
||||||
|
expression: IR.Expression,
|
||||||
|
isInsideApplication: Boolean,
|
||||||
|
isInsideCallArgument: Boolean
|
||||||
|
): IR.Expression = {
|
||||||
|
expression match {
|
||||||
|
case fn: IR.Function => analyseFunction(fn, isInsideApplication)
|
||||||
|
case name: IR.Name => analyseName(name, isInsideCallArgument)
|
||||||
|
case app: IR.Application =>
|
||||||
|
analyseApplication(app, isInsideApplication, isInsideCallArgument)
|
||||||
|
case typ: IR.Type =>
|
||||||
|
analyseType(typ, isInsideApplication, isInsideCallArgument)
|
||||||
|
case cse: IR.Case =>
|
||||||
|
analyseCase(cse, isInsideApplication, isInsideCallArgument)
|
||||||
|
case block @ IR.Expression.Block(expressions, retVal, _, _, _) =>
|
||||||
|
block.copy(
|
||||||
|
expressions = expressions.map(x =>
|
||||||
|
analyseExpression(x, isInsideApplication, isInsideCallArgument)
|
||||||
|
),
|
||||||
|
returnValue =
|
||||||
|
analyseExpression(retVal, isInsideApplication, isInsideCallArgument)
|
||||||
|
)
|
||||||
|
case binding @ IR.Expression.Binding(_, expression, _, _) =>
|
||||||
|
binding.copy(expression =
|
||||||
|
analyseExpression(
|
||||||
|
expression,
|
||||||
|
isInsideApplication,
|
||||||
|
isInsideCallArgument = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case lit: IR.Literal => lit
|
||||||
|
case err: IR.Error => err
|
||||||
|
case foreign: IR.Foreign => foreign
|
||||||
|
case comment: IR.Comment =>
|
||||||
|
comment.mapExpressions(x =>
|
||||||
|
analyseExpression(
|
||||||
|
x,
|
||||||
|
isInsideApplication = false,
|
||||||
|
isInsideCallArgument
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs demand analysis for a function.
|
||||||
|
*
|
||||||
|
* @param function the function to perform demand analysis on
|
||||||
|
* @param isInsideApplication whether or not the function occurs inside an
|
||||||
|
* application
|
||||||
|
* @return `function`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseFunction(
|
||||||
|
function: IR.Function,
|
||||||
|
isInsideApplication: Boolean
|
||||||
|
): IR.Function = function match {
|
||||||
|
case lam @ IR.Function.Lambda(args, body, _, _, _) =>
|
||||||
|
lam.copy(
|
||||||
|
arguments = args.map(analyseDefinitionArgument),
|
||||||
|
body = analyseExpression(
|
||||||
|
body,
|
||||||
|
isInsideApplication,
|
||||||
|
isInsideCallArgument = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs demand analysis for a name.
|
||||||
|
*
|
||||||
|
* If the name refers to a term that is suspended, this name is forced unless
|
||||||
|
* it is being passed to a function. If the name is being passed to a function
|
||||||
|
* it is passed raw.
|
||||||
|
*
|
||||||
|
* @param name the name to perform demand analysis on.
|
||||||
|
* @param isInsideCallArgument whether or not the name occurs inside a call
|
||||||
|
* call argument
|
||||||
|
* @return `name`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseName(
|
||||||
|
name: IR.Name,
|
||||||
|
isInsideCallArgument: Boolean
|
||||||
|
): IR.Expression = {
|
||||||
|
val usesLazyTerm = isUsageOfSuspendedTerm(name)
|
||||||
|
|
||||||
|
if (isInsideCallArgument) {
|
||||||
|
name
|
||||||
|
} else {
|
||||||
|
if (usesLazyTerm) {
|
||||||
|
val forceLocation = name.location
|
||||||
|
val newNameLocation = name.location.map(l => l.copy(id = None))
|
||||||
|
|
||||||
|
val newName = name match {
|
||||||
|
case lit: IR.Name.Literal => lit.copy(location = newNameLocation)
|
||||||
|
case ths: IR.Name.This => ths.copy(location = newNameLocation)
|
||||||
|
case here: IR.Name.Here => here.copy(location = newNameLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
IR.Application.Force(newName, forceLocation)
|
||||||
|
} else {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs demand analysis on an application.
|
||||||
|
*
|
||||||
|
* @param application the function application to perform demand analysis on
|
||||||
|
* @param isInsideApplication whether or not the application is occuring
|
||||||
|
* inside another application
|
||||||
|
* @param isInsideCallArgument whether or not the application is occurring
|
||||||
|
* inside a call argument
|
||||||
|
* @return `application`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseApplication(
|
||||||
|
application: IR.Application,
|
||||||
|
isInsideApplication: Boolean,
|
||||||
|
isInsideCallArgument: Boolean
|
||||||
|
): IR.Application = application match {
|
||||||
|
case pref @ IR.Application.Prefix(fn, args, _, _, _) =>
|
||||||
|
pref.copy(
|
||||||
|
function = analyseExpression(
|
||||||
|
fn,
|
||||||
|
isInsideApplication = true,
|
||||||
|
isInsideCallArgument = false
|
||||||
|
),
|
||||||
|
arguments = args.map(analyseCallArgument)
|
||||||
|
)
|
||||||
|
case force @ IR.Application.Force(target, _, _) =>
|
||||||
|
force.copy(target =
|
||||||
|
analyseExpression(
|
||||||
|
target,
|
||||||
|
isInsideApplication,
|
||||||
|
isInsideCallArgument
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
throw new CompilerError(
|
||||||
|
"Unexpected application type during demand analysis."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Determines whether a particular piece of IR represents the usage of a
|
||||||
|
* suspended term (and hence requires forcing).
|
||||||
|
*
|
||||||
|
* @param expr the expression to check
|
||||||
|
* @return `true` if `expr` represents the usage of a suspended term, `false`
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
def isUsageOfSuspendedTerm(expr: IR.Expression): Boolean = {
|
||||||
|
expr match {
|
||||||
|
case name: IR.Name =>
|
||||||
|
val aliasInfo = name.unsafeGetMetadata[AliasAnalysis.Info.Occurrence](
|
||||||
|
"Missing alias occurrence information for a name usage"
|
||||||
|
)
|
||||||
|
|
||||||
|
aliasInfo.graph
|
||||||
|
.defLinkFor(aliasInfo.id)
|
||||||
|
.flatMap(link => {
|
||||||
|
aliasInfo.graph
|
||||||
|
.getOccurrence(link.target)
|
||||||
|
.getOrElse(
|
||||||
|
throw new CompilerError(
|
||||||
|
s"Malformed aliasing link with target ${link.target}"
|
||||||
|
)
|
||||||
|
) match {
|
||||||
|
case AliasAnalysis.Graph.Occurrence.Def(_, _, isLazy) =>
|
||||||
|
if (isLazy) Some(true) else None
|
||||||
|
case _ => None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.isDefined
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs demand analysis on a function call argument.
|
||||||
|
*
|
||||||
|
* In keeping with the requirement by the runtime to pass all function
|
||||||
|
* arguments as thunks, we mark the argument as needing suspension based on
|
||||||
|
* whether it already is a thunk or not.
|
||||||
|
*
|
||||||
|
* @param arg the argument to perform demand analysis on
|
||||||
|
* @return `arg`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseCallArgument(arg: IR.CallArgument): IR.CallArgument = {
|
||||||
|
arg match {
|
||||||
|
case spec @ IR.CallArgument.Specified(_, expr, _, _, _) =>
|
||||||
|
spec.copy(
|
||||||
|
value = analyseExpression(
|
||||||
|
expr,
|
||||||
|
isInsideApplication = true,
|
||||||
|
isInsideCallArgument = true
|
||||||
|
),
|
||||||
|
shouldBeSuspended = Some(!isUsageOfSuspendedTerm(expr))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs demand analysis on a function definition argument.
|
||||||
|
*
|
||||||
|
* @param arg the argument to perform demand analysis on
|
||||||
|
* @return `arg`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseDefinitionArgument(
|
||||||
|
arg: IR.DefinitionArgument
|
||||||
|
): IR.DefinitionArgument = {
|
||||||
|
arg match {
|
||||||
|
case spec @ IR.DefinitionArgument.Specified(_, default, _, _, _) =>
|
||||||
|
spec.copy(
|
||||||
|
defaultValue = default.map(x =>
|
||||||
|
analyseExpression(
|
||||||
|
x,
|
||||||
|
isInsideApplication = false,
|
||||||
|
isInsideCallArgument = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case redef: IR.Error.Redefined.Argument => redef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs demand analysis on a typing expression.
|
||||||
|
*
|
||||||
|
* @param typ the expression to perform demand analysis on
|
||||||
|
* @param isInsideApplication whether the typing expression occurs inside a
|
||||||
|
* function application
|
||||||
|
* @param isInsideCallArgument whether the typing expression occurs inside a
|
||||||
|
* function call argument
|
||||||
|
* @return `typ`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseType(
|
||||||
|
typ: IR.Type,
|
||||||
|
isInsideApplication: Boolean,
|
||||||
|
isInsideCallArgument: Boolean
|
||||||
|
): IR.Type =
|
||||||
|
typ.mapExpressions(x =>
|
||||||
|
analyseExpression(x, isInsideApplication, isInsideCallArgument)
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Performs demand analysis on a case expression.
|
||||||
|
*
|
||||||
|
* @param cse the case expression to perform demand analysis on
|
||||||
|
* @param isInsideApplication whether the case expression occurs inside a
|
||||||
|
* function application
|
||||||
|
* @param isInsideCallArgument whether the case expression occurs inside a
|
||||||
|
* function call argument
|
||||||
|
* @return `cse`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseCase(
|
||||||
|
cse: IR.Case,
|
||||||
|
isInsideApplication: Boolean,
|
||||||
|
isInsideCallArgument: Boolean
|
||||||
|
): IR.Case = cse match {
|
||||||
|
case expr @ IR.Case.Expr(scrutinee, branches, fallback, _, _) =>
|
||||||
|
expr.copy(
|
||||||
|
scrutinee = analyseExpression(
|
||||||
|
scrutinee,
|
||||||
|
isInsideApplication,
|
||||||
|
isInsideCallArgument
|
||||||
|
),
|
||||||
|
branches = branches.map(b => analyseCaseBranch(b)),
|
||||||
|
fallback = fallback.map(x =>
|
||||||
|
analyseExpression(
|
||||||
|
x,
|
||||||
|
isInsideApplication = false,
|
||||||
|
isInsideCallArgument = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
case _ => throw new CompilerError("Unexpected case construct.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Performs demand analysis on a case branch.
|
||||||
|
*
|
||||||
|
* @param branch the case branch to perform demand analysis on
|
||||||
|
* @return `branch`, transformed by the demand analysis process
|
||||||
|
*/
|
||||||
|
def analyseCaseBranch(branch: IR.Case.Branch): IR.Case.Branch = {
|
||||||
|
branch.copy(
|
||||||
|
expression = analyseExpression(
|
||||||
|
branch.expression,
|
||||||
|
isInsideApplication = false,
|
||||||
|
isInsideCallArgument = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -189,7 +189,7 @@ case object TailCall extends IRPass {
|
|||||||
*/
|
*/
|
||||||
def analyseCallArg(argument: IR.CallArgument): IR.CallArgument = {
|
def analyseCallArg(argument: IR.CallArgument): IR.CallArgument = {
|
||||||
argument match {
|
argument match {
|
||||||
case arg @ IR.CallArgument.Specified(_, expr, _, _) =>
|
case arg @ IR.CallArgument.Specified(_, expr, _, _, _) =>
|
||||||
arg
|
arg
|
||||||
.copy(
|
.copy(
|
||||||
// Note [Call Argument Tail Position]
|
// Note [Call Argument Tail Position]
|
||||||
|
@ -96,18 +96,50 @@ trait CompilerRunner {
|
|||||||
inlineContext: InlineContext
|
inlineContext: InlineContext
|
||||||
): IR = ir match {
|
): IR = ir match {
|
||||||
case expr: IR.Expression =>
|
case expr: IR.Expression =>
|
||||||
passes.foldLeft(expr)(
|
passes.foldLeft(expr)((intermediate, pass) =>
|
||||||
(intermediate, pass) =>
|
pass.runExpression(intermediate, inlineContext)
|
||||||
pass.runExpression(intermediate, inlineContext)
|
|
||||||
)
|
)
|
||||||
case mod: IR.Module =>
|
case mod: IR.Module =>
|
||||||
passes.foldLeft(mod)(
|
passes.foldLeft(mod)((intermediate, pass) =>
|
||||||
(intermediate, pass) => pass.runModule(intermediate)
|
pass.runModule(intermediate)
|
||||||
)
|
)
|
||||||
case _ => throw new RuntimeException(s"Cannot run passes on $ir.")
|
case _ => throw new RuntimeException(s"Cannot run passes on $ir.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Adds an extension method to preprocess the source as IR.
|
||||||
|
*
|
||||||
|
* @param source the source code to preprocess
|
||||||
|
*/
|
||||||
|
implicit class Preprocess(source: String)(
|
||||||
|
implicit precursorPasses: List[IRPass]
|
||||||
|
) {
|
||||||
|
|
||||||
|
/** Translates the source code into appropriate IR for testing this pass.
|
||||||
|
*
|
||||||
|
* @return IR appropriate for testing the alias analysis pass as a module
|
||||||
|
*/
|
||||||
|
def preprocessModule: IR.Module = {
|
||||||
|
source.toIrModule
|
||||||
|
.runPasses(precursorPasses, InlineContext())
|
||||||
|
.asInstanceOf[IR.Module]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Translates the source code into appropriate IR for testing this pass
|
||||||
|
*
|
||||||
|
* @return IR appropriate for testing the alias analysis pass as an
|
||||||
|
* expression
|
||||||
|
*/
|
||||||
|
def preprocessExpression(
|
||||||
|
implicit inlineContext: InlineContext
|
||||||
|
): Option[IR.Expression] = {
|
||||||
|
source.toIrExpression.map(
|
||||||
|
_.runPasses(precursorPasses, inlineContext)
|
||||||
|
.asInstanceOf[IR.Expression]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// === IR Testing Utils =====================================================
|
// === IR Testing Utils =====================================================
|
||||||
|
|
||||||
/** A variety of extension methods on IR expressions to aid testing.
|
/** A variety of extension methods on IR expressions to aid testing.
|
||||||
|
@ -18,41 +18,12 @@ class AliasAnalysisTest extends CompilerTest {
|
|||||||
|
|
||||||
// === Utilities ============================================================
|
// === Utilities ============================================================
|
||||||
|
|
||||||
/** Adds an extension method to preprocess the source as IR.
|
/** The passes that need to be run before the alias analysis pass. */
|
||||||
*
|
implicit val precursorPasses: List[IRPass] = List(
|
||||||
* @param source the source code to preprocess
|
GenerateMethodBodies,
|
||||||
*/
|
LiftSpecialOperators,
|
||||||
implicit class Preprocess(source: String) {
|
OperatorToFunction
|
||||||
val precursorPasses: List[IRPass] = List(
|
)
|
||||||
GenerateMethodBodies,
|
|
||||||
LiftSpecialOperators,
|
|
||||||
OperatorToFunction
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Translates the source code into appropriate IR for testing this pass.
|
|
||||||
*
|
|
||||||
* @return IR appropriate for testing the alias analysis pass as a module
|
|
||||||
*/
|
|
||||||
def preprocessModule: IR.Module = {
|
|
||||||
source.toIrModule
|
|
||||||
.runPasses(precursorPasses, InlineContext())
|
|
||||||
.asInstanceOf[IR.Module]
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Translates the source code into appropriate IR for testing this pass
|
|
||||||
*
|
|
||||||
* @return IR appropriate for testing the alias analysis pass as an
|
|
||||||
* expression
|
|
||||||
*/
|
|
||||||
def preprocessExpression(
|
|
||||||
inlineContext: InlineContext
|
|
||||||
): Option[IR.Expression] = {
|
|
||||||
source.toIrExpression.map(
|
|
||||||
_.runPasses(precursorPasses, inlineContext)
|
|
||||||
.asInstanceOf[IR.Expression]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Adds an extension method to run alias analysis on an [[IR.Module]].
|
/** Adds an extension method to run alias analysis on an [[IR.Module]].
|
||||||
*
|
*
|
||||||
@ -69,7 +40,7 @@ class AliasAnalysisTest extends CompilerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Adds an extension method to run alias analusis on an [[IR.Expression]].
|
/** Adds an extension method to run alias analysis on an [[IR.Expression]].
|
||||||
*
|
*
|
||||||
* @param ir the expression to run alias analysis on
|
* @param ir the expression to run alias analysis on
|
||||||
*/
|
*/
|
||||||
@ -77,6 +48,8 @@ class AliasAnalysisTest extends CompilerTest {
|
|||||||
|
|
||||||
/** Runs alias analysis on an expression.
|
/** Runs alias analysis on an expression.
|
||||||
*
|
*
|
||||||
|
* @param inlineContext the inline context in which to process the
|
||||||
|
* expression
|
||||||
* @return [[ir]], with attached aliasing information
|
* @return [[ir]], with attached aliasing information
|
||||||
*/
|
*/
|
||||||
def analyse(inlineContext: InlineContext): IR.Expression = {
|
def analyse(inlineContext: InlineContext): IR.Expression = {
|
||||||
@ -515,8 +488,8 @@ class AliasAnalysisTest extends CompilerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"assign Info.Occurrence to definitions and usages of symbols" in {
|
"assign Info.Occurrence to definitions and usages of symbols" in {
|
||||||
topLambda.arguments.foreach(
|
topLambda.arguments.foreach(arg =>
|
||||||
arg => arg.getMetadata[Info.Occurrence] shouldBe defined
|
arg.getMetadata[Info.Occurrence] shouldBe defined
|
||||||
)
|
)
|
||||||
|
|
||||||
topLambdaBody.expressions.foreach(
|
topLambdaBody.expressions.foreach(
|
||||||
@ -524,8 +497,8 @@ class AliasAnalysisTest extends CompilerTest {
|
|||||||
.getMetadata[Info.Occurrence] shouldBe defined
|
.getMetadata[Info.Occurrence] shouldBe defined
|
||||||
)
|
)
|
||||||
|
|
||||||
childLambda.arguments.foreach(
|
childLambda.arguments.foreach(arg =>
|
||||||
arg => arg.getMetadata[Info.Occurrence] shouldBe defined
|
arg.getMetadata[Info.Occurrence] shouldBe defined
|
||||||
)
|
)
|
||||||
|
|
||||||
childLambdaBody.function.getMetadata[Info.Occurrence] shouldBe defined
|
childLambdaBody.function.getMetadata[Info.Occurrence] shouldBe defined
|
||||||
|
@ -0,0 +1,277 @@
|
|||||||
|
package org.enso.compiler.test.pass.analyse
|
||||||
|
|
||||||
|
import org.enso.compiler.InlineContext
|
||||||
|
import org.enso.compiler.core.IR
|
||||||
|
import org.enso.compiler.pass.IRPass
|
||||||
|
import org.enso.compiler.pass.analyse.{AliasAnalysis, DemandAnalysis}
|
||||||
|
import org.enso.compiler.pass.desugar.{
|
||||||
|
GenerateMethodBodies,
|
||||||
|
LiftSpecialOperators,
|
||||||
|
OperatorToFunction
|
||||||
|
}
|
||||||
|
import org.enso.compiler.test.CompilerTest
|
||||||
|
import org.enso.interpreter.runtime.scope.LocalScope
|
||||||
|
|
||||||
|
class DemandAnalysisTest extends CompilerTest {
|
||||||
|
|
||||||
|
// === Test Setup ===========================================================
|
||||||
|
|
||||||
|
/** The passes that must be run before the demand analysis pass. */
|
||||||
|
implicit val precursorPasses: List[IRPass] = List(
|
||||||
|
GenerateMethodBodies,
|
||||||
|
LiftSpecialOperators,
|
||||||
|
OperatorToFunction,
|
||||||
|
AliasAnalysis
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Adds an extension method to run alias analysis on an [[IR.Module]].
|
||||||
|
*
|
||||||
|
* @param ir the module to run alias analysis on
|
||||||
|
*/
|
||||||
|
implicit class AnalyseModule(ir: IR.Module) {
|
||||||
|
|
||||||
|
/** Runs demand analysis on a module.
|
||||||
|
*
|
||||||
|
* @return [[ir]], transformed by the demand analysis pass
|
||||||
|
*/
|
||||||
|
def analyse: IR.Module = {
|
||||||
|
DemandAnalysis.runModule(ir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds an extension method to run alias analysis on an [[IR.Expression]].
|
||||||
|
*
|
||||||
|
* @param ir the expression to run alias analysis on
|
||||||
|
*/
|
||||||
|
implicit class AnalyseExpression(ir: IR.Expression) {
|
||||||
|
|
||||||
|
/** Runs demand analysis on an expression.
|
||||||
|
*
|
||||||
|
* @param inlineContext the inline context in which to process the
|
||||||
|
* expression
|
||||||
|
* @return [[ir]], transformed by the demand analysis pass
|
||||||
|
*/
|
||||||
|
def analyse(implicit inlineContext: InlineContext): IR.Expression = {
|
||||||
|
DemandAnalysis.runExpression(ir, inlineContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === The Tests ============================================================
|
||||||
|
|
||||||
|
"Suspended arguments" should {
|
||||||
|
"be forced when assigned" in {
|
||||||
|
implicit val ctx: InlineContext =
|
||||||
|
InlineContext(localScope = Some(LocalScope.root))
|
||||||
|
|
||||||
|
val ir =
|
||||||
|
"""
|
||||||
|
|~x ~y z ->
|
||||||
|
| a = x
|
||||||
|
| z
|
||||||
|
|""".stripMargin.preprocessExpression.get.analyse
|
||||||
|
|
||||||
|
val boundX = ir
|
||||||
|
.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Expression.Block]
|
||||||
|
.expressions
|
||||||
|
.head
|
||||||
|
.asInstanceOf[IR.Expression.Binding]
|
||||||
|
.expression
|
||||||
|
|
||||||
|
boundX shouldBe an[IR.Application.Force]
|
||||||
|
boundX.asInstanceOf[IR.Application.Force].target shouldBe an[IR.Name]
|
||||||
|
}
|
||||||
|
|
||||||
|
"work correctly when deeply nested" in {
|
||||||
|
implicit val ctx: InlineContext =
|
||||||
|
InlineContext(localScope = Some(LocalScope.root))
|
||||||
|
|
||||||
|
val ir =
|
||||||
|
"""
|
||||||
|
|~x -> b -> a -> x
|
||||||
|
|""".stripMargin.preprocessExpression.get.analyse
|
||||||
|
|
||||||
|
val xUsage =
|
||||||
|
ir.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
|
||||||
|
xUsage shouldBe an[IR.Application.Force]
|
||||||
|
xUsage.asInstanceOf[IR.Application.Force].target shouldBe an[IR.Name]
|
||||||
|
}
|
||||||
|
|
||||||
|
"not be forced when passed to functions" in {
|
||||||
|
implicit val ctx: InlineContext =
|
||||||
|
InlineContext(localScope = Some(LocalScope.root))
|
||||||
|
|
||||||
|
val ir =
|
||||||
|
"""
|
||||||
|
|~x ~y z -> foo x y z
|
||||||
|
|""".stripMargin.preprocessExpression.get.analyse
|
||||||
|
|
||||||
|
val app = ir
|
||||||
|
.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Application.Prefix]
|
||||||
|
|
||||||
|
app.arguments.head
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
.value shouldBe an[IR.Name]
|
||||||
|
|
||||||
|
app
|
||||||
|
.arguments(1)
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
.value shouldBe an[IR.Name]
|
||||||
|
}
|
||||||
|
|
||||||
|
"be marked as not to suspend during codegen when passed to a function" in {
|
||||||
|
implicit val ctx: InlineContext =
|
||||||
|
InlineContext(localScope = Some(LocalScope.root))
|
||||||
|
|
||||||
|
val ir =
|
||||||
|
"""
|
||||||
|
|~x ~y z -> foo x y z
|
||||||
|
|""".stripMargin.preprocessExpression.get.analyse
|
||||||
|
|
||||||
|
val app = ir
|
||||||
|
.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Application.Prefix]
|
||||||
|
|
||||||
|
app.arguments.head
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
.shouldBeSuspended shouldEqual Some(false)
|
||||||
|
|
||||||
|
app
|
||||||
|
.arguments(1)
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
.shouldBeSuspended shouldEqual Some(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"Non-suspended arguments" should {
|
||||||
|
implicit val ctx: InlineContext =
|
||||||
|
InlineContext(localScope = Some(LocalScope.root))
|
||||||
|
|
||||||
|
val ir =
|
||||||
|
"""x y ->
|
||||||
|
| a = x
|
||||||
|
| foo x a
|
||||||
|
|""".stripMargin.preprocessExpression.get.analyse
|
||||||
|
|
||||||
|
val body = ir
|
||||||
|
.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Expression.Block]
|
||||||
|
|
||||||
|
"be left alone by demand analysis" in {
|
||||||
|
body.expressions.head
|
||||||
|
.asInstanceOf[IR.Expression.Binding]
|
||||||
|
.expression shouldBe an[IR.Name]
|
||||||
|
|
||||||
|
body.returnValue
|
||||||
|
.asInstanceOf[IR.Application.Prefix]
|
||||||
|
.arguments
|
||||||
|
.head
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
.value shouldBe an[IR.Name]
|
||||||
|
}
|
||||||
|
|
||||||
|
"be marked for suspension during codegen when passed to a function" in {
|
||||||
|
val xArg = body.returnValue
|
||||||
|
.asInstanceOf[IR.Application.Prefix]
|
||||||
|
.arguments
|
||||||
|
.head
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
|
||||||
|
val aArg = body.returnValue
|
||||||
|
.asInstanceOf[IR.Application.Prefix]
|
||||||
|
.arguments(1)
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
|
||||||
|
xArg.value shouldBe an[IR.Name]
|
||||||
|
xArg.shouldBeSuspended shouldEqual Some(true)
|
||||||
|
|
||||||
|
aArg.value shouldBe an[IR.Name]
|
||||||
|
aArg.shouldBeSuspended shouldEqual Some(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"Suspended blocks" should {
|
||||||
|
"be forced when used" in {
|
||||||
|
implicit val ctx: InlineContext =
|
||||||
|
InlineContext(localScope = Some(LocalScope.root))
|
||||||
|
|
||||||
|
val ir =
|
||||||
|
"""
|
||||||
|
|x ->
|
||||||
|
| blck =
|
||||||
|
| foo a b
|
||||||
|
| test = blck
|
||||||
|
| blck
|
||||||
|
|""".stripMargin.preprocessExpression.get.analyse
|
||||||
|
|
||||||
|
val irBody = ir
|
||||||
|
.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Expression.Block]
|
||||||
|
|
||||||
|
irBody
|
||||||
|
.expressions(1)
|
||||||
|
.asInstanceOf[IR.Expression.Binding]
|
||||||
|
.expression shouldBe an[IR.Application.Force]
|
||||||
|
|
||||||
|
irBody.returnValue shouldBe an[IR.Application.Force]
|
||||||
|
}
|
||||||
|
|
||||||
|
"not be forced when passed to a function" in {
|
||||||
|
implicit val ctx: InlineContext =
|
||||||
|
InlineContext(localScope = Some(LocalScope.root))
|
||||||
|
|
||||||
|
val ir =
|
||||||
|
"""
|
||||||
|
|x ->
|
||||||
|
| blck =
|
||||||
|
| foo a b
|
||||||
|
| bar blck
|
||||||
|
|""".stripMargin.preprocessExpression.get.analyse
|
||||||
|
|
||||||
|
ir.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Expression.Block]
|
||||||
|
.returnValue
|
||||||
|
.asInstanceOf[IR.Application.Prefix]
|
||||||
|
.arguments
|
||||||
|
.head
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
.value shouldBe an[IR.Name]
|
||||||
|
}
|
||||||
|
|
||||||
|
"be marked as not to suspend during codegen when passed to a function" in {
|
||||||
|
implicit val ctx: InlineContext =
|
||||||
|
InlineContext(localScope = Some(LocalScope.root))
|
||||||
|
|
||||||
|
val ir =
|
||||||
|
"""
|
||||||
|
|x ->
|
||||||
|
| blck =
|
||||||
|
| foo a b
|
||||||
|
| bar blck
|
||||||
|
|""".stripMargin.preprocessExpression.get.analyse
|
||||||
|
|
||||||
|
ir.asInstanceOf[IR.Function.Lambda]
|
||||||
|
.body
|
||||||
|
.asInstanceOf[IR.Expression.Block]
|
||||||
|
.returnValue
|
||||||
|
.asInstanceOf[IR.Application.Prefix]
|
||||||
|
.arguments
|
||||||
|
.head
|
||||||
|
.asInstanceOf[IR.CallArgument.Specified]
|
||||||
|
.shouldBeSuspended shouldEqual Some(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -157,11 +157,11 @@ class CodeLocationsTest extends InterpreterTest {
|
|||||||
val code =
|
val code =
|
||||||
"""
|
"""
|
||||||
|main =
|
|main =
|
||||||
| bar = a ~b ~c -> ~b
|
| bar = a ~b ~c -> b
|
||||||
|
|
|
|
||||||
| bar 0 10 0
|
| bar 0 10 0
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
instrumenter.assertNodeExists(29, 2, classOf[ForceNode])
|
instrumenter.assertNodeExists(29, 1, classOf[ForceNode])
|
||||||
eval(code)
|
eval(code)
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,6 @@ class ExpressionIdTest extends InterpreterTest {
|
|||||||
instrumenter.assertNodeExists(id3, "45")
|
instrumenter.assertNodeExists(id3, "45")
|
||||||
|
|
||||||
eval(meta.appendToCode(code))
|
eval(meta.appendToCode(code))
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Ids" should "be correct with parenthesized expressions" in
|
"Ids" should "be correct with parenthesized expressions" in
|
||||||
@ -29,7 +28,6 @@ class ExpressionIdTest extends InterpreterTest {
|
|||||||
instrumenter.assertNodeExists(id1, "940")
|
instrumenter.assertNodeExists(id1, "940")
|
||||||
instrumenter.assertNodeExists(id2, "47")
|
instrumenter.assertNodeExists(id2, "47")
|
||||||
eval(meta.appendToCode(code))
|
eval(meta.appendToCode(code))
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Ids" should "be correct in applications and method calls" in
|
"Ids" should "be correct in applications and method calls" in
|
||||||
@ -42,7 +40,6 @@ class ExpressionIdTest extends InterpreterTest {
|
|||||||
instrumenter.assertNodeExists(id1, "Cons 5 6")
|
instrumenter.assertNodeExists(id1, "Cons 5 6")
|
||||||
instrumenter.assertNodeExists(id2, "Cons 5 6")
|
instrumenter.assertNodeExists(id2, "Cons 5 6")
|
||||||
eval(meta.appendToCode(code))
|
eval(meta.appendToCode(code))
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Ids" should "be correct for deeply nested functions" in
|
"Ids" should "be correct for deeply nested functions" in
|
||||||
@ -69,7 +66,6 @@ class ExpressionIdTest extends InterpreterTest {
|
|||||||
instrumenter.assertNodeExistsTail(id3)
|
instrumenter.assertNodeExistsTail(id3)
|
||||||
instrumenter.assertNodeExistsTail(id4)
|
instrumenter.assertNodeExistsTail(id4)
|
||||||
eval(meta.appendToCode(code))
|
eval(meta.appendToCode(code))
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Ids" should "be correct inside pattern matches" in
|
"Ids" should "be correct inside pattern matches" in
|
||||||
@ -102,7 +98,6 @@ class ExpressionIdTest extends InterpreterTest {
|
|||||||
instrumenter.assertNodeExists(id3, "Unit")
|
instrumenter.assertNodeExists(id3, "Unit")
|
||||||
instrumenter.assertNodeExists(id4, "25")
|
instrumenter.assertNodeExists(id4, "25")
|
||||||
eval(meta.appendToCode(code))
|
eval(meta.appendToCode(code))
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Ids" should "be correct for defaulted arguments" in
|
"Ids" should "be correct for defaulted arguments" in
|
||||||
@ -121,7 +116,6 @@ class ExpressionIdTest extends InterpreterTest {
|
|||||||
instrumenter.assertNodeExists(id1, "12")
|
instrumenter.assertNodeExists(id1, "12")
|
||||||
instrumenter.assertNodeExists(id2, "3")
|
instrumenter.assertNodeExists(id2, "3")
|
||||||
eval(meta.appendToCode(code))
|
eval(meta.appendToCode(code))
|
||||||
()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"Ids" should "be correct for lazy arguments" in
|
"Ids" should "be correct for lazy arguments" in
|
||||||
@ -129,15 +123,14 @@ class ExpressionIdTest extends InterpreterTest {
|
|||||||
val code =
|
val code =
|
||||||
"""
|
"""
|
||||||
|main =
|
|main =
|
||||||
| bar = a ~b ~c -> ~b
|
| bar = a ~b ~c -> b
|
||||||
|
|
|
|
||||||
| bar 0 10 0
|
| bar 0 10 0
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
val meta = new Metadata
|
val meta = new Metadata
|
||||||
val id = meta.addItem(29, 2)
|
val id = meta.addItem(29, 1)
|
||||||
|
|
||||||
instrumenter.assertNodeExists(id, "10")
|
instrumenter.assertNodeExists(id, "10")
|
||||||
eval(meta.appendToCode(code))
|
eval(meta.appendToCode(code))
|
||||||
()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ class GlobalScopeTest extends InterpreterTest {
|
|||||||
| 0
|
| 0
|
||||||
|
|
|
|
||||||
| IO.println 5
|
| IO.println 5
|
||||||
| ~myFun
|
| myFun
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
|
|
||||||
eval(code) shouldEqual 0
|
eval(code) shouldEqual 0
|
||||||
@ -123,7 +123,7 @@ class GlobalScopeTest extends InterpreterTest {
|
|||||||
|
|
|
|
||||||
| State.put 5
|
| State.put 5
|
||||||
| IO.println State.get
|
| IO.println State.get
|
||||||
| ~block
|
| block
|
||||||
| IO.println State.get
|
| IO.println State.get
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class GroupingTest extends InterpreterTest {
|
|||||||
val code =
|
val code =
|
||||||
"""
|
"""
|
||||||
|main =
|
|main =
|
||||||
| ifTest = c (~ifT) ~ifF -> ifZero c ~ifT (~ifF)
|
| ifTest = c (~ifT) ~ifF -> ifZero c ifT ifF
|
||||||
| sum = c acc -> ifTest c acc (sum c-1 acc+c)
|
| sum = c acc -> ifTest c acc (sum c-1 acc+c)
|
||||||
| sum 10000 0
|
| sum 10000 0
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
|
@ -9,7 +9,7 @@ class LazyArgumentsTest extends InterpreterTest {
|
|||||||
val code =
|
val code =
|
||||||
"""
|
"""
|
||||||
|main =
|
|main =
|
||||||
| lazyId = ~x -> ~x
|
| lazyId = ~x -> x
|
||||||
| lazyId (1 + 1)
|
| lazyId (1 + 1)
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ class LazyArgumentsTest extends InterpreterTest {
|
|||||||
val code =
|
val code =
|
||||||
"""
|
"""
|
||||||
|main =
|
|main =
|
||||||
| foo = i ~x ~y -> ifZero i ~x ~y
|
| foo = i ~x ~y -> ifZero i x y
|
||||||
| foo 1 (IO.println 1) (IO.println 2)
|
| foo 1 (IO.println 1) (IO.println 2)
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
eval(code)
|
eval(code)
|
||||||
@ -31,7 +31,7 @@ class LazyArgumentsTest extends InterpreterTest {
|
|||||||
val code =
|
val code =
|
||||||
"""
|
"""
|
||||||
|main =
|
|main =
|
||||||
| ifTest = c ~ifT ~ifF -> ifZero c ~ifT ~ifF
|
| ifTest = c ~ifT ~ifF -> ifZero c ifT ifF
|
||||||
| sum = c acc -> ifTest c acc (sum c-1 acc+c)
|
| sum = c acc -> ifTest c acc (sum c-1 acc+c)
|
||||||
| sum 10000 0
|
| sum 10000 0
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
@ -42,7 +42,7 @@ class LazyArgumentsTest extends InterpreterTest {
|
|||||||
val code =
|
val code =
|
||||||
"""
|
"""
|
||||||
|main =
|
|main =
|
||||||
| suspInc = ~x -> 1 + ~x
|
| suspInc = ~x -> 1 + x
|
||||||
| suspInc (suspInc 10)
|
| suspInc (suspInc 10)
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ class LazyArgumentsTest extends InterpreterTest {
|
|||||||
val code =
|
val code =
|
||||||
"""
|
"""
|
||||||
|main =
|
|main =
|
||||||
| ifTest = c ~ifT ~ifF -> ifZero c ~ifT ~ifF
|
| ifTest = c ~ifT ~ifF -> ifZero c ifT ifF
|
||||||
| foo = c -> ifTest c
|
| foo = c -> ifTest c
|
||||||
|
|
|
|
||||||
| foo 0 (IO.println 1) (IO.println 2)
|
| foo 0 (IO.println 1) (IO.println 2)
|
||||||
@ -88,4 +88,14 @@ class LazyArgumentsTest extends InterpreterTest {
|
|||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
eval(code).call(1) shouldEqual 1
|
eval(code).call(1) shouldEqual 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
subject should "allow passing suspended functions" in {
|
||||||
|
val code =
|
||||||
|
"""main =
|
||||||
|
| foo = ~x -> x 1
|
||||||
|
| foo (x -> x)
|
||||||
|
|""".stripMargin
|
||||||
|
|
||||||
|
eval(code) shouldEqual 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ class StateTest extends InterpreterTest {
|
|||||||
| State.put 0
|
| State.put 0
|
||||||
| res
|
| res
|
||||||
|
|
|
|
||||||
| res2 = State.run 10 ~myBlock
|
| res2 = State.run 10 myBlock
|
||||||
| state = State.get
|
| state = State.get
|
||||||
| res2 + state
|
| res2 + state
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
@ -108,7 +108,7 @@ class StateTest extends InterpreterTest {
|
|||||||
| Panic.throw Unit
|
| Panic.throw Unit
|
||||||
|
|
|
|
||||||
| State.put 5
|
| State.put 5
|
||||||
| Panic.recover ~panicker
|
| Panic.recover panicker
|
||||||
| State.get
|
| State.get
|
||||||
|""".stripMargin
|
|""".stripMargin
|
||||||
eval(code) shouldEqual 5
|
eval(code) shouldEqual 5
|
||||||
|
Loading…
Reference in New Issue
Block a user