Removal of useless ApplicationSaturation phase (#8181)

This commit is contained in:
Jaroslav Tulach 2023-10-31 06:20:29 +01:00 committed by GitHub
parent 646b47b246
commit 3d23c6a8d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 13 additions and 648 deletions

View File

@ -11,7 +11,6 @@ import org.enso.compiler.pass.lint.{
UnusedBindings
}
import org.enso.compiler.pass.optimise.{
ApplicationSaturation,
LambdaConsolidate,
UnreachableMatchBranches
}
@ -114,8 +113,7 @@ class Passes(
/** Configuration for the passes. */
private val passConfig: PassConfiguration = PassConfiguration(
ApplicationSaturation -->> ApplicationSaturation.Configuration(),
AliasAnalysis -->> AliasAnalysis.Configuration()
AliasAnalysis -->> AliasAnalysis.Configuration()
)
/** The pass manager for running compiler passes. */

View File

@ -26,10 +26,7 @@ import org.enso.compiler.pass.analyse.{
TailCall
}
import org.enso.compiler.pass.lint.UnusedBindings
import org.enso.compiler.pass.optimise.{
ApplicationSaturation,
LambdaConsolidate
}
import org.enso.compiler.pass.optimise.{LambdaConsolidate}
import org.enso.compiler.pass.resolve.{
DocumentationComments,
IgnoredBindings,
@ -58,7 +55,6 @@ case object ComplexType extends IRPass {
override lazy val invalidatedPasses: Seq[IRPass] =
List(
AliasAnalysis,
ApplicationSaturation,
DataflowAnalysis,
DemandAnalysis,
FunctionBinding,

View File

@ -21,10 +21,7 @@ import org.enso.compiler.pass.analyse.{
TailCall
}
import org.enso.compiler.pass.lint.UnusedBindings
import org.enso.compiler.pass.optimise.{
ApplicationSaturation,
LambdaConsolidate
}
import org.enso.compiler.pass.optimise.{LambdaConsolidate}
import org.enso.compiler.pass.resolve.{
DocumentationComments,
IgnoredBindings,
@ -53,7 +50,6 @@ case object LambdaShorthandToLambda extends IRPass {
)
override lazy val invalidatedPasses: Seq[IRPass] = List(
AliasAnalysis,
ApplicationSaturation,
DataflowAnalysis,
DemandAnalysis,
IgnoredBindings,

View File

@ -1,278 +0,0 @@
package org.enso.compiler.pass.optimise
import org.enso.compiler.context.{InlineContext, ModuleContext}
import org.enso.compiler.core.Implicits.AsMetadata
import org.enso.compiler.core.ir.{Expression, Module, Name}
import org.enso.compiler.core.ir.MetadataStorage._
import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ir.expression.Application
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.desugar._
import org.enso.interpreter.node.{ExpressionNode => RuntimeExpression}
import org.enso.interpreter.runtime.callable.argument.CallArgument
import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope}
/** This optimisation pass recognises fully-saturated applications of known
* functions and writes analysis data that allows optimisation of them to
* specific nodes at codegen time.
*
* This pass requires the context to provide:
*
* - A [[org.enso.compiler.pass.PassConfiguration]] containing an instance of
* [[ApplicationSaturation.Configuration]].
*/
case object ApplicationSaturation extends IRPass {
/** Information on the saturation state of a function. */
override type Metadata = CallSaturation
override type Config = Configuration
override lazy val precursorPasses: Seq[IRPass] = List(
AliasAnalysis,
ComplexType,
FunctionBinding,
GenerateMethodBodies,
LambdaConsolidate,
LambdaShorthandToLambda,
NestedPatternMatch,
OperatorToFunction,
SectionsToBinOp
)
override lazy val invalidatedPasses: Seq[IRPass] = List()
/** Executes the analysis pass, marking functions with information about their
* argument saturation.
*
* @param ir the Enso IR to process
* @param moduleContext a context object that contains the information needed
* to process a module
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
override def runModule(
ir: Module,
moduleContext: ModuleContext
): Module = {
val passConfig = moduleContext.passConfiguration
ir.mapExpressions(
runExpression(
_,
new InlineContext(
moduleContext,
passConfiguration = passConfig,
compilerConfig = moduleContext.compilerConfig
)
)
)
}
/** Executes the analysis pass, marking functions with information about their
* argument saturation.
*
* @param ir the Enso IR to process
* @return `ir`, possibly having made transformations or annotations to that
* IR.
*/
//noinspection DuplicatedCode
override def runExpression(
ir: Expression,
inlineContext: InlineContext
): Expression = {
val knownFunctions =
inlineContext.passConfiguration
.flatMap(configs => configs.get(this))
.getOrElse(
throw new CompilerError("Pass configuration is missing.")
)
.knownFunctions
ir.transformExpressions {
case func @ Application.Prefix(fn, args, _, _, _, _) =>
fn match {
case name: Name =>
val aliasInfo =
name
.unsafeGetMetadata(
AliasAnalysis,
"Name occurrence with missing alias information."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
if (!aliasInfo.graph.linkedToShadowingBinding(aliasInfo.id)) {
knownFunctions.get(name.name) match {
case Some(FunctionSpec(arity, codegenHelper)) =>
if (args.length == arity) {
val argsArePositional = args.forall(arg => arg.name.isEmpty)
// TODO [AA] In future this should work regardless of the
// application style. Needs interpreter changes.
val saturationInfo = if (argsArePositional) {
CallSaturation.Exact(codegenHelper)
} else {
CallSaturation.ExactButByName()
}
func
.copy(
arguments = args.map(
_.mapExpressions((ir: Expression) =>
runExpression(ir, inlineContext)
)
)
)
.updateMetadata(this -->> saturationInfo)
} else if (args.length > arity) {
func
.copy(
arguments = args.map(
_.mapExpressions((ir: Expression) =>
runExpression(ir, inlineContext)
)
)
)
.updateMetadata(
this -->> CallSaturation.Over(args.length - arity)
)
} else {
func
.copy(
arguments = args.map(
_.mapExpressions((ir: Expression) =>
runExpression(ir, inlineContext)
)
)
)
.updateMetadata(
this -->> CallSaturation.Partial(arity - args.length)
)
}
case None =>
func
.copy(
arguments = args.map(
_.mapExpressions((ir: Expression) =>
runExpression(ir, inlineContext)
)
)
)
.updateMetadata(this -->> CallSaturation.Unknown())
}
} else {
func
.copy(
function = runExpression(fn, inlineContext),
arguments =
args.map(_.mapExpressions(runExpression(_, inlineContext)))
)
.updateMetadata(this -->> CallSaturation.Unknown())
}
case _ =>
func
.copy(
function = runExpression(fn, inlineContext),
arguments =
args.map(_.mapExpressions(runExpression(_, inlineContext)))
)
.updateMetadata(this -->> CallSaturation.Unknown())
}
}
}
/** Configuration for this pass
*
* @param knownFunctions the mapping of known functions
*/
sealed case class Configuration(
knownFunctions: KnownFunctionsMapping = Map()
) extends IRPass.Configuration {
override var shouldWriteToContext: Boolean = false
}
/** A function for constructing the optimised node for a function. */
type CodegenHelper =
ModuleScope => LocalScope => List[CallArgument] => RuntimeExpression
/** The configuration for this pass.
*
* The [[String]] is the name of the known function, while the
* [[FunctionSpec]] describes said function.
*/
type KnownFunctionsMapping = Map[String, FunctionSpec]
/** Describes the saturation state of a function application. */
sealed trait CallSaturation extends IRPass.IRMetadata {
override def duplicate(): Option[IRPass.IRMetadata] = Some(this)
}
object CallSaturation {
sealed case class Over(additionalArgCount: Int) extends CallSaturation {
override val metadataName: String =
"ApplicationSaturation.CallSaturation.Over"
/** @inheritdoc */
override def prepareForSerialization(compiler: Compiler): Over = this
/** @inheritdoc */
override def restoreFromSerialization(compiler: Compiler): Option[Over] =
Some(this)
}
sealed case class Exact(helper: CodegenHelper) extends CallSaturation {
override val metadataName: String =
"ApplicationSaturation.CallSaturation.Exact"
/** @inheritdoc */
override def prepareForSerialization(compiler: Compiler): Exact = this
/** @inheritdoc */
override def restoreFromSerialization(
compiler: Compiler
): Option[Exact] = Some(this)
}
sealed case class ExactButByName() extends CallSaturation {
override val metadataName: String =
"ApplicationSaturation.CallSaturation.ExactButByName"
/** @inheritdoc */
override def prepareForSerialization(compiler: Compiler): ExactButByName =
this
/** @inheritdoc */
override def restoreFromSerialization(
compiler: Compiler
): Option[ExactButByName] = Some(this)
}
sealed case class Partial(unappliedArgCount: Int) extends CallSaturation {
override val metadataName: String =
"ApplicationSaturation.CallSaturation.Partial"
/** @inheritdoc */
override def prepareForSerialization(compiler: Compiler): Partial = this
/** @inheritdoc */
override def restoreFromSerialization(
compiler: Compiler
): Option[Partial] = Some(this)
}
sealed case class Unknown() extends CallSaturation {
override val metadataName: String =
"ApplicationSaturation.CallSaturation.Unknown"
/** @inheritdoc */
override def prepareForSerialization(compiler: Compiler): Unknown = this
/** @inheritdoc */
override def restoreFromSerialization(
compiler: Compiler
): Option[Unknown] = Some(this)
}
}
/** A description of a known function
*
* @param arity the number of arguments the function expects
* @param codegenHelper a function that can construct the optimised node to
* represent the function at codegen time.
*/
sealed case class FunctionSpec(arity: Int, codegenHelper: CodegenHelper)
}

View File

@ -51,7 +51,6 @@ import org.enso.compiler.pass.analyse.{
DataflowAnalysis,
TailCall
}
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.resolve.{
ExpressionAnnotations,
GenericAnnotations,
@ -2072,18 +2071,11 @@ class IrToTruffle(
InvokeCallableNode.DefaultsExecutionMode.EXECUTE
}
val appNode = application.getMetadata(ApplicationSaturation) match {
case Some(
ApplicationSaturation.CallSaturation.Exact(createOptimised)
) =>
createOptimised(moduleScope)(scope)(callArgs.toList)
case _ =>
ApplicationNode.build(
this.run(fn, subjectToInstrumentation),
callArgs.toArray,
defaultsExecutionMode
)
}
val appNode = ApplicationNode.build(
this.run(fn, subjectToInstrumentation),
callArgs.toArray,
defaultsExecutionMode
)
setLocation(appNode, loc)
}

View File

@ -16,10 +16,7 @@ import org.enso.compiler.pass.analyse.{
}
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.lint.UnusedBindings
import org.enso.compiler.pass.optimise.{
ApplicationSaturation,
LambdaConsolidate
}
import org.enso.compiler.pass.optimise.{LambdaConsolidate}
import org.enso.compiler.pass.resolve.{IgnoredBindings, OverloadsResolution}
import org.enso.compiler.test.CompilerTest
@ -39,7 +36,6 @@ class PassManagerTest extends CompilerTest {
LambdaConsolidate,
OverloadsResolution,
DemandAnalysis,
ApplicationSaturation,
TailCall,
AliasAnalysis,
DataflowAnalysis,

View File

@ -24,7 +24,6 @@ import org.enso.compiler.pass.analyse.DataflowAnalysis.{
DependencyMapping
}
import org.enso.compiler.pass.analyse.{AliasAnalysis, DataflowAnalysis}
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
import org.enso.interpreter.runtime.scope.LocalScope
@ -44,8 +43,7 @@ class DataflowAnalysisTest extends CompilerTest {
passes.getPrecursors(DataflowAnalysis).get
val passConfig: PassConfiguration = PassConfiguration(
AliasAnalysis -->> AliasAnalysis.Configuration(),
ApplicationSaturation -->> ApplicationSaturation.Configuration()
AliasAnalysis -->> AliasAnalysis.Configuration()
)
implicit val passManager: PassManager =

View File

@ -16,7 +16,6 @@ import org.enso.compiler.core.ir.expression.Case
import org.enso.compiler.pass.PassConfiguration._
import org.enso.compiler.pass.analyse.TailCall.TailPosition
import org.enso.compiler.pass.analyse.{AliasAnalysis, TailCall}
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
import org.enso.interpreter.runtime.scope.LocalScope
@ -49,8 +48,7 @@ class TailCallTest extends CompilerTest {
val precursorPasses: PassGroup = passes.getPrecursors(TailCall).get
val passConfiguration: PassConfiguration = PassConfiguration(
AliasAnalysis -->> AliasAnalysis.Configuration(),
ApplicationSaturation -->> ApplicationSaturation.Configuration()
AliasAnalysis -->> AliasAnalysis.Configuration()
)
implicit val passManager: PassManager =

View File

@ -8,7 +8,6 @@ import org.enso.compiler.core.ir.expression.{warnings, Case}
import org.enso.compiler.pass.PassConfiguration._
import org.enso.compiler.pass.analyse._
import org.enso.compiler.pass.lint.UnusedBindings
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
import org.enso.interpreter.runtime.scope.LocalScope
@ -23,8 +22,7 @@ class UnusedBindingsTest extends CompilerTest with Inside {
val precursorPasses: PassGroup = passes.getPrecursors(UnusedBindings).get
val passConfiguration: PassConfiguration = PassConfiguration(
ApplicationSaturation -->> ApplicationSaturation.Configuration(),
AliasAnalysis -->> AliasAnalysis.Configuration()
AliasAnalysis -->> AliasAnalysis.Configuration()
)
implicit val passManager: PassManager =

View File

@ -1,327 +0,0 @@
package org.enso.compiler.test.pass.optimise
import org.enso.compiler.Passes
import org.enso.compiler.context.FreshNameSupply
import org.enso.compiler.core.Implicits.AsMetadata
import org.enso.compiler.core.ir.{CallArgument, Empty, Expression, Name}
import org.enso.compiler.core.ir.expression.Application
import org.enso.compiler.pass.PassConfiguration._
import org.enso.compiler.pass.analyse.{AliasAnalysis, TailCall}
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.optimise.ApplicationSaturation.{
CallSaturation,
FunctionSpec,
Metadata
}
import org.enso.compiler.pass.{PassConfiguration, PassManager}
import org.enso.compiler.test.CompilerTest
import org.enso.interpreter.node.ExpressionNode
import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope}
import org.enso.interpreter.runtime.callable
import scala.annotation.unused
class ApplicationSaturationTest extends CompilerTest {
// === Utilities ============================================================
/** Generates n arguments that _do not_ contain function applications
* themselves.
*
* @param n the number of arguments to generate
* @param positional whether or not the arguments should be generated by name
* or positionally
* @return a list containing `n` arguments
*/
def genNArgs(n: Int, positional: Boolean = true): List[CallArgument] = {
val name = if (positional) {
None
} else {
Some(Name.Literal("a", isMethod = false, None))
}
List.fill(n)(CallArgument.Specified(name, Empty(None), None))
}
// === Test Setup ===========================================================
// The functions are unused, so left undefined for ease of testing
def dummyFn(@unused mod: ModuleScope)(@unused loc: LocalScope)(
@unused args: List[callable.argument.CallArgument]
): ExpressionNode = ???
val knownFunctions: ApplicationSaturation.Configuration =
ApplicationSaturation.Configuration(
Map(
"+" -> FunctionSpec(2, dummyFn),
"baz" -> FunctionSpec(3, dummyFn),
"foo" -> FunctionSpec(4, dummyFn)
)
)
val passes: Passes = new Passes(defaultConfig)
val precursorPasses = passes.getPrecursors(TailCall).get
val knownPassConfig: PassConfiguration = PassConfiguration(
ApplicationSaturation -->> knownFunctions,
AliasAnalysis -->> AliasAnalysis.Configuration()
)
val passManagerKnown = new PassManager(List(precursorPasses), knownPassConfig)
val localScope: Option[LocalScope] = Some(LocalScope.root)
val knownCtx = buildInlineContext(
localScope = localScope,
freshNameSupply = Some(new FreshNameSupply),
passConfiguration = Some(knownPassConfig)
)
val moduleCtx = buildModuleContext(
passConfiguration = Some(knownPassConfig),
freshNameSupply = Some(new FreshNameSupply)
)
// === The Tests ============================================================
"Known applications" should {
val plusFn = Application
.Prefix(
Name.Literal("+", isMethod = true, None),
genNArgs(2),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
val bazFn = Application
.Prefix(
Name.Literal("baz", isMethod = false, None),
genNArgs(2),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
val fooFn = Application
.Prefix(
Name.Literal("foo", isMethod = false, None),
genNArgs(5),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
val fooFnByName = Application
.Prefix(
Name.Literal("foo", isMethod = false, None),
genNArgs(4, positional = false),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
"be tagged with full saturation where possible" in {
val resultIR =
ApplicationSaturation.runExpression(plusFn, knownCtx)
resultIR.getMetadata(ApplicationSaturation).foreach {
case _: CallSaturation.Exact => succeed
case _ => fail()
}
}
"be tagged with partial saturation where possible" in {
val resultIR =
ApplicationSaturation.runExpression(bazFn, knownCtx)
val expected = Some(CallSaturation.Partial(1))
resultIR.getMetadata(ApplicationSaturation) shouldEqual expected
}
"be tagged with over saturation where possible" in {
val resultIR =
ApplicationSaturation.runExpression(fooFn, knownCtx)
val expected = Some(CallSaturation.Over(1))
resultIR.getMetadata(ApplicationSaturation) shouldEqual expected
}
"be tagged with by name if applied by name" in {
val resultIR =
ApplicationSaturation.runExpression(fooFnByName, knownCtx)
val expected = Some(CallSaturation.ExactButByName())
resultIR.getMetadata(ApplicationSaturation) shouldEqual expected
}
}
"Unknown applications" should {
val unknownFn = Application
.Prefix(
Name.Literal("unknown", isMethod = false, None),
genNArgs(10),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
"be tagged with unknown saturation" in {
val resultIR =
ApplicationSaturation.runExpression(unknownFn, knownCtx)
val expected = Some(CallSaturation.Unknown())
resultIR.getMetadata(ApplicationSaturation) shouldEqual expected
}
}
"Known applications containing known applications" should {
val empty = Empty(None)
val knownPlus = Application
.Prefix(
Name.Literal("+", isMethod = true, None),
genNArgs(2),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
val undersaturatedPlus = Application
.Prefix(
Name.Literal("+", isMethod = true, None),
genNArgs(1),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
val oversaturatedPlus = Application
.Prefix(
Name.Literal("+", isMethod = true, None),
genNArgs(3),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
implicit class InnerMeta(ir: Expression) {
def getInnerMetadata: Option[Metadata] = {
ir.asInstanceOf[Application.Prefix]
.arguments
.head
.asInstanceOf[CallArgument.Specified]
.value
.getMetadata(ApplicationSaturation)
}
}
def outerPlus(argExpr: Expression): Application.Prefix = {
Application
.Prefix(
Name.Literal("+", isMethod = true, None),
List(
CallArgument.Specified(None, argExpr, None),
CallArgument.Specified(None, empty, None)
),
hasDefaultsSuspended = false,
None
)
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Application.Prefix]
}
"have fully saturated applications tagged correctly" in {
val result =
ApplicationSaturation.runExpression(outerPlus(knownPlus), knownCtx)
// The outer should be reported as fully saturated
result.getMetadata(ApplicationSaturation).foreach {
case _: CallSaturation.Exact => succeed
case _ => fail()
}
// The inner should be reported as fully saturated
result.getInnerMetadata.foreach {
case _: CallSaturation.Exact => succeed
case _ => fail()
}
}
"have non-fully saturated applications tagged correctly" in {
val result =
ApplicationSaturation.runExpression(
outerPlus(undersaturatedPlus),
knownCtx
)
val expectedInnerMeta = CallSaturation.Partial(1)
// The outer should be reported as fully saturated
result.getMetadata(ApplicationSaturation).foreach {
case _: CallSaturation.Exact => succeed
case _ => fail()
}
// The inner should be reported as under saturateD
result.getInnerMetadata
.foreach(t => t shouldEqual expectedInnerMeta)
}
"have a mixture of application saturations tagged correctly" in {
val result =
ApplicationSaturation.runExpression(
outerPlus(oversaturatedPlus),
knownCtx
)
val expectedInnerMeta = CallSaturation.Over(1)
// The outer should be reported as fully saturated
result.getMetadata(ApplicationSaturation).foreach {
case _: CallSaturation.Exact => succeed
case _ => fail()
}
// The inner should be reported as under saturateD
result.getInnerMetadata
.foreach(t => t shouldEqual expectedInnerMeta)
}
}
"Shadowed known functions" should {
val rawIR =
"""
|main =
| foo = x -> y -> z -> x + y + z
|
| foo a b c
|""".stripMargin.toIrExpression
val inputIR = rawIR.get
.runPasses(passManagerKnown, knownCtx)
.asInstanceOf[Expression]
val result = ApplicationSaturation
.runExpression(inputIR, knownCtx)
.asInstanceOf[Expression.Binding]
"be tagged as unknown even if their name is known" in {
// Needs alias analysis to work
result.expression
.asInstanceOf[Expression.Block]
.returnValue
.getMetadata(ApplicationSaturation)
.foreach {
case _: CallSaturation.Unknown => succeed
case _ => fail()
}
}
}
}

View File

@ -8,7 +8,6 @@ import org.enso.compiler.core.ir.expression.{errors, Case}
import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.pass.PassConfiguration.ToPair
import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.optimise.ApplicationSaturation
import org.enso.compiler.pass.resolve.Patterns
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
@ -28,8 +27,7 @@ class PatternsTest extends CompilerTest {
passes.getPrecursors(Patterns).get
val passConfiguration: PassConfiguration = PassConfiguration(
AliasAnalysis -->> AliasAnalysis.Configuration(),
ApplicationSaturation -->> ApplicationSaturation.Configuration()
AliasAnalysis -->> AliasAnalysis.Configuration()
)
implicit val passManager: PassManager =