From 233c0468eeb700ffd1395d2a718f830ffc0a9324 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 31 Jul 2024 19:21:53 +0200 Subject: [PATCH 01/39] Add stub of FramePointerAnalysis pass --- .../main/scala/org/enso/compiler/Passes.scala | 1 + .../pass/analyse/FramePointerAnalysis.scala | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala index 39a098b243f3..b80e4c227f16 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala @@ -95,6 +95,7 @@ class Passes( List(PrivateSymbolsAnalysis.INSTANCE) } else List()) ++ List( AliasAnalysis, + FramePointerAnalysis, DataflowAnalysis, CachePreferenceAnalysis, // TODO passes below this line could be separated into a separate group, but it's more complicated - see usages of `functionBodyPasses` diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala new file mode 100644 index 000000000000..f100fec98ceb --- /dev/null +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -0,0 +1,67 @@ +package org.enso.compiler.pass.analyse + +import org.enso.compiler.context.{ + CompilerContext, + FramePointer, + InlineContext, + ModuleContext +} +import org.enso.compiler.core.ir.Module +import org.enso.compiler.core.ir.{Expression, ProcessingPass} +import org.enso.compiler.pass.IRPass +import org.enso.compiler.pass.IRPass.IRMetadata + +/** This pass attaches [[FramePointer]] as metadata to all the IR elements that already + * have [[org.enso.compiler.pass.analyse.alias.Info.Occurrence]] attached. + */ +case object FramePointerAnalysis extends IRPass { + + override type Metadata = FramePointerMeta + + override type Config = IRPass.Configuration.Default + + override val precursorPasses: Seq[IRPass] = { + Seq(AliasAnalysis) + } + + override val invalidatedPasses: Seq[IRPass] = Seq(this) + + override def runModule(ir: Module, moduleContext: ModuleContext): Module = { + ir + } + + /** Not implemented for this pass. + */ + override def runExpression( + ir: Expression, + inlineContext: InlineContext + ): Expression = { + ir + } + + // === Pass Configuration =================================================== + + class FramePointerMeta( + val framePointer: FramePointer + ) extends IRMetadata { + override val metadataName: String = "FramePointer" + + /** @inheritdoc + */ + override def duplicate(): Option[Metadata] = { + Some(new FramePointerMeta(framePointer)) + } + + /** @inheritdoc + */ + override def prepareForSerialization( + compiler: CompilerContext + ): ProcessingPass.Metadata = this + + /** @inheritdoc + */ + override def restoreFromSerialization( + compiler: CompilerContext + ): Option[ProcessingPass.Metadata] = Some(this) + } +} From 71bc55dba9255ffc6aff3a76e07ba07cee2101e7 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 31 Jul 2024 19:47:00 +0200 Subject: [PATCH 02/39] Add stub of FramePointerAnalysisTest --- .../analyse/FramePointerAnalysisTest.scala | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala new file mode 100644 index 000000000000..e9ba652db837 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -0,0 +1,87 @@ +package org.enso.compiler.test.pass.analyse + +import org.enso.compiler.Passes +import org.enso.compiler.context.{FreshNameSupply, ModuleContext} +import org.enso.compiler.core.IR +import org.enso.compiler.core.ir.Expression +import org.enso.compiler.core.Implicits.AsMetadata +import org.enso.compiler.core.ir.Module +import org.enso.compiler.pass.analyse.alias.Info +import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} +import org.enso.compiler.pass.analyse.{AliasAnalysis, FramePointerAnalysis} +import org.enso.compiler.test.CompilerTest + + +class FramePointerAnalysisTest extends CompilerTest { + + // === Test Setup =========================================================== + + def mkModuleContext: ModuleContext = + buildModuleContext( + freshNameSupply = Some(new FreshNameSupply) + ) + + val passes = new Passes(defaultConfig) + + val precursorPasses: PassGroup = + passes.getPrecursors(FramePointerAnalysis).get + + val passConfiguration: PassConfiguration = PassConfiguration() + + implicit val passManager: PassManager = + new PassManager(List(precursorPasses), passConfiguration) + + /** Adds an extension method to analyse an Enso module. + * + * @param ir the ir to analyse + */ + implicit class AnalyseModule(ir: Module) { + + /** Performs tail call analysis on [[ir]]. + * + * @param context the module context in which analysis takes place + * @return [[ir]], with tail call analysis metadata attached + */ + def analyse(implicit context: ModuleContext): Module = { + FramePointerAnalysis.runModule(ir, context) + } + } + + // === The Tests ============================================================ + "Frame pointer analysis" should { + "attach frame pointers to a simple method" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |main = + | a = 1 + | b = 2 + | 42 + |""".stripMargin.preprocessModule.analyse + val allOcc = collectAllOccurences(ir.bindings.head) + allOcc.size shouldBe 2 + val firstOcc = allOcc.head + firstOcc._1 + .asInstanceOf[Expression.Binding] + .name + .name shouldEqual "a" + val secondOcc = allOcc.last + secondOcc._1 + .asInstanceOf[Expression.Binding] + .name + .name shouldEqual "b" + } + } + + private def collectAllOccurences( + ir: IR + ): List[(IR, Info.Occurrence)] = { + ir.preorder().flatMap { childIr => + childIr.getMetadata(AliasAnalysis) match { + case Some(occMeta: Info.Occurrence) => + Some((childIr, occMeta)) + case _ => None + } + } + } +} From 56e7b3f3811497ecee57aaa812e0d826aaff02dc Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 1 Aug 2024 13:16:58 +0200 Subject: [PATCH 03/39] Add more framepointer tests --- .../analyse/FramePointerAnalysisTest.scala | 139 ++++++++++++++++-- 1 file changed, 124 insertions(+), 15 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index e9ba652db837..7585294e8bc5 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -1,17 +1,16 @@ package org.enso.compiler.test.pass.analyse import org.enso.compiler.Passes -import org.enso.compiler.context.{FreshNameSupply, ModuleContext} +import org.enso.compiler.context.{FramePointer, FreshNameSupply, ModuleContext} import org.enso.compiler.core.IR import org.enso.compiler.core.ir.Expression import org.enso.compiler.core.Implicits.AsMetadata import org.enso.compiler.core.ir.Module -import org.enso.compiler.pass.analyse.alias.Info +import org.enso.compiler.pass.analyse.alias.{Graph, Info} import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} import org.enso.compiler.pass.analyse.{AliasAnalysis, FramePointerAnalysis} import org.enso.compiler.test.CompilerTest - class FramePointerAnalysisTest extends CompilerTest { // === Test Setup =========================================================== @@ -49,7 +48,7 @@ class FramePointerAnalysisTest extends CompilerTest { // === The Tests ============================================================ "Frame pointer analysis" should { - "attach frame pointers to a simple method" in { + "attach frame pointers to local variables" in { implicit val ctx: ModuleContext = mkModuleContext val ir = """ @@ -58,18 +57,116 @@ class FramePointerAnalysisTest extends CompilerTest { | b = 2 | 42 |""".stripMargin.preprocessModule.analyse - val allOcc = collectAllOccurences(ir.bindings.head) + val allOcc = collectAllOccurences(ir) allOcc.size shouldBe 2 - val firstOcc = allOcc.head - firstOcc._1 - .asInstanceOf[Expression.Binding] - .name - .name shouldEqual "a" - val secondOcc = allOcc.last - secondOcc._1 - .asInstanceOf[Expression.Binding] - .name - .name shouldEqual "b" + withClue("Occurences are attached to Expression.Binding") { + val firstOcc = allOcc.head + firstOcc._1 + .asInstanceOf[Expression.Binding] + .name + .name shouldEqual "a" + val secondOcc = allOcc.last + secondOcc._1 + .asInstanceOf[Expression.Binding] + .name + .name shouldEqual "b" + } + val framePointers = collectAllFramePointers(ir) + withClue( + "There should be the exact same amount of AliasAnalysis Uses and FramePointers metadata" + ) { + allOcc.size shouldEqual framePointers.size + } + framePointers.head._2.framePointer shouldEqual new FramePointer(0, 1) + framePointers.last._2.framePointer shouldEqual new FramePointer(0, 2) + } + + "attach frame pointers to parameters" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |main x y = x + y + |""".stripMargin.preprocessModule.analyse + val framePointers = collectAllFramePointers(ir) + framePointers.head._2.framePointer shouldEqual new FramePointer(0, 1) + framePointers.last._2.framePointer shouldEqual new FramePointer(0, 2) + } + + "attach frame pointers inside nested scope" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |main = + | nested x y = x + y + | nested 1 2 + |""".stripMargin.preprocessModule.analyse + val mainScope = ir.bindings.head + .unsafeGetMetadata(AliasAnalysis, "should exist") + .asInstanceOf[Info.Scope.Root] + val xUseId = mainScope.graph.symbolToIds[Graph.Occurrence.Use]("x").head + val yUseId = mainScope.graph.symbolToIds[Graph.Occurrence.Use]("y").head + val nestedUseId = + mainScope.graph.symbolToIds[Graph.Occurrence.Use]("nested").head + val allOccurences = collectAllOccurences(ir) + val xIr = allOccurences + .find { case (_, occ) => occ.id == xUseId } + .map(_._1) + .get + val yIr = allOccurences + .find { case (_, occ) => occ.id == yUseId } + .map(_._1) + .get + val nestedIr = allOccurences + .find { case (_, occ) => occ.id == nestedUseId } + .map(_._1) + .get + withClue("All Uses must have FramePointerMeta associated") { + xIr.passData().get(FramePointerAnalysis) shouldBe defined + yIr.passData().get(FramePointerAnalysis) shouldBe defined + nestedIr.passData().get(FramePointerAnalysis) shouldBe defined + } + xIr + .unsafeGetMetadata(FramePointerAnalysis, "should exist") + .framePointer shouldEqual new FramePointer(0, 1) + yIr + .unsafeGetMetadata(FramePointerAnalysis, "should exist") + .framePointer shouldEqual new FramePointer(0, 2) + nestedIr + .unsafeGetMetadata(FramePointerAnalysis, "should exist") + .framePointer shouldEqual new FramePointer(0, 1) + } + + "attach frame pointer in nested scope that uses parent scope" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |main = + | x = 1 + | nested = + | x + 1 + | nested + |""".stripMargin.preprocessModule.analyse + val mainScope = ir.bindings.head + .unsafeGetMetadata(AliasAnalysis, "should exist") + .asInstanceOf[Info.Scope.Root] + val xUseId = mainScope.graph.symbolToIds[Graph.Occurrence.Use]("x").head + val allOccurences = collectAllOccurences(ir) + val xIr = allOccurences + .find { case (_, occ) => occ.id == xUseId } + .map(_._1) + .get + xIr + .unsafeGetMetadata(FramePointerAnalysis, "should exist") + .framePointer shouldEqual new FramePointer(1, 1) + val nestedUseId = + mainScope.graph.symbolToIds[Graph.Occurrence.Use]("nested").head + val nestedIr = allOccurences + .find { case (_, occ) => occ.id == nestedUseId } + .map(_._1) + .get + nestedIr + .unsafeGetMetadata(FramePointerAnalysis, "should exist") + .framePointer shouldEqual new FramePointer(0, 2) } } @@ -84,4 +181,16 @@ class FramePointerAnalysisTest extends CompilerTest { } } } + + private def collectAllFramePointers( + ir: IR + ): List[(IR, FramePointerAnalysis.Metadata)] = { + ir.preorder().flatMap { childIr => + childIr.getMetadata(FramePointerAnalysis) match { + case Some(framePointerMeta: FramePointerAnalysis.Metadata) => + Some((childIr, framePointerMeta)) + case _ => None + } + } + } } From 1218406e32e8895a9dc9fc2a64ae914ed9876f3d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 1 Aug 2024 17:41:08 +0200 Subject: [PATCH 04/39] FramePointer has constructor for better debugging --- .../main/java/org/enso/compiler/context/FramePointer.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/context/FramePointer.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/context/FramePointer.java index 64637f8f274d..a4ff318083d0 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/context/FramePointer.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/context/FramePointer.java @@ -3,4 +3,10 @@ /** * A representation of a pointer into a stack frame at a given number of levels above the current. */ -public record FramePointer(int parentLevel, int frameSlotIdx) {} +public record FramePointer(int parentLevel, int frameSlotIdx) { + + public FramePointer { + assert parentLevel >= 0; + assert frameSlotIdx >= 0; + } +} From 7125220dff2643f33201a667aad99d5a49d7f73d Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 1 Aug 2024 17:41:44 +0200 Subject: [PATCH 05/39] Update tests - FramePointer metadata is also in Info.Def --- .../analyse/FramePointerAnalysisTest.scala | 144 +++++++++++------- 1 file changed, 87 insertions(+), 57 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 7585294e8bc5..80e0166d0a66 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -71,14 +71,14 @@ class FramePointerAnalysisTest extends CompilerTest { .name .name shouldEqual "b" } - val framePointers = collectAllFramePointers(ir) - withClue( - "There should be the exact same amount of AliasAnalysis Uses and FramePointers metadata" - ) { - allOcc.size shouldEqual framePointers.size + withClue("Expression.Binding must have FramePointer associated") { + allOcc.head._1.passData().get(FramePointerAnalysis) shouldBe Some( + new FramePointer(0, 1) + ) + allOcc.last._1.passData().get(FramePointerAnalysis) shouldBe Some( + new FramePointer(0, 2) + ) } - framePointers.head._2.framePointer shouldEqual new FramePointer(0, 1) - framePointers.last._2.framePointer shouldEqual new FramePointer(0, 2) } "attach frame pointers to parameters" in { @@ -88,8 +88,9 @@ class FramePointerAnalysisTest extends CompilerTest { |main x y = x + y |""".stripMargin.preprocessModule.analyse val framePointers = collectAllFramePointers(ir) - framePointers.head._2.framePointer shouldEqual new FramePointer(0, 1) - framePointers.last._2.framePointer shouldEqual new FramePointer(0, 2) + framePointers.size shouldBe 4 + framePointers(0)._2.framePointer shouldEqual new FramePointer(0, 1) + framePointers(1)._2.framePointer shouldEqual new FramePointer(0, 2) } "attach frame pointers inside nested scope" in { @@ -103,37 +104,29 @@ class FramePointerAnalysisTest extends CompilerTest { val mainScope = ir.bindings.head .unsafeGetMetadata(AliasAnalysis, "should exist") .asInstanceOf[Info.Scope.Root] - val xUseId = mainScope.graph.symbolToIds[Graph.Occurrence.Use]("x").head - val yUseId = mainScope.graph.symbolToIds[Graph.Occurrence.Use]("y").head - val nestedUseId = - mainScope.graph.symbolToIds[Graph.Occurrence.Use]("nested").head - val allOccurences = collectAllOccurences(ir) - val xIr = allOccurences - .find { case (_, occ) => occ.id == xUseId } - .map(_._1) - .get - val yIr = allOccurences - .find { case (_, occ) => occ.id == yUseId } - .map(_._1) - .get - val nestedIr = allOccurences - .find { case (_, occ) => occ.id == nestedUseId } - .map(_._1) - .get - withClue("All Uses must have FramePointerMeta associated") { - xIr.passData().get(FramePointerAnalysis) shouldBe defined - yIr.passData().get(FramePointerAnalysis) shouldBe defined - nestedIr.passData().get(FramePointerAnalysis) shouldBe defined + + withClue( + "Both definition and usage of `x` should be associated with the same frame pointer" + ) { + mainScope.graph.symbolToIds[Graph.Occurrence]("x").foreach { xId => + val xIr = findAssociatedIr(xId, ir) + expectFramePointer(xIr, new FramePointer(0, 1)) + } + } + withClue( + "Both definition and usage of `y` should be associated with the same frame pointer" + ) { + mainScope.graph.symbolToIds[Graph.Occurrence]("y").foreach { yId => + val yIr = findAssociatedIr(yId, ir) + expectFramePointer(yIr, new FramePointer(0, 2)) + } + } + + mainScope.graph.symbolToIds[Graph.Occurrence]("nested").foreach { + nestedId => + val nestedIr = findAssociatedIr(nestedId, ir) + expectFramePointer(nestedIr, new FramePointer(0, 1)) } - xIr - .unsafeGetMetadata(FramePointerAnalysis, "should exist") - .framePointer shouldEqual new FramePointer(0, 1) - yIr - .unsafeGetMetadata(FramePointerAnalysis, "should exist") - .framePointer shouldEqual new FramePointer(0, 2) - nestedIr - .unsafeGetMetadata(FramePointerAnalysis, "should exist") - .framePointer shouldEqual new FramePointer(0, 1) } "attach frame pointer in nested scope that uses parent scope" in { @@ -149,25 +142,62 @@ class FramePointerAnalysisTest extends CompilerTest { val mainScope = ir.bindings.head .unsafeGetMetadata(AliasAnalysis, "should exist") .asInstanceOf[Info.Scope.Root] - val xUseId = mainScope.graph.symbolToIds[Graph.Occurrence.Use]("x").head - val allOccurences = collectAllOccurences(ir) - val xIr = allOccurences - .find { case (_, occ) => occ.id == xUseId } - .map(_._1) - .get - xIr - .unsafeGetMetadata(FramePointerAnalysis, "should exist") - .framePointer shouldEqual new FramePointer(1, 1) - val nestedUseId = - mainScope.graph.symbolToIds[Graph.Occurrence.Use]("nested").head - val nestedIr = allOccurences - .find { case (_, occ) => occ.id == nestedUseId } - .map(_._1) - .get - nestedIr - .unsafeGetMetadata(FramePointerAnalysis, "should exist") - .framePointer shouldEqual new FramePointer(0, 2) + val xDefIr = findAssociatedIr( + mainScope.graph.symbolToIds[Graph.Occurrence.Def]("x").head, + ir + ) + expectFramePointer(xDefIr, new FramePointer(0, 1)) + + val nestedDefIr = findAssociatedIr( + mainScope.graph.symbolToIds[Graph.Occurrence.Def]("nested").head, + ir + ) + expectFramePointer(nestedDefIr, new FramePointer(0, 2)) + + val xUseIr = findAssociatedIr( + mainScope.graph.symbolToIds[Graph.Occurrence.Use]("x").head, + ir + ) + expectFramePointer(xUseIr, new FramePointer(1, 1)) + + val nestedUseIr = findAssociatedIr( + mainScope.graph.symbolToIds[Graph.Occurrence.Use]("nested").head, + ir + ) + expectFramePointer(nestedUseIr, new FramePointer(0, 2)) + } + } + + /** Asserts that the given `ir` has the given `framePointer` attached as metadata. + */ + private def expectFramePointer( + ir: IR, + framePointer: FramePointer + ): Unit = { + withClue("FramePointerAnalysis metadata should be attached to the IR") { + ir.passData().get(FramePointerAnalysis) shouldBe defined + } + ir + .unsafeGetMetadata(FramePointerAnalysis, "should exist") + .framePointer shouldEqual framePointer + } + + private def findAssociatedIr( + id: Graph.Id, + moduleIr: IR + ): IR = { + val irs = moduleIr.preorder().collect { childIr => + childIr.getMetadata(AliasAnalysis) match { + case Some(Info.Occurrence(_, occId)) if occId == id => + childIr + } + } + withClue( + "There should be just one IR element that has a particular Graph.ID" + ) { + irs.size shouldBe 1 } + irs.head } private def collectAllOccurences( From 05610fa62cf788c9c1dc62bfd220e594955b4745 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 2 Aug 2024 12:17:05 +0200 Subject: [PATCH 06/39] FramePointerAnalysis traverses the whole IR. --- .../enso/compiler/context/LocalScope.scala | 4 +- .../pass/analyse/FramePointerAnalysis.scala | 284 +++++++++++++++++- 2 files changed, 283 insertions(+), 5 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala index dc70212d5c1e..5ac062ef30b8 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala @@ -174,7 +174,7 @@ object LocalScope { * Every tuple of the list denotes frame slot kind and its name. * Note that `info` for a frame slot is not used by Enso. */ - def monadicStateSlotName: String = "<>" - private def internalSlotsSize: Int = 1 + def monadicStateSlotName: String = "<>" + def internalSlotsSize: Int = 1 } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index f100fec98ceb..bc99a13938e2 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -4,12 +4,27 @@ import org.enso.compiler.context.{ CompilerContext, FramePointer, InlineContext, + LocalScope, ModuleContext } -import org.enso.compiler.core.ir.Module -import org.enso.compiler.core.ir.{Expression, ProcessingPass} +import org.enso.compiler.core.{CompilerError, IR} +import org.enso.compiler.core.ir.expression.Application +import org.enso.compiler.core.ir.{ + CallArgument, + DefinitionArgument, + Expression, + Function, + Module, + Name, + ProcessingPass +} +import org.enso.compiler.core.Implicits.AsMetadata +import org.enso.compiler.core.ir.MetadataStorage.MetadataPair +import org.enso.compiler.core.ir.module.scope.Definition +import org.enso.compiler.core.ir.module.scope.definition.Method import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.IRPass.IRMetadata +import org.enso.compiler.pass.analyse.alias.{Graph, Info} /** This pass attaches [[FramePointer]] as metadata to all the IR elements that already * have [[org.enso.compiler.pass.analyse.alias.Info.Occurrence]] attached. @@ -27,7 +42,270 @@ case object FramePointerAnalysis extends IRPass { override val invalidatedPasses: Seq[IRPass] = Seq(this) override def runModule(ir: Module, moduleContext: ModuleContext): Module = { - ir + val newBindings = ir.bindings.map(processBinding) + ir.copy(bindings = newBindings) + } + + private def processBinding( + ir: Definition + ): Definition = { + ir match { + case m: Method.Explicit => + getAliasAnalysisGraph(m) match { + case Some(graph) => + m.copy( + body = m.body.mapExpressions(processExpression(_, graph)) + ) + case _ => m + } + case m: Method.Conversion => + getAliasAnalysisGraph(m) match { + case Some(graph) => + m.copy( + body = m.body.mapExpressions(processExpression(_, graph)) + ) + case _ => m + } + case t: Definition.Type => + getAliasAnalysisGraph(t) match { + case Some(graph) => + t.copy( + params = processArgumentDefs( + t.params, + graph + ), + members = t.members.map(d => { + d.copy( + arguments = processArgumentDefs( + d.arguments, + graph + ), + annotations = d.annotations.map { ann => + ann.copy( + expression = processExpression( + ann.expression, + graph + ) + ) + } + ) + }) + ) + case _ => t + } + case _ => ir + } + } + + private def processArgumentDefs( + args: List[DefinitionArgument], + graph: Graph + ): List[DefinitionArgument] = { + args.map { + case arg @ DefinitionArgument.Specified( + name, + ascribedType, + defaultValue, + _, + _, + _, + _ + ) => + arg.copy( + name = maybeAttachFramePointer(name, graph), + ascribedType = ascribedType.map(processExpression(_, graph)), + defaultValue = defaultValue.map(processExpression(_, graph)) + ) + } + } + + /** Returns the index of the given `defOcc` definition in the given `scope` + * @param scope This scope must contain the given `defOcc` + * @param defOcc This occurrence must be in the given `scope` + */ + private def getFrameSlotIdxInScope( + graph: Graph, + scope: Graph.Scope, + defOcc: Graph.Occurrence.Def + ): Int = { + assert( + graph.scopeFor(defOcc.id).contains(scope), + "Def occurrence must be in the given scope" + ) + assert( + scope.allDefinitions.contains(defOcc), + "The given scope must contain the given Def occurrence" + ) + val idxInScope = scope.allDefinitions.zipWithIndex + .find { case (def_, _) => def_.id == defOcc.id } + .map(_._2) + .getOrElse( + throw new IllegalStateException( + "Def occurrence must be in the given scope" + ) + ) + idxInScope + LocalScope.internalSlotsSize + } + + private def getScopeDistance( + parentScope: Graph.Scope, + childScope: Graph.Scope + ): Int = { + var currScope: Option[Graph.Scope] = Some(childScope) + var scopeDistance = 0 + while (currScope.isDefined && currScope.get != parentScope) { + currScope = currScope.get.parent + scopeDistance += 1 + } + scopeDistance + } + + private def processExpression( + exprIr: Expression, + graph: Graph + ): Expression = { + exprIr match { + case name: Name => maybeAttachFramePointer(name, graph) + case block: Expression.Block => + block.copy( + expressions = block.expressions.map { expr => + processExpression(expr, graph) + }, + returnValue = processExpression(block.returnValue, graph) + ) + case lambda @ Function.Lambda(args, body, _, _, _, _) => + lambda.copy( + arguments = processArgumentDefs(args, graph), + body = processExpression(body, graph) + ) + case binding @ Expression.Binding(name, expr, _, _, _) => + maybeAttachFramePointer(binding, graph) + .copy( + name = maybeAttachFramePointer(name, graph), + expression = processExpression(expr, graph) + ) + case app: Application => processApplication(app, graph) + case _ => + exprIr.mapExpressions(processExpression(_, graph)) + } + } + + private def processApplication( + application: Application, + graph: Graph + ): Application = { + application match { + case app @ Application.Prefix(func, arguments, _, _, _, _) => + app.copy( + function = processExpression(func, graph), + arguments = processCallArguments(arguments, graph) + ) + case app @ Application.Force(expr, _, _, _) => + app.copy(target = processExpression(expr, graph)) + case app @ Application.Sequence(items, _, _, _) => + app.copy(items = items.map(processExpression(_, graph))) + case tSet @ Application.Typeset(expr, _, _, _) => + tSet.copy( + expression = expr.map(processExpression(_, graph)) + ) + case _ => + throw new CompilerError( + "Unexpected type of Application: " + application + ) + } + } + + private def processCallArguments( + arguments: List[CallArgument], + graph: Graph + ): List[CallArgument] = { + arguments.map { case arg @ CallArgument.Specified(name, value, _, _, _) => + arg.copy( + name = name.map(maybeAttachFramePointer(_, graph)), + value = processExpression(value, graph) + ) + } + } + + /** Attaches [[FramePointerMeta]] metadata to the given `ir` if there is an + * appropriate [[Info.Occurrence]] already attached to it. + * @param ir IR to attach the frame pointer metadata to. + * @param graph Alias analysis graph + * @tparam T Type of IR. + * @return Copy of `ir` with attached metadata, or just the `ir` if nothing + * was attached. + */ + private def maybeAttachFramePointer[T <: IR]( + ir: T, + graph: Graph + ): T = { + getAliasAnalysisMeta(ir) match { + case Some(Info.Occurrence(_, id)) => + graph.scopeFor(id) match { + case Some(scope) => + graph.getOccurrence(id) match { + case Some(use: Graph.Occurrence.Use) => + // Use is allowed to read a variable from some parent scope + graph.defLinkFor(use.id) match { + case Some(defLink) => + val defId = defLink.target + val defOcc = graph + .getOccurrence(defId) + .get + .asInstanceOf[Graph.Occurrence.Def] + val defScope = graph.scopeFor(defId).get + val parentLevel = getScopeDistance(defScope, scope) + val frameSlotIdx = + getFrameSlotIdxInScope(graph, defScope, defOcc) + ir.updateMetadata( + new MetadataPair( + this, + new FramePointerMeta( + new FramePointer(parentLevel, frameSlotIdx) + ) + ) + ) + case None => + // It is possible that there is no Def for this Use. It can, for example, be + // Use for some global symbol. In `IrToTruffle`, an UnresolvedSymbol will be + // generated for it. + // We will not attach any metadata in this case. + ir + } + case Some(defn: Graph.Occurrence.Def) => + // The definition cannot write to parent's frame slots. + val parentLevel = 0 + val frameSlotIdx = getFrameSlotIdxInScope(graph, scope, defn) + ir.updateMetadata( + new MetadataPair( + this, + new FramePointerMeta( + new FramePointer(parentLevel, frameSlotIdx) + ) + ) + ) + case _ => ir + } + case _ => ir + } + case _ => ir + } + } + + private def getAliasAnalysisMeta( + ir: IR + ): Option[AliasAnalysis.Metadata] = { + ir.passData.get(AliasAnalysis) match { + case Some(aliasInfo: Info) => + Some(aliasInfo) + case _ => None + } + } + + private def getAliasAnalysisGraph( + ir: IR + ): Option[Graph] = { + getAliasAnalysisMeta(ir).map(_.graph) } /** Not implemented for this pass. From c66b25fb43fb01e5bbc230676b3c1817dc777053 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 2 Aug 2024 12:17:17 +0200 Subject: [PATCH 07/39] Fix test - check for metadata --- .../test/pass/analyse/FramePointerAnalysisTest.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 80e0166d0a66..369739791f5f 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -72,12 +72,12 @@ class FramePointerAnalysisTest extends CompilerTest { .name shouldEqual "b" } withClue("Expression.Binding must have FramePointer associated") { - allOcc.head._1.passData().get(FramePointerAnalysis) shouldBe Some( - new FramePointer(0, 1) - ) - allOcc.last._1.passData().get(FramePointerAnalysis) shouldBe Some( - new FramePointer(0, 2) - ) + allOcc.head._1 + .unsafeGetMetadata(FramePointerAnalysis, "should exist") + .framePointer shouldEqual new FramePointer(0, 1) + allOcc.last._1 + .unsafeGetMetadata(FramePointerAnalysis, "should exist") + .framePointer shouldEqual new FramePointer(0, 2) } } From 3520614847e1059a37c6933c3c81135060933a5a Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 2 Aug 2024 12:20:25 +0200 Subject: [PATCH 08/39] Reorder methods in FramePointerAnalysis --- .../pass/analyse/FramePointerAnalysis.scala | 88 ++++++++++--------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index bc99a13938e2..9ac7345c4c22 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -119,47 +119,6 @@ case object FramePointerAnalysis extends IRPass { } } - /** Returns the index of the given `defOcc` definition in the given `scope` - * @param scope This scope must contain the given `defOcc` - * @param defOcc This occurrence must be in the given `scope` - */ - private def getFrameSlotIdxInScope( - graph: Graph, - scope: Graph.Scope, - defOcc: Graph.Occurrence.Def - ): Int = { - assert( - graph.scopeFor(defOcc.id).contains(scope), - "Def occurrence must be in the given scope" - ) - assert( - scope.allDefinitions.contains(defOcc), - "The given scope must contain the given Def occurrence" - ) - val idxInScope = scope.allDefinitions.zipWithIndex - .find { case (def_, _) => def_.id == defOcc.id } - .map(_._2) - .getOrElse( - throw new IllegalStateException( - "Def occurrence must be in the given scope" - ) - ) - idxInScope + LocalScope.internalSlotsSize - } - - private def getScopeDistance( - parentScope: Graph.Scope, - childScope: Graph.Scope - ): Int = { - var currScope: Option[Graph.Scope] = Some(childScope) - var scopeDistance = 0 - while (currScope.isDefined && currScope.get != parentScope) { - currScope = currScope.get.parent - scopeDistance += 1 - } - scopeDistance - } - private def processExpression( exprIr: Expression, graph: Graph @@ -292,6 +251,53 @@ case object FramePointerAnalysis extends IRPass { } } + /** Returns the index of the given `defOcc` definition in the given `scope` + * @param scope This scope must contain the given `defOcc` + * @param defOcc This occurrence must be in the given `scope` + */ + private def getFrameSlotIdxInScope( + graph: Graph, + scope: Graph.Scope, + defOcc: Graph.Occurrence.Def + ): Int = { + assert( + graph.scopeFor(defOcc.id).contains(scope), + "Def occurrence must be in the given scope" + ) + assert( + scope.allDefinitions.contains(defOcc), + "The given scope must contain the given Def occurrence" + ) + val idxInScope = scope.allDefinitions.zipWithIndex + .find { case (def_, _) => def_.id == defOcc.id } + .map(_._2) + .getOrElse( + throw new IllegalStateException( + "Def occurrence must be in the given scope" + ) + ) + idxInScope + LocalScope.internalSlotsSize + } + + /** Returns the *scope distance* of the given `childScope` to the given `parentScope`. + * Scope distance is the number of parents from the `childScope`. + * @param parentScope Some of the parent scopes of `childScope`. + * @param childScope Nested child scope of `parentScope`. + * @return + */ + private def getScopeDistance( + parentScope: Graph.Scope, + childScope: Graph.Scope + ): Int = { + var currScope: Option[Graph.Scope] = Some(childScope) + var scopeDistance = 0 + while (currScope.isDefined && currScope.get != parentScope) { + currScope = currScope.get.parent + scopeDistance += 1 + } + scopeDistance + } + private def getAliasAnalysisMeta( ir: IR ): Option[AliasAnalysis.Metadata] = { From e8c821ac003e82e7aefef4144d05b99869119326 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 2 Aug 2024 13:18:16 +0200 Subject: [PATCH 09/39] Fix argument processing --- .../pass/analyse/FramePointerAnalysis.scala | 37 +++++++------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 9ac7345c4c22..698dfb086a9f 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -54,7 +54,7 @@ case object FramePointerAnalysis extends IRPass { getAliasAnalysisGraph(m) match { case Some(graph) => m.copy( - body = m.body.mapExpressions(processExpression(_, graph)) + body = processExpression(m.body, graph) ) case _ => m } @@ -101,21 +101,8 @@ case object FramePointerAnalysis extends IRPass { args: List[DefinitionArgument], graph: Graph ): List[DefinitionArgument] = { - args.map { - case arg @ DefinitionArgument.Specified( - name, - ascribedType, - defaultValue, - _, - _, - _, - _ - ) => - arg.copy( - name = maybeAttachFramePointer(name, graph), - ascribedType = ascribedType.map(processExpression(_, graph)), - defaultValue = defaultValue.map(processExpression(_, graph)) - ) + args.map { arg => + maybeAttachFramePointer(arg, graph) } } @@ -155,10 +142,11 @@ case object FramePointerAnalysis extends IRPass { ): Application = { application match { case app @ Application.Prefix(func, arguments, _, _, _, _) => - app.copy( - function = processExpression(func, graph), - arguments = processCallArguments(arguments, graph) - ) + maybeAttachFramePointer(app, graph) + .copy( + function = processExpression(func, graph), + arguments = processCallArguments(arguments, graph) + ) case app @ Application.Force(expr, _, _, _) => app.copy(target = processExpression(expr, graph)) case app @ Application.Sequence(items, _, _, _) => @@ -179,10 +167,11 @@ case object FramePointerAnalysis extends IRPass { graph: Graph ): List[CallArgument] = { arguments.map { case arg @ CallArgument.Specified(name, value, _, _, _) => - arg.copy( - name = name.map(maybeAttachFramePointer(_, graph)), - value = processExpression(value, graph) - ) + maybeAttachFramePointer(arg, graph) + .copy( + name = name.map(maybeAttachFramePointer(_, graph)), + value = processExpression(value, graph) + ) } } From d91c580b361d6c396c24a5591014d508dbbd3cd1 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 2 Aug 2024 16:10:13 +0200 Subject: [PATCH 10/39] Improve some toString methods for debugging --- .../main/scala/org/enso/compiler/context/LocalScope.scala | 2 +- .../org/enso/compiler/pass/analyse/alias/Graph.scala | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala index 5ac062ef30b8..1d8a29806e06 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala @@ -151,7 +151,7 @@ class LocalScope( } override def toString: String = { - s"LocalScope(${allFrameSlotIdxs.keySet})" + s"LocalScope(flattenToParent = ${flattenToParent}, allFrameSlotIdxs = ${allFrameSlotIdxs.keySet})" } } object LocalScope { diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/alias/Graph.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/alias/Graph.scala index 0ed0f41f930c..2e7c5d2f5919 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/alias/Graph.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/alias/Graph.scala @@ -726,7 +726,9 @@ object Graph { identifier: UUID @Identifier, externalId: Option[UUID @ExternalID], isLazy: Boolean = false - ) extends Occurrence + ) extends Occurrence { + override def toString: String = s"Def($id, $symbol)" + } /** A usage of a symbol in the aliasing graph * @@ -745,7 +747,9 @@ object Graph { override val symbol: Graph.Symbol, identifier: UUID @Identifier, externalId: Option[UUID @ExternalID] - ) extends Occurrence + ) extends Occurrence { + override def toString: String = s"Use($id, $symbol)" + } // TODO [AA] At some point the analysis should make use of these. /** Represents a global symbol that has been _asked for_ in the program. From 65e8c1a7824725acf24bc6afa60483f1520d5e54 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Fri, 2 Aug 2024 16:11:20 +0200 Subject: [PATCH 11/39] Fix parent levels in tests --- .../analyse/FramePointerAnalysisTest.scala | 59 +++++++++---------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 369739791f5f..569a0489106d 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -87,46 +87,39 @@ class FramePointerAnalysisTest extends CompilerTest { """ |main x y = x + y |""".stripMargin.preprocessModule.analyse - val framePointers = collectAllFramePointers(ir) - framePointers.size shouldBe 4 - framePointers(0)._2.framePointer shouldEqual new FramePointer(0, 1) - framePointers(1)._2.framePointer shouldEqual new FramePointer(0, 2) - } - - "attach frame pointers inside nested scope" in { - implicit val ctx: ModuleContext = mkModuleContext - val ir = - """ - |main = - | nested x y = x + y - | nested 1 2 - |""".stripMargin.preprocessModule.analyse - val mainScope = ir.bindings.head + val aliasGraph = ir.bindings.head .unsafeGetMetadata(AliasAnalysis, "should exist") .asInstanceOf[Info.Scope.Root] + .graph + val xDefIr = findAssociatedIr( + aliasGraph.symbolToIds[Graph.Occurrence.Def]("x").head, + ir + ) + expectFramePointer(xDefIr, new FramePointer(0, 1)) + val xUseIr = findAssociatedIr( + aliasGraph.symbolToIds[Graph.Occurrence.Use]("x").head, + ir + ) withClue( - "Both definition and usage of `x` should be associated with the same frame pointer" + "x is used as an argument to a method call, so is in a separate scope from root scope." + + " Its FramePointer should therefore have parentLevel = 1" ) { - mainScope.graph.symbolToIds[Graph.Occurrence]("x").foreach { xId => - val xIr = findAssociatedIr(xId, ir) - expectFramePointer(xIr, new FramePointer(0, 1)) - } + expectFramePointer(xUseIr, new FramePointer(1, 1)) } + + val plusUseIr = findAssociatedIr( + aliasGraph.symbolToIds[Graph.Occurrence.Use]("+").head, + ir + ) withClue( - "Both definition and usage of `y` should be associated with the same frame pointer" + "There should be no associated FramePointer with usage of `+`, because it is not defined " + + "in any scope" ) { - mainScope.graph.symbolToIds[Graph.Occurrence]("y").foreach { yId => - val yIr = findAssociatedIr(yId, ir) - expectFramePointer(yIr, new FramePointer(0, 2)) - } - } - - mainScope.graph.symbolToIds[Graph.Occurrence]("nested").foreach { - nestedId => - val nestedIr = findAssociatedIr(nestedId, ir) - expectFramePointer(nestedIr, new FramePointer(0, 1)) + plusUseIr.passData().get(FramePointerAnalysis) shouldNot be(defined) } + val framePointers = collectAllFramePointers(ir) + framePointers.size shouldBe 4 } "attach frame pointer in nested scope that uses parent scope" in { @@ -142,6 +135,8 @@ class FramePointerAnalysisTest extends CompilerTest { val mainScope = ir.bindings.head .unsafeGetMetadata(AliasAnalysis, "should exist") .asInstanceOf[Info.Scope.Root] + val allFps = collectAllFramePointers(ir) + allFps.size shouldBe 4 val xDefIr = findAssociatedIr( mainScope.graph.symbolToIds[Graph.Occurrence.Def]("x").head, ir @@ -158,7 +153,7 @@ class FramePointerAnalysisTest extends CompilerTest { mainScope.graph.symbolToIds[Graph.Occurrence.Use]("x").head, ir ) - expectFramePointer(xUseIr, new FramePointer(1, 1)) + expectFramePointer(xUseIr, new FramePointer(2, 1)) val nestedUseIr = findAssociatedIr( mainScope.graph.symbolToIds[Graph.Occurrence.Use]("nested").head, From 61821bf3932f72bac1355f2aed3c05af6972d02b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 6 Aug 2024 12:41:34 +0200 Subject: [PATCH 12/39] FramePointerAnalysis does not copy IRs --- .../pass/analyse/FramePointerAnalysis.scala | 165 +++++++----------- 1 file changed, 64 insertions(+), 101 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 698dfb086a9f..72d0ad8c8076 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -18,8 +18,6 @@ import org.enso.compiler.core.ir.{ Name, ProcessingPass } -import org.enso.compiler.core.Implicits.AsMetadata -import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.core.ir.module.scope.Definition import org.enso.compiler.core.ir.module.scope.definition.Method import org.enso.compiler.pass.IRPass @@ -28,6 +26,7 @@ import org.enso.compiler.pass.analyse.alias.{Graph, Info} /** This pass attaches [[FramePointer]] as metadata to all the IR elements that already * have [[org.enso.compiler.pass.analyse.alias.Info.Occurrence]] attached. + * It does not replace the IR elements with errors, it just attaches metadata. */ case object FramePointerAnalysis extends IRPass { @@ -42,66 +41,47 @@ case object FramePointerAnalysis extends IRPass { override val invalidatedPasses: Seq[IRPass] = Seq(this) override def runModule(ir: Module, moduleContext: ModuleContext): Module = { - val newBindings = ir.bindings.map(processBinding) - ir.copy(bindings = newBindings) + ir.bindings.foreach(processBinding) + ir } private def processBinding( ir: Definition - ): Definition = { + ): Unit = { ir match { case m: Method.Explicit => getAliasAnalysisGraph(m) match { case Some(graph) => - m.copy( - body = processExpression(m.body, graph) - ) - case _ => m + processExpression(m.body, graph) + case _ => () } case m: Method.Conversion => getAliasAnalysisGraph(m) match { case Some(graph) => - m.copy( - body = m.body.mapExpressions(processExpression(_, graph)) - ) - case _ => m + processExpression(m.body, graph) + case _ => () } case t: Definition.Type => getAliasAnalysisGraph(t) match { case Some(graph) => - t.copy( - params = processArgumentDefs( - t.params, - graph - ), - members = t.members.map(d => { - d.copy( - arguments = processArgumentDefs( - d.arguments, - graph - ), - annotations = d.annotations.map { ann => - ann.copy( - expression = processExpression( - ann.expression, - graph - ) - ) - } - ) - }) - ) - case _ => t + processArgumentDefs(t.params, graph) + t.members.foreach { member => + processArgumentDefs(member.arguments, graph) + member.annotations.foreach { annotation => + processExpression(annotation.expression, graph) + } + } + case _ => () } - case _ => ir + case _ => () } } private def processArgumentDefs( args: List[DefinitionArgument], graph: Graph - ): List[DefinitionArgument] = { - args.map { arg => + ): Unit = { + args.foreach { arg => maybeAttachFramePointer(arg, graph) } } @@ -109,52 +89,44 @@ case object FramePointerAnalysis extends IRPass { private def processExpression( exprIr: Expression, graph: Graph - ): Expression = { + ): Unit = { exprIr match { case name: Name => maybeAttachFramePointer(name, graph) case block: Expression.Block => - block.copy( - expressions = block.expressions.map { expr => - processExpression(expr, graph) - }, - returnValue = processExpression(block.returnValue, graph) - ) - case lambda @ Function.Lambda(args, body, _, _, _, _) => - lambda.copy( - arguments = processArgumentDefs(args, graph), - body = processExpression(body, graph) - ) + block.expressions.foreach { blockExpr => + processExpression(blockExpr, graph) + } + processExpression(block.returnValue, graph) + case Function.Lambda(args, body, _, _, _, _) => + processArgumentDefs(args, graph) + processExpression(body, graph) case binding @ Expression.Binding(name, expr, _, _, _) => maybeAttachFramePointer(binding, graph) - .copy( - name = maybeAttachFramePointer(name, graph), - expression = processExpression(expr, graph) - ) + maybeAttachFramePointer(name, graph) + processExpression(expr, graph) + maybeAttachFramePointer(binding, graph) case app: Application => processApplication(app, graph) - case _ => - exprIr.mapExpressions(processExpression(_, graph)) + case _ => () } } private def processApplication( application: Application, graph: Graph - ): Application = { + ): Unit = { application match { case app @ Application.Prefix(func, arguments, _, _, _, _) => maybeAttachFramePointer(app, graph) - .copy( - function = processExpression(func, graph), - arguments = processCallArguments(arguments, graph) - ) - case app @ Application.Force(expr, _, _, _) => - app.copy(target = processExpression(expr, graph)) - case app @ Application.Sequence(items, _, _, _) => - app.copy(items = items.map(processExpression(_, graph))) - case tSet @ Application.Typeset(expr, _, _, _) => - tSet.copy( - expression = expr.map(processExpression(_, graph)) - ) + processExpression(func, graph) + processCallArguments(arguments, graph) + case Application.Force(expr, _, _, _) => + processExpression(expr, graph) + case Application.Sequence(items, _, _, _) => + items.foreach { item => + processExpression(item, graph) + } + case Application.Typeset(expr, _, _, _) => + expr.foreach(processExpression(_, graph)) case _ => throw new CompilerError( "Unexpected type of Application: " + application @@ -165,13 +137,12 @@ case object FramePointerAnalysis extends IRPass { private def processCallArguments( arguments: List[CallArgument], graph: Graph - ): List[CallArgument] = { - arguments.map { case arg @ CallArgument.Specified(name, value, _, _, _) => - maybeAttachFramePointer(arg, graph) - .copy( - name = name.map(maybeAttachFramePointer(_, graph)), - value = processExpression(value, graph) - ) + ): Unit = { + arguments.foreach { + case arg @ CallArgument.Specified(name, value, _, _, _) => + maybeAttachFramePointer(arg, graph) + name.foreach(maybeAttachFramePointer(_, graph)) + processExpression(value, graph) } } @@ -179,14 +150,13 @@ case object FramePointerAnalysis extends IRPass { * appropriate [[Info.Occurrence]] already attached to it. * @param ir IR to attach the frame pointer metadata to. * @param graph Alias analysis graph - * @tparam T Type of IR. * @return Copy of `ir` with attached metadata, or just the `ir` if nothing * was attached. */ - private def maybeAttachFramePointer[T <: IR]( - ir: T, + private def maybeAttachFramePointer( + ir: IR, graph: Graph - ): T = { + ): Unit = { getAliasAnalysisMeta(ir) match { case Some(Info.Occurrence(_, id)) => graph.scopeFor(id) match { @@ -205,41 +175,34 @@ case object FramePointerAnalysis extends IRPass { val parentLevel = getScopeDistance(defScope, scope) val frameSlotIdx = getFrameSlotIdxInScope(graph, defScope, defOcc) - ir.updateMetadata( - new MetadataPair( - this, - new FramePointerMeta( - new FramePointer(parentLevel, frameSlotIdx) - ) - ) - ) + updateMeta(ir, new FramePointer(parentLevel, frameSlotIdx)) case None => // It is possible that there is no Def for this Use. It can, for example, be // Use for some global symbol. In `IrToTruffle`, an UnresolvedSymbol will be // generated for it. // We will not attach any metadata in this case. - ir + () } case Some(defn: Graph.Occurrence.Def) => // The definition cannot write to parent's frame slots. val parentLevel = 0 val frameSlotIdx = getFrameSlotIdxInScope(graph, scope, defn) - ir.updateMetadata( - new MetadataPair( - this, - new FramePointerMeta( - new FramePointer(parentLevel, frameSlotIdx) - ) - ) - ) - case _ => ir + updateMeta(ir, new FramePointer(parentLevel, frameSlotIdx)) + case _ => () } - case _ => ir + case _ => () } - case _ => ir + case _ => () } } + private def updateMeta( + ir: IR, + framePointer: FramePointer + ): Unit = { + ir.passData().update(this, new FramePointerMeta(framePointer)) + } + /** Returns the index of the given `defOcc` definition in the given `scope` * @param scope This scope must contain the given `defOcc` * @param defOcc This occurrence must be in the given `scope` From 1f373f1cb63ac75e5bd5dca7894c172b427356a4 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 6 Aug 2024 12:56:40 +0200 Subject: [PATCH 13/39] FramePointerAnalysis metadata is Persistable --- .../java/org/enso/compiler/pass/analyse/PassPersistance.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java index 838a148e7a0b..893f2ce4b7cd 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PassPersistance.java @@ -1,6 +1,7 @@ package org.enso.compiler.pass.analyse; import java.io.IOException; +import org.enso.compiler.context.FramePointer; import org.enso.compiler.pass.analyse.alias.Graph; import org.enso.compiler.pass.analyse.alias.Info; import org.enso.compiler.pass.analyse.types.TypeInference; @@ -76,6 +77,9 @@ allowInlining = false) @Persistable(clazz = Graph.Link.class, id = 1266, allowInlining = false) @Persistable(clazz = TypeInference.class, id = 1280) +@Persistable(clazz = FramePointerAnalysis$.class, id = 1281) +@Persistable(clazz = FramePointerAnalysis.FramePointerMeta.class, id = 1282) +@Persistable(clazz = FramePointer.class, id = 1283) public final class PassPersistance { private PassPersistance() {} From 98356a6d017a060a84b7301ed7738ef3801e4a32 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 6 Aug 2024 17:46:57 +0200 Subject: [PATCH 14/39] FramePointerAnalysis attaches metadata to atom constructors --- .../pass/analyse/FramePointerAnalysis.scala | 18 ++++- .../analyse/FramePointerAnalysisTest.scala | 71 ++++++++++++++++++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 72d0ad8c8076..eacc8e1a9550 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -66,9 +66,14 @@ case object FramePointerAnalysis extends IRPass { case Some(graph) => processArgumentDefs(t.params, graph) t.members.foreach { member => - processArgumentDefs(member.arguments, graph) + val memberGraph = getAliasRootScope(member) match { + case Some(memberRootScope) => + memberRootScope.graph + case _ => graph + } + processArgumentDefs(member.arguments, memberGraph) member.annotations.foreach { annotation => - processExpression(annotation.expression, graph) + processExpression(annotation.expression, memberGraph) } } case _ => () @@ -260,6 +265,15 @@ case object FramePointerAnalysis extends IRPass { } } + private def getAliasRootScope( + ir: IR + ): Option[Info.Scope.Root] = { + ir.passData().get(AliasAnalysis) match { + case Some(root: Info.Scope.Root) => Some(root) + case _ => None + } + } + private def getAliasAnalysisGraph( ir: IR ): Option[Graph] = { diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 569a0489106d..c721651c3c50 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -3,14 +3,15 @@ package org.enso.compiler.test.pass.analyse import org.enso.compiler.Passes import org.enso.compiler.context.{FramePointer, FreshNameSupply, ModuleContext} import org.enso.compiler.core.IR -import org.enso.compiler.core.ir.Expression +import org.enso.compiler.core.ir.{DefinitionArgument, Expression, Module} import org.enso.compiler.core.Implicits.AsMetadata -import org.enso.compiler.core.ir.Module import org.enso.compiler.pass.analyse.alias.{Graph, Info} import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} import org.enso.compiler.pass.analyse.{AliasAnalysis, FramePointerAnalysis} import org.enso.compiler.test.CompilerTest +import scala.reflect.ClassTag + class FramePointerAnalysisTest extends CompilerTest { // === Test Setup =========================================================== @@ -161,6 +162,72 @@ class FramePointerAnalysisTest extends CompilerTest { ) expectFramePointer(nestedUseIr, new FramePointer(0, 2)) } + + "attach frame pointers to constructor parameters" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Type + | Cons x y + |""".stripMargin.preprocessModule.analyse + val xArg = + findIRElement[DefinitionArgument.Specified](ir, _.name.name == "x") + val yArg = + findIRElement[DefinitionArgument.Specified](ir, _.name.name == "y") + expectFramePointer(xArg, new FramePointer(0, 1)) + expectFramePointer(yArg, new FramePointer(0, 2)) + } + + "attach frame pointers to constructor parameters with default values" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Type + | Cons x=1 y=(x + 1) + |""".stripMargin.preprocessModule.analyse + val xArg = + findIRElement[DefinitionArgument.Specified](ir, _.name.name == "x") + val yArg = + findIRElement[DefinitionArgument.Specified](ir, _.name.name == "y") + expectFramePointer(xArg, new FramePointer(0, 1)) + expectFramePointer(yArg, new FramePointer(0, 2)) + } + + "attach frame pointers to constructor parameters with type ascriptions" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Integer + |type My_Type + | Cons x:My_Integer (y:My_Integer = 1) + |""".stripMargin.preprocessModule.analyse + val xArg = + findIRElement[DefinitionArgument.Specified](ir, _.name.name == "x") + val yArg = + findIRElement[DefinitionArgument.Specified](ir, _.name.name == "y") + expectFramePointer(xArg, new FramePointer(0, 1)) + expectFramePointer(yArg, new FramePointer(0, 2)) + } + } + + /** Find the first IR element of the given `T` type by the given `filterCondition`. + * @param filterCondition Filter condition will be applied to all the elements of the desired type. + * The first element that matches the condition will be returned + * @tparam T type of the IR element to be found + * @return + */ + private def findIRElement[T <: IR: ClassTag]( + rootIr: IR, + filterCondition: T => Boolean + ): T = { + rootIr + .preorder() + .flatMap { + case childIr: T => + Some(childIr).filter(filterCondition) + case _ => None + } + .head } /** Asserts that the given `ir` has the given `framePointer` attached as metadata. From d274cbb47c0c7df659a539b7c8c5217a4ef35d92 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 6 Aug 2024 18:36:31 +0200 Subject: [PATCH 15/39] Synthetic self argument is handled specially --- .../pass/analyse/FramePointerAnalysis.scala | 9 +++- .../analyse/FramePointerAnalysisTest.scala | 47 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index eacc8e1a9550..0e0877e8ee19 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -87,7 +87,14 @@ case object FramePointerAnalysis extends IRPass { graph: Graph ): Unit = { args.foreach { arg => - maybeAttachFramePointer(arg, graph) + arg.name match { + case Name.Self(loc, synthetic, _, _) if loc.isEmpty && synthetic => + // synthetic self argument has occurrence attached, but there is no Occurence.Def for it. + // So we have to handle it specially. + updateMeta(arg, new FramePointer(0, 1)) + case _ => + maybeAttachFramePointer(arg, graph) + } } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index c721651c3c50..332325523f96 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -3,7 +3,7 @@ package org.enso.compiler.test.pass.analyse import org.enso.compiler.Passes import org.enso.compiler.context.{FramePointer, FreshNameSupply, ModuleContext} import org.enso.compiler.core.IR -import org.enso.compiler.core.ir.{DefinitionArgument, Expression, Module} +import org.enso.compiler.core.ir.{DefinitionArgument, Expression, Module, Name} import org.enso.compiler.core.Implicits.AsMetadata import org.enso.compiler.pass.analyse.alias.{Graph, Info} import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} @@ -208,6 +208,51 @@ class FramePointerAnalysisTest extends CompilerTest { expectFramePointer(xArg, new FramePointer(0, 1)) expectFramePointer(yArg, new FramePointer(0, 2)) } + + "attach frame pointers to synthetic self argument" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Type + | static_method = 42 + |""".stripMargin.preprocessModule.analyse + // synthetic self argument does not have location and is `synthetic == true`. + val syntheticSelfArg = findIRElement[DefinitionArgument.Specified](ir, _.name match { + case Name.Self(loc, synthetic, _, _) if loc.isEmpty && synthetic => true + case _ => false + } + ) + expectFramePointer(syntheticSelfArg, new FramePointer(0, 1)) + } + + "attach frame pointers to arguments with synthetic self" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Type + | static_method x = 42 + |""".stripMargin.preprocessModule.analyse + // synthetic self argument does not have location and is `synthetic == true`. + val syntheticSelfArg = findIRElement[DefinitionArgument.Specified](ir, _.name match { + case Name.Self(loc, synthetic, _, _) if loc.isEmpty && synthetic => true + case _ => false + } + ) + val xArg = findIRElement[DefinitionArgument.Specified](ir, _.name.name == "x") + expectFramePointer(syntheticSelfArg, new FramePointer(0, 1)) + expectFramePointer(xArg, new FramePointer(0, 2)) + } + + "attach frame pointers to self argument" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Type + | instance_method self = self.data + 42 + |""".stripMargin.preprocessModule.analyse + val selfArg = findIRElement[DefinitionArgument.Specified](ir, _.name.isInstanceOf[Name.Self]) + expectFramePointer(selfArg, new FramePointer(0, 1)) + } } /** Find the first IR element of the given `T` type by the given `filterCondition`. From dae42c307b6196a0a7582e69e8445098a57d3bec Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 7 Aug 2024 13:02:53 +0200 Subject: [PATCH 16/39] IRDumperPass is run as the last compiler pass This ensure that one can see all the metadata on the IR. --- .../src/main/scala/org/enso/compiler/Passes.scala | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala index 57508ad9826c..34ef9011b995 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/Passes.scala @@ -51,11 +51,7 @@ class Passes(config: CompilerConfig) { PrivateModuleAnalysis.INSTANCE, PrivateConstructorAnalysis.INSTANCE ) - } else List()) ++ (if (config.dumpIrs) { - List( - IRDumperPass.INSTANCE - ) - } else List()) + } else List()) ++ List( ShadowedPatternFields, UnreachableMatchBranches, @@ -108,7 +104,9 @@ class Passes(config: CompilerConfig) { List( TypeInference.INSTANCE ) - } else Nil) + } else Nil) ++ (if (config.dumpIrs) { + List(IRDumperPass.INSTANCE) + } else Nil) ) /** A list of the compiler phases, in the order they should be run. From 3fdec42047acbc20d0a4894476d602ad8bd2760e Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 7 Aug 2024 14:03:52 +0200 Subject: [PATCH 17/39] IRDumper can dump alias analysis metadata --- .../java/org/enso/compiler/dump/IRDumper.java | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java index a53050db320c..dd3babe25fd4 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java @@ -33,6 +33,10 @@ import org.enso.compiler.data.BindingsMap.ResolvedPolyglotField; import org.enso.compiler.data.BindingsMap.ResolvedPolyglotSymbol; import org.enso.compiler.data.BindingsMap.ResolvedType; +import org.enso.compiler.pass.analyse.alias.Graph; +import org.enso.compiler.pass.analyse.alias.Info; +import org.enso.compiler.pass.analyse.alias.Info$Scope$Child; +import org.enso.compiler.pass.analyse.alias.Info$Scope$Root; import org.enso.compiler.pass.resolve.FullyQualifiedNames.FQNResolution; import org.enso.compiler.pass.resolve.FullyQualifiedNames.ResolvedLibrary; import org.enso.compiler.pass.resolve.FullyQualifiedNames.ResolvedModule; @@ -607,13 +611,61 @@ private void createPassDataGraph(IR ir) { addNode(bmNode); createEdge(ir, bindingsMap, "BindingsMap"); } - // The rest is ignored + case Info.Occurrence occurence -> { + bldr.addLabelLine("occurenceId: " + occurence.id()); + addNode(bldr.build()); + createEdge(ir, occurence, "Alias.Info.Occurence"); + } + case Info$Scope$Root rootScope -> { + addAliasGraphScopeLabels(bldr, rootScope.graph().rootScope()); + var aliasNode = bldr.build(); + addNode(aliasNode); + createEdge(ir, rootScope, "Alias.Info.Scope.Root"); + } + case Info$Scope$Child childScope -> { + addAliasGraphScopeLabels(bldr, childScope.scope()); + var aliasNode = bldr.build(); + addNode(aliasNode); + createEdge(ir, childScope, "Alias.Info.Scope.Child"); + } + // The rest is ignored default -> {} } return null; }); } + private void addAliasGraphScopeLabels(GraphVizNode.Builder bldr, Graph.Scope scope) { + var parent = scope.parent(); + if (parent.isDefined()) { + var parentId = Utils.id(parent.get()); + bldr.addLabelLine("parent: " + parentId); + } else { + bldr.addLabelLine("parent: null"); + } + var occurences = scope.occurrences(); + if (occurences.isEmpty()) { + bldr.addLabelLine("occurrences: []"); + } else { + bldr.addLabelLine("occurrences: "); + occurences.values().foreach(occ -> { + bldr.addLabelLine(" - " + occ); + return null; + }); + } + var childScopes = scope.childScopes(); + if (childScopes.isEmpty()) { + bldr.addLabelLine("childScopes: []"); + } else { + bldr.addLabelLine("childScopes: "); + childScopes.foreach(childScope -> { + var id = Utils.id(childScope); + bldr.addLabelLine(" - " + id); + return null; + }); + } + } + private void addNode(GraphVizNode node) { var isNodeAlreadyDefined = nodes.stream().anyMatch(n -> n.equals(node)); if (isNodeAlreadyDefined) { From 4427f6f1436ae247c48e839fe248c18ff4a01bc1 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 7 Aug 2024 17:19:59 +0200 Subject: [PATCH 18/39] FramePointerMeta has meaningful toString --- .../org/enso/compiler/pass/analyse/FramePointerAnalysis.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 0e0877e8ee19..c81a707aee84 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -91,7 +91,7 @@ case object FramePointerAnalysis extends IRPass { case Name.Self(loc, synthetic, _, _) if loc.isEmpty && synthetic => // synthetic self argument has occurrence attached, but there is no Occurence.Def for it. // So we have to handle it specially. - updateMeta(arg, new FramePointer(0, 1)) + updateMeta(arg, new FramePointer(0, 1)) case _ => maybeAttachFramePointer(arg, graph) } @@ -320,5 +320,7 @@ case object FramePointerAnalysis extends IRPass { override def restoreFromSerialization( compiler: CompilerContext ): Option[ProcessingPass.Metadata] = Some(this) + + override def toString: String = s"FramePointerMeta($framePointer)" } } From dd484ccec4d6625c715709fbeddd38cb4a4653e7 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 7 Aug 2024 18:16:02 +0200 Subject: [PATCH 19/39] FramePointerAnalysis process case expressions --- .../pass/analyse/FramePointerAnalysis.scala | 47 +++++++++++++++- .../analyse/FramePointerAnalysisTest.scala | 55 +++++++++++++++++-- 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index c81a707aee84..68728facdb2f 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -8,7 +8,7 @@ import org.enso.compiler.context.{ ModuleContext } import org.enso.compiler.core.{CompilerError, IR} -import org.enso.compiler.core.ir.expression.Application +import org.enso.compiler.core.ir.expression.{Application, Case} import org.enso.compiler.core.ir.{ CallArgument, DefinitionArgument, @@ -16,6 +16,7 @@ import org.enso.compiler.core.ir.{ Function, Module, Name, + Pattern, ProcessingPass } import org.enso.compiler.core.ir.module.scope.Definition @@ -118,7 +119,49 @@ case object FramePointerAnalysis extends IRPass { processExpression(expr, graph) maybeAttachFramePointer(binding, graph) case app: Application => processApplication(app, graph) - case _ => () + case caseExpr: Case.Expr => + processExpression(caseExpr.scrutinee, graph) + caseExpr.branches.foreach { branch => + processCaseBranch(branch) + } + case _ => () + } + } + + private def processCaseBranch( + branch: Case.Branch + ): Unit = { + getAliasAnalysisGraph(branch) match { + case None => + throw new CompilerError( + "An alias analysis graph is expected on " + branch + ) + case Some(graph) => + processExpression(branch.expression, graph) + processCasePattern(branch.pattern, graph) + } + } + + /** @param graph Graph fetched from the corresponding Case.Branch + */ + private def processCasePattern( + pattern: Pattern, + graph: Graph + ): Unit = { + pattern match { + case name: Pattern.Name => + processExpression(name.name, graph) + case lit: Pattern.Literal => + processExpression(lit.literal, graph) + case tp: Pattern.Type => + processExpression(tp.name, graph) + processExpression(tp.tpe, graph) + case ctor: Pattern.Constructor => + processExpression(ctor.constructor, graph) + ctor.fields.foreach { field => + processCasePattern(field, graph) + } + case _: Pattern.Documentation => () } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 332325523f96..497d284c30a8 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -217,8 +217,11 @@ class FramePointerAnalysisTest extends CompilerTest { | static_method = 42 |""".stripMargin.preprocessModule.analyse // synthetic self argument does not have location and is `synthetic == true`. - val syntheticSelfArg = findIRElement[DefinitionArgument.Specified](ir, _.name match { - case Name.Self(loc, synthetic, _, _) if loc.isEmpty && synthetic => true + val syntheticSelfArg = findIRElement[DefinitionArgument.Specified]( + ir, + _.name match { + case Name.Self(loc, synthetic, _, _) if loc.isEmpty && synthetic => + true case _ => false } ) @@ -233,12 +236,16 @@ class FramePointerAnalysisTest extends CompilerTest { | static_method x = 42 |""".stripMargin.preprocessModule.analyse // synthetic self argument does not have location and is `synthetic == true`. - val syntheticSelfArg = findIRElement[DefinitionArgument.Specified](ir, _.name match { - case Name.Self(loc, synthetic, _, _) if loc.isEmpty && synthetic => true + val syntheticSelfArg = findIRElement[DefinitionArgument.Specified]( + ir, + _.name match { + case Name.Self(loc, synthetic, _, _) if loc.isEmpty && synthetic => + true case _ => false } ) - val xArg = findIRElement[DefinitionArgument.Specified](ir, _.name.name == "x") + val xArg = + findIRElement[DefinitionArgument.Specified](ir, _.name.name == "x") expectFramePointer(syntheticSelfArg, new FramePointer(0, 1)) expectFramePointer(xArg, new FramePointer(0, 2)) } @@ -250,9 +257,45 @@ class FramePointerAnalysisTest extends CompilerTest { |type My_Type | instance_method self = self.data + 42 |""".stripMargin.preprocessModule.analyse - val selfArg = findIRElement[DefinitionArgument.Specified](ir, _.name.isInstanceOf[Name.Self]) + val selfArg = findIRElement[DefinitionArgument.Specified]( + ir, + _.name.isInstanceOf[Name.Self] + ) expectFramePointer(selfArg, new FramePointer(0, 1)) } + + "attach frame pointers to internal variables" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Type + | + |method x = + | case x of + | _ : My_Type -> 42 + | _ -> 0 + |""".stripMargin.preprocessModule.analyse + // = (FORCE x) + // case (FORCE ) of + // : My_Type -> 42 + // -> 0 + val allOcc = collectAllOccurences(ir) + val internal0 = + findIRElement[Expression.Binding](ir, _.name.name == "") + val internal1 = findIRElement[Name.Literal](ir, _.name == "") + val internal2 = findIRElement[Name.Literal](ir, _.name == "") + // internal1 and internal2 are in different scopes. + withClue( + " is assigned after x is assigned and x has FramePointer(0, 2)" + ) { + expectFramePointer(internal0, new FramePointer(0, 3)) + } + withClue(" and are in different scopes") { + expectFramePointer(internal1, new FramePointer(0, 1)) + expectFramePointer(internal2, new FramePointer(0, 1)) + } + allOcc shouldNot be(null) + } } /** Find the first IR element of the given `T` type by the given `filterCondition`. From db7b6455f2029e554a66c230439d8077b60d1fe5 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 12 Aug 2024 12:52:02 +0200 Subject: [PATCH 20/39] Fix javadoc links --- .../org/enso/compiler/context/NameResolutionAlgorithm.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java index 3e4b19acc7c4..970eb5d4a927 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java @@ -15,9 +15,9 @@ *

The same logic is needed in two places: * *

    - *
  1. in the runtime ({@link org.enso.interpreter.runtime.IrToTruffle.processName}), + *
  2. in the runtime ({@link org.enso.interpreter.runtime.IrToTruffle.ExpressionProcessor#processName}), *
  3. in the type checker ({@link - * org.enso.compiler.pass.analyse.types.TypeInference.processName}). + * org.enso.compiler.pass.analyse.types.TypePropagation#processName}). *
* *

To ensure that all usages stay consistent, they should all rely on the logic implemented in From 6c2aadfdf6dfcf35e81f0e14b78894b293193f80 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 12 Aug 2024 18:52:41 +0200 Subject: [PATCH 21/39] FramePointer is attached to GenericAnnotation expression --- .../pass/analyse/FramePointerAnalysis.scala | 10 +++++ .../analyse/FramePointerAnalysisTest.scala | 39 ++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 68728facdb2f..feee73861e07 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -7,6 +7,7 @@ import org.enso.compiler.context.{ LocalScope, ModuleContext } +import org.enso.compiler.core.ir.Name.GenericAnnotation import org.enso.compiler.core.{CompilerError, IR} import org.enso.compiler.core.ir.expression.{Application, Case} import org.enso.compiler.core.ir.{ @@ -79,6 +80,15 @@ case object FramePointerAnalysis extends IRPass { } case _ => () } + case annot: GenericAnnotation => + getAliasAnalysisGraph(annot) match { + case Some(annotGraph) => + processExpression(annot.expression, annotGraph) + case None => + throw new CompilerError( + s"No alias analysis graph found for annotation $annot" + ) + } case _ => () } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 497d284c30a8..78079c392033 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -3,7 +3,13 @@ package org.enso.compiler.test.pass.analyse import org.enso.compiler.Passes import org.enso.compiler.context.{FramePointer, FreshNameSupply, ModuleContext} import org.enso.compiler.core.IR -import org.enso.compiler.core.ir.{DefinitionArgument, Expression, Module, Name} +import org.enso.compiler.core.ir.{ + CallArgument, + DefinitionArgument, + Expression, + Module, + Name +} import org.enso.compiler.core.Implicits.AsMetadata import org.enso.compiler.pass.analyse.alias.{Graph, Info} import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} @@ -296,6 +302,33 @@ class FramePointerAnalysisTest extends CompilerTest { } allOcc shouldNot be(null) } + + "attach frame pointers to arguments in annotation" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type List + | @index (t-> t + 1) + | at self index = + | 42 + |""".stripMargin.preprocessModule.analyse + val tDefArg = findIRElement[DefinitionArgument.Specified]( + ir, + arg => arg.name.name == "t" + ) + val tUseArg = findIRElement[CallArgument.Specified]( + ir, + arg => { + arg.value match { + case lit: Name.Literal => + lit.name == "t" + case _ => false + } + } + ) + expectFramePointer(tDefArg, new FramePointer(0, 1)) + expectFramePointer(tUseArg.value, new FramePointer(1, 1)) + } } /** Find the first IR element of the given `T` type by the given `filterCondition`. @@ -324,7 +357,9 @@ class FramePointerAnalysisTest extends CompilerTest { ir: IR, framePointer: FramePointer ): Unit = { - withClue("FramePointerAnalysis metadata should be attached to the IR") { + withClue( + "FramePointerAnalysis metadata should be attached to the IR " + ir + ) { ir.passData().get(FramePointerAnalysis) shouldBe defined } ir From 9da19fe8ec675668b59189232c816b642fd17f04 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 12 Aug 2024 18:52:57 +0200 Subject: [PATCH 22/39] IRDumper handles case type patterns --- .../java/org/enso/compiler/dump/IRDumper.java | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java index dd3babe25fd4..092b1fc77b9f 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java @@ -390,11 +390,13 @@ private void createIRGraph(Pattern pattern) { } } case Pattern.Type tp -> { + addNode(bldr.build()); var name = tp.name(); var tpe = tp.tpe(); - bldr.addLabelLine("name: " + name.name()); - bldr.addLabelLine("tpe: " + tpe.name()); - addNode(bldr.build()); + createIRGraph(name); + createIRGraph(tpe); + createEdge(tp, name, "name"); + createEdge(tp, tpe, "tpe"); } case Pattern.Literal litPat -> { addNode(bldr.build()); @@ -611,7 +613,7 @@ private void createPassDataGraph(IR ir) { addNode(bmNode); createEdge(ir, bindingsMap, "BindingsMap"); } - case Info.Occurrence occurence -> { + case Info.Occurrence occurence -> { bldr.addLabelLine("occurenceId: " + occurence.id()); addNode(bldr.build()); createEdge(ir, occurence, "Alias.Info.Occurence"); @@ -628,7 +630,7 @@ private void createPassDataGraph(IR ir) { addNode(aliasNode); createEdge(ir, childScope, "Alias.Info.Scope.Child"); } - // The rest is ignored + // The rest is ignored default -> {} } return null; @@ -648,21 +650,25 @@ private void addAliasGraphScopeLabels(GraphVizNode.Builder bldr, Graph.Scope sco bldr.addLabelLine("occurrences: []"); } else { bldr.addLabelLine("occurrences: "); - occurences.values().foreach(occ -> { - bldr.addLabelLine(" - " + occ); - return null; - }); + occurences + .values() + .foreach( + occ -> { + bldr.addLabelLine(" - " + occ); + return null; + }); } var childScopes = scope.childScopes(); if (childScopes.isEmpty()) { bldr.addLabelLine("childScopes: []"); } else { bldr.addLabelLine("childScopes: "); - childScopes.foreach(childScope -> { - var id = Utils.id(childScope); - bldr.addLabelLine(" - " + id); - return null; - }); + childScopes.foreach( + childScope -> { + var id = Utils.id(childScope); + bldr.addLabelLine(" - " + id); + return null; + }); } } From 10b04108e6cbc5c93b5a358264923f9d9bebcc0f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Mon, 12 Aug 2024 19:51:03 +0200 Subject: [PATCH 23/39] FramePointer is attached to argument default value expressions --- .../pass/analyse/FramePointerAnalysis.scala | 12 +++++++++ .../analyse/FramePointerAnalysisTest.scala | 25 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index feee73861e07..295c5fafc460 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -106,6 +106,18 @@ case object FramePointerAnalysis extends IRPass { case _ => maybeAttachFramePointer(arg, graph) } + arg.defaultValue match { + case Some(defaultValue) => + getAliasAnalysisGraph(defaultValue) match { + case Some(defaultValueGraph) => + processExpression(defaultValue, defaultValueGraph) + case None => + // If there is no alias analysis graph for the default value, it means that the + // default value is some primitive IR, like Text.Literal. In that case, we don't do anything. + () + } + case None => () + } } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 78079c392033..795c30438c95 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -329,6 +329,31 @@ class FramePointerAnalysisTest extends CompilerTest { expectFramePointer(tDefArg, new FramePointer(0, 1)) expectFramePointer(tUseArg.value, new FramePointer(1, 1)) } + + "attach frame pointers to argument default values" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |method fn=(\x -> x + 1) = + | fn 42 + |""".stripMargin.preprocessModule.analyse + val xDefArg = findIRElement[DefinitionArgument.Specified]( + ir, + arg => arg.name.name == "x" + ) + val xUseArg = findIRElement[CallArgument.Specified]( + ir, + arg => { + arg.value match { + case lit: Name.Literal => + lit.name == "x" + case _ => false + } + } + ) + expectFramePointer(xDefArg, new FramePointer(0, 1)) + expectFramePointer(xUseArg.value, new FramePointer(1, 1)) + } } /** Find the first IR element of the given `T` type by the given `filterCondition`. From a46c3b701161ca11ad9e817496d08190ee09d6a3 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 13 Aug 2024 12:23:32 +0200 Subject: [PATCH 24/39] Add test for default argument value metadata --- .../analyse/FramePointerAnalysisTest.scala | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 795c30438c95..60fc21358eef 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -330,7 +330,7 @@ class FramePointerAnalysisTest extends CompilerTest { expectFramePointer(tUseArg.value, new FramePointer(1, 1)) } - "attach frame pointers to argument default values" in { + "attach frame pointers to argument default value expression" in { implicit val ctx: ModuleContext = mkModuleContext val ir = """ @@ -354,6 +354,22 @@ class FramePointerAnalysisTest extends CompilerTest { expectFramePointer(xDefArg, new FramePointer(0, 1)) expectFramePointer(xUseArg.value, new FramePointer(1, 1)) } + + "does not attach frame pointer to argument default value literal" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |method lit="Some Text" = + | lit + |""".stripMargin.preprocessModule.analyse + val litDefArg = findIRElement[DefinitionArgument.Specified]( + ir, + arg => arg.name.name == "lit" + ) + litDefArg.defaultValue.get + .passData() + .get(FramePointerAnalysis) shouldNot be(defined) + } } /** Find the first IR element of the given `T` type by the given `filterCondition`. From cc211f9a15b5ec30ba8f42cb7290d318c6975b3c Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 13 Aug 2024 12:57:38 +0200 Subject: [PATCH 25/39] Attach frame pointer to default value expressions even if they have no alias graph --- .../pass/analyse/FramePointerAnalysis.scala | 4 +- .../analyse/FramePointerAnalysisTest.scala | 45 ++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 295c5fafc460..83ba64f091bf 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -112,9 +112,7 @@ case object FramePointerAnalysis extends IRPass { case Some(defaultValueGraph) => processExpression(defaultValue, defaultValueGraph) case None => - // If there is no alias analysis graph for the default value, it means that the - // default value is some primitive IR, like Text.Literal. In that case, we don't do anything. - () + processExpression(defaultValue, graph) } case None => () } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 60fc21358eef..2060fd6c0d67 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -330,7 +330,7 @@ class FramePointerAnalysisTest extends CompilerTest { expectFramePointer(tUseArg.value, new FramePointer(1, 1)) } - "attach frame pointers to argument default value expression" in { + "attach frame pointers to argument default value expression (1)" in { implicit val ctx: ModuleContext = mkModuleContext val ir = """ @@ -355,6 +355,49 @@ class FramePointerAnalysisTest extends CompilerTest { expectFramePointer(xUseArg.value, new FramePointer(1, 1)) } + "attach frame pointer to argument default value expression using previous argument" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |method x y=(x + 1) = + | 42 + |""".stripMargin.preprocessModule.analyse + val allOcc = collectAllOccurences(ir) + val fps = collectAllFramePointers(ir) + // `xLit` is the literal used in the `(x + 1)` expression + val xLit = findIRElement[Name.Literal]( + ir, + lit => { + lit.name == "x" && + lit.location.isDefined && + lit.location.get.location().start() == 13 && + lit.location.get.location().end() == 14 + } + ) + expectFramePointer(xLit, new FramePointer(1, 2)) + allOcc shouldNot be(null) + fps shouldNot be(null) + } + + "attach frame pointer to argument default value expression (2)" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |find data_link_instance (if_not_supported = (Error.throw (Illegal_Argument.Error "The "+(data_link_name data_link_instance)+" cannot be opened as a stream."))) = + | 42 + |""".stripMargin.preprocessModule.analyse + val dataLinkInstanceLit = findIRElement[Name.Literal]( + ir, + lit => { + lit.name == "data_link_instance" && + lit.location.isDefined && + lit.location.get.location().start() == 105 && + lit.location.get.location().end() == 123 + } + ) + expectFramePointer(dataLinkInstanceLit, new FramePointer(5, 2)) + } + "does not attach frame pointer to argument default value literal" in { implicit val ctx: ModuleContext = mkModuleContext val ir = From db92068d8e89a0974c165d556994743d751d60bd Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 13 Aug 2024 13:23:53 +0200 Subject: [PATCH 26/39] FramePointerAnalysis can run inline on expression --- .../pass/analyse/FramePointerAnalysis.scala | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 83ba64f091bf..cdd72ede6fe4 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -350,13 +350,22 @@ case object FramePointerAnalysis extends IRPass { getAliasAnalysisMeta(ir).map(_.graph) } - /** Not implemented for this pass. + /** @inheritdoc */ override def runExpression( - ir: Expression, + exprIr: Expression, inlineContext: InlineContext ): Expression = { - ir + inlineContext.localScope match { + case None => + throw new CompilerError( + "Local scope must be provided for frame pointer analysis" + ) + case Some(localScope) => + val graph = localScope.aliasingGraph + processExpression(exprIr, graph) + exprIr + } } // === Pass Configuration =================================================== From ce74f86d9b1e4e65daec202ca5a0015de9b4e32f Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Tue, 13 Aug 2024 17:58:15 +0200 Subject: [PATCH 27/39] Fixes after rebase --- .../java/org/enso/compiler/dump/IRDumper.java | 12 +++---- .../pass/analyse/FramePointerAnalysis.scala | 21 ++++++------ .../analyse/FramePointerAnalysisTest.scala | 33 +++++++++++-------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java index 092b1fc77b9f..93d3077ddb2e 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java @@ -33,10 +33,8 @@ import org.enso.compiler.data.BindingsMap.ResolvedPolyglotField; import org.enso.compiler.data.BindingsMap.ResolvedPolyglotSymbol; import org.enso.compiler.data.BindingsMap.ResolvedType; -import org.enso.compiler.pass.analyse.alias.Graph; -import org.enso.compiler.pass.analyse.alias.Info; -import org.enso.compiler.pass.analyse.alias.Info$Scope$Child; -import org.enso.compiler.pass.analyse.alias.Info$Scope$Root; +import org.enso.compiler.pass.analyse.alias.AliasMetadata; +import org.enso.compiler.pass.analyse.alias.graph.Graph; import org.enso.compiler.pass.resolve.FullyQualifiedNames.FQNResolution; import org.enso.compiler.pass.resolve.FullyQualifiedNames.ResolvedLibrary; import org.enso.compiler.pass.resolve.FullyQualifiedNames.ResolvedModule; @@ -613,18 +611,18 @@ private void createPassDataGraph(IR ir) { addNode(bmNode); createEdge(ir, bindingsMap, "BindingsMap"); } - case Info.Occurrence occurence -> { + case AliasMetadata.Occurrence occurence -> { bldr.addLabelLine("occurenceId: " + occurence.id()); addNode(bldr.build()); createEdge(ir, occurence, "Alias.Info.Occurence"); } - case Info$Scope$Root rootScope -> { + case AliasMetadata.RootScope rootScope -> { addAliasGraphScopeLabels(bldr, rootScope.graph().rootScope()); var aliasNode = bldr.build(); addNode(aliasNode); createEdge(ir, rootScope, "Alias.Info.Scope.Root"); } - case Info$Scope$Child childScope -> { + case AliasMetadata.ChildScope childScope -> { addAliasGraphScopeLabels(bldr, childScope.scope()); var aliasNode = bldr.build(); addNode(aliasNode); diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index cdd72ede6fe4..f5f4f12a56b5 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -24,7 +24,8 @@ import org.enso.compiler.core.ir.module.scope.Definition import org.enso.compiler.core.ir.module.scope.definition.Method import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.IRPass.IRMetadata -import org.enso.compiler.pass.analyse.alias.{Graph, Info} +import org.enso.compiler.pass.analyse.alias.AliasMetadata +import org.enso.compiler.pass.analyse.alias.graph.{Graph, GraphOccurrence} /** This pass attaches [[FramePointer]] as metadata to all the IR elements that already * have [[org.enso.compiler.pass.analyse.alias.Info.Occurrence]] attached. @@ -233,11 +234,11 @@ case object FramePointerAnalysis extends IRPass { graph: Graph ): Unit = { getAliasAnalysisMeta(ir) match { - case Some(Info.Occurrence(_, id)) => + case Some(AliasMetadata.Occurrence(_, id)) => graph.scopeFor(id) match { case Some(scope) => graph.getOccurrence(id) match { - case Some(use: Graph.Occurrence.Use) => + case Some(use: GraphOccurrence.Use) => // Use is allowed to read a variable from some parent scope graph.defLinkFor(use.id) match { case Some(defLink) => @@ -245,7 +246,7 @@ case object FramePointerAnalysis extends IRPass { val defOcc = graph .getOccurrence(defId) .get - .asInstanceOf[Graph.Occurrence.Def] + .asInstanceOf[GraphOccurrence.Def] val defScope = graph.scopeFor(defId).get val parentLevel = getScopeDistance(defScope, scope) val frameSlotIdx = @@ -258,7 +259,7 @@ case object FramePointerAnalysis extends IRPass { // We will not attach any metadata in this case. () } - case Some(defn: Graph.Occurrence.Def) => + case Some(defn: GraphOccurrence.Def) => // The definition cannot write to parent's frame slots. val parentLevel = 0 val frameSlotIdx = getFrameSlotIdxInScope(graph, scope, defn) @@ -285,7 +286,7 @@ case object FramePointerAnalysis extends IRPass { private def getFrameSlotIdxInScope( graph: Graph, scope: Graph.Scope, - defOcc: Graph.Occurrence.Def + defOcc: GraphOccurrence.Def ): Int = { assert( graph.scopeFor(defOcc.id).contains(scope), @@ -329,7 +330,7 @@ case object FramePointerAnalysis extends IRPass { ir: IR ): Option[AliasAnalysis.Metadata] = { ir.passData.get(AliasAnalysis) match { - case Some(aliasInfo: Info) => + case Some(aliasInfo: AliasMetadata) => Some(aliasInfo) case _ => None } @@ -337,10 +338,10 @@ case object FramePointerAnalysis extends IRPass { private def getAliasRootScope( ir: IR - ): Option[Info.Scope.Root] = { + ): Option[AliasMetadata.RootScope] = { ir.passData().get(AliasAnalysis) match { - case Some(root: Info.Scope.Root) => Some(root) - case _ => None + case Some(root: AliasMetadata.RootScope) => Some(root) + case _ => None } } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 2060fd6c0d67..d0aa7e38b030 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -11,9 +11,14 @@ import org.enso.compiler.core.ir.{ Name } import org.enso.compiler.core.Implicits.AsMetadata -import org.enso.compiler.pass.analyse.alias.{Graph, Info} +import org.enso.compiler.pass.analyse.alias.graph.{Graph, GraphOccurrence} +import org.enso.compiler.pass.analyse.alias.AliasMetadata import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} -import org.enso.compiler.pass.analyse.{AliasAnalysis, FramePointerAnalysis} +import org.enso.compiler.pass.analyse.{ + alias, + AliasAnalysis, + FramePointerAnalysis +} import org.enso.compiler.test.CompilerTest import scala.reflect.ClassTag @@ -96,16 +101,16 @@ class FramePointerAnalysisTest extends CompilerTest { |""".stripMargin.preprocessModule.analyse val aliasGraph = ir.bindings.head .unsafeGetMetadata(AliasAnalysis, "should exist") - .asInstanceOf[Info.Scope.Root] + .asInstanceOf[AliasMetadata.RootScope] .graph val xDefIr = findAssociatedIr( - aliasGraph.symbolToIds[Graph.Occurrence.Def]("x").head, + aliasGraph.symbolToIds[GraphOccurrence.Def]("x").head, ir ) expectFramePointer(xDefIr, new FramePointer(0, 1)) val xUseIr = findAssociatedIr( - aliasGraph.symbolToIds[Graph.Occurrence.Use]("x").head, + aliasGraph.symbolToIds[GraphOccurrence.Use]("x").head, ir ) withClue( @@ -116,7 +121,7 @@ class FramePointerAnalysisTest extends CompilerTest { } val plusUseIr = findAssociatedIr( - aliasGraph.symbolToIds[Graph.Occurrence.Use]("+").head, + aliasGraph.symbolToIds[GraphOccurrence.Use]("+").head, ir ) withClue( @@ -141,29 +146,29 @@ class FramePointerAnalysisTest extends CompilerTest { |""".stripMargin.preprocessModule.analyse val mainScope = ir.bindings.head .unsafeGetMetadata(AliasAnalysis, "should exist") - .asInstanceOf[Info.Scope.Root] + .asInstanceOf[AliasMetadata.RootScope] val allFps = collectAllFramePointers(ir) allFps.size shouldBe 4 val xDefIr = findAssociatedIr( - mainScope.graph.symbolToIds[Graph.Occurrence.Def]("x").head, + mainScope.graph.symbolToIds[GraphOccurrence.Def]("x").head, ir ) expectFramePointer(xDefIr, new FramePointer(0, 1)) val nestedDefIr = findAssociatedIr( - mainScope.graph.symbolToIds[Graph.Occurrence.Def]("nested").head, + mainScope.graph.symbolToIds[GraphOccurrence.Def]("nested").head, ir ) expectFramePointer(nestedDefIr, new FramePointer(0, 2)) val xUseIr = findAssociatedIr( - mainScope.graph.symbolToIds[Graph.Occurrence.Use]("x").head, + mainScope.graph.symbolToIds[GraphOccurrence.Use]("x").head, ir ) expectFramePointer(xUseIr, new FramePointer(2, 1)) val nestedUseIr = findAssociatedIr( - mainScope.graph.symbolToIds[Graph.Occurrence.Use]("nested").head, + mainScope.graph.symbolToIds[GraphOccurrence.Use]("nested").head, ir ) expectFramePointer(nestedUseIr, new FramePointer(0, 2)) @@ -457,7 +462,7 @@ class FramePointerAnalysisTest extends CompilerTest { ): IR = { val irs = moduleIr.preorder().collect { childIr => childIr.getMetadata(AliasAnalysis) match { - case Some(Info.Occurrence(_, occId)) if occId == id => + case Some(AliasMetadata.Occurrence(_, occId)) if occId == id => childIr } } @@ -471,10 +476,10 @@ class FramePointerAnalysisTest extends CompilerTest { private def collectAllOccurences( ir: IR - ): List[(IR, Info.Occurrence)] = { + ): List[(IR, AliasMetadata.Occurrence)] = { ir.preorder().flatMap { childIr => childIr.getMetadata(AliasAnalysis) match { - case Some(occMeta: Info.Occurrence) => + case Some(occMeta: alias.AliasMetadata.Occurrence) => Some((childIr, occMeta)) case _ => None } From 9e251716eace4a3cc6ab4dc258366e78aefe897b Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 14 Aug 2024 17:31:17 +0200 Subject: [PATCH 28/39] Fix annotation processing --- .../pass/analyse/FramePointerAnalysis.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index f5f4f12a56b5..7f0861e69b45 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -76,7 +76,7 @@ case object FramePointerAnalysis extends IRPass { } processArgumentDefs(member.arguments, memberGraph) member.annotations.foreach { annotation => - processExpression(annotation.expression, memberGraph) + processAnnotation(annotation, memberGraph) } } case _ => () @@ -84,7 +84,7 @@ case object FramePointerAnalysis extends IRPass { case annot: GenericAnnotation => getAliasAnalysisGraph(annot) match { case Some(annotGraph) => - processExpression(annot.expression, annotGraph) + processAnnotation(annot, annotGraph) case None => throw new CompilerError( s"No alias analysis graph found for annotation $annot" @@ -94,6 +94,18 @@ case object FramePointerAnalysis extends IRPass { } } + private def processAnnotation( + annot: GenericAnnotation, + graph: Graph + ): Unit = { + val annotGraph = getAliasRootScope(annot) match { + case Some(rootScope) => + rootScope.graph + case None => graph + } + processExpression(annot.expression, annotGraph) + } + private def processArgumentDefs( args: List[DefinitionArgument], graph: Graph From f01eef0554cdfdbc9f032e0eea4010693df8d7ca Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 14 Aug 2024 17:32:13 +0200 Subject: [PATCH 29/39] Add tests for no frame pointers on global symbol usages --- .../analyse/FramePointerAnalysisTest.scala | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index d0aa7e38b030..7a4cc66f5d47 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -19,6 +19,7 @@ import org.enso.compiler.pass.analyse.{ AliasAnalysis, FramePointerAnalysis } +import org.enso.compiler.pass.resolve.GlobalNames import org.enso.compiler.test.CompilerTest import scala.reflect.ClassTag @@ -418,6 +419,67 @@ class FramePointerAnalysisTest extends CompilerTest { .passData() .get(FramePointerAnalysis) shouldNot be(defined) } + + "does not attach frame pointer to name literal that is resolved to global type in the same module" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Type + |method = + | My_Type + |""".stripMargin.preprocessModule.analyse + val myTypeLit = findIRElement[Name.Literal]( + ir, + lit => lit.name == "My_Type" && hasLocation(lit, 27, 34) + ) + withClue("No frame pointer attached to a symbol with global occurence") { + myTypeLit.passData.get(FramePointerAnalysis) shouldNot be(defined) + } + withClue("There is a Use occurence") { + myTypeLit.passData.get(AliasAnalysis) shouldBe defined + } + withClue("There is Resolution attached") { + myTypeLit.passData.get(GlobalNames) shouldBe defined + } + } + + "does not attach frame pointer to name literal in annotation that is resolved to global type in the same module" in { + implicit val ctx: ModuleContext = mkModuleContext + val ir = + """ + |type My_Type + | + |@x My_Type + |method x = + | My_Type + |""".stripMargin.preprocessModule.analyse + // literal used in the annotation + val myTypeLit = findIRElement[Name.Literal]( + ir, + lit => lit.name == "My_Type" && hasLocation(lit, 18, 25) + ) + withClue("No frame pointer attached to a symbol with global occurence") { + myTypeLit.passData.get(FramePointerAnalysis) shouldNot be(defined) + } + withClue("There is a Use occurence") { + myTypeLit.passData.get(AliasAnalysis) shouldBe defined + } + withClue("There is Resolution attached") { + myTypeLit.passData.get(GlobalNames) shouldBe defined + } + } + } + + private def hasLocation( + ir: IR, + start: Int, + end: Int + ): Boolean = { + ir.location() match { + case Some(loc) => + loc.start() == start && loc.end() == end + case None => false + } } /** Find the first IR element of the given `T` type by the given `filterCondition`. From af1b7e8d97032628a7276821f9e3b5cdfcc1165c Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 14 Aug 2024 18:50:32 +0200 Subject: [PATCH 30/39] Do not use AliasAnalysis in IrToTruffle. Also refactor NameResolutionAlgorithm to not use AliasMetadata --- .../context/NameResolutionAlgorithm.java | 40 ++++++++----- .../pass/analyse/types/TypePropagation.java | 9 ++- .../pass/analyse/FramePointerAnalysis.scala | 4 ++ .../interpreter/runtime/IrToTruffle.scala | 56 +++++++++++-------- 4 files changed, 70 insertions(+), 39 deletions(-) diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java index 7321924e544b..dc62bdd49758 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/context/NameResolutionAlgorithm.java @@ -2,10 +2,8 @@ import org.enso.compiler.MetadataInteropHelpers; import org.enso.compiler.core.ConstantsNames; -import org.enso.compiler.core.ir.Name; +import org.enso.compiler.core.ir.Name.Literal; import org.enso.compiler.data.BindingsMap; -import org.enso.compiler.pass.analyse.AliasAnalysis$; -import org.enso.compiler.pass.analyse.alias.AliasMetadata; import org.enso.compiler.pass.resolve.GlobalNames$; import scala.Option; @@ -15,7 +13,8 @@ *

The same logic is needed in two places: * *

    - *
  1. in the runtime ({@link org.enso.interpreter.runtime.IrToTruffle.ExpressionProcessor#processName}), + *
  2. in the runtime ({@link + * org.enso.interpreter.runtime.IrToTruffle.ExpressionProcessor#processName}), *
  3. in the type checker ({@link * org.enso.compiler.pass.analyse.types.TypePropagation#processName}). *
@@ -27,15 +26,23 @@ * @param The type describing a link to a local name in a current scope. * Depending on the context this may be a Graph.Link (in the compiler) or a FramePointer (in the * runtime). + * @param Type of the metadata that is associated with the name IR that is resolved. */ -public abstract class NameResolutionAlgorithm { - public final ResultType resolveName(Name.Literal name) { - AliasMetadata.Occurrence occurrenceMetadata = - MetadataInteropHelpers.getMetadata( - name, AliasAnalysis$.MODULE$, AliasMetadata.Occurrence.class); - var maybeLocalLink = findLocalLink(occurrenceMetadata); - if (maybeLocalLink.isDefined()) { - return resolveLocalName(maybeLocalLink.get()); +public abstract class NameResolutionAlgorithm { + + /** + * Resolves a name to {@code ResultType}. + * + * @param name literal name to be resolved + * @param meta Nullable metadata gathered from the {@code name} IR. + * @return The result of the resolution process. + */ + public final ResultType resolveName(Literal name, MetadataType meta) { + if (meta != null) { + var maybeLocalLink = findLocalLink(meta); + if (maybeLocalLink.isDefined()) { + return resolveLocalName(maybeLocalLink.get()); + } } BindingsMap.Resolution global = @@ -53,8 +60,13 @@ public final ResultType resolveName(Name.Literal name) { return resolveUnresolvedSymbol(name.name()); } - protected abstract Option findLocalLink( - AliasMetadata.Occurrence occurrenceMetadata); + /** + * Finds a local link in the current scope from the given {@code metadata}. + * + * @param metadata Not null. + * @return The local link if found, {@code None} otherwise. + */ + protected abstract Option findLocalLink(MetadataType metadata); protected abstract ResultType resolveLocalName(LocalNameLinkType localLink); diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypePropagation.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypePropagation.java index d9c3493216ee..27f254c31679 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypePropagation.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/types/TypePropagation.java @@ -4,6 +4,7 @@ import static org.enso.compiler.MetadataInteropHelpers.getMetadataOrNull; import java.util.List; +import org.enso.compiler.MetadataInteropHelpers; import org.enso.compiler.context.NameResolutionAlgorithm; import org.enso.compiler.core.CompilerError; import org.enso.compiler.core.IR; @@ -159,7 +160,10 @@ private TypeRepresentation processCaseExpression( private TypeRepresentation processName( Name.Literal literalName, LocalBindingsTyping localBindingsTyping) { var resolver = new CompilerNameResolution(localBindingsTyping); - return resolver.resolveName(literalName); + var occMeta = + MetadataInteropHelpers.getMetadataOrNull( + literalName, AliasAnalysis$.MODULE$, AliasMetadata.Occurrence.class); + return resolver.resolveName(literalName, occMeta); } private TypeRepresentation processLiteral(Literal literal) { @@ -333,7 +337,8 @@ private TypeRepresentation processUnresolvedSymbolApplication( } private class CompilerNameResolution - extends NameResolutionAlgorithm { + extends NameResolutionAlgorithm< + TypeRepresentation, CompilerNameResolution.LinkInfo, AliasMetadata.Occurrence> { private final LocalBindingsTyping localBindingsTyping; private CompilerNameResolution(LocalBindingsTyping localBindingsTyping) { diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 7f0861e69b45..1822ba2b1550 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -388,6 +388,10 @@ case object FramePointerAnalysis extends IRPass { ) extends IRMetadata { override val metadataName: String = "FramePointer" + def parentLevel(): Int = framePointer.parentLevel + + def frameSlotIdx(): Int = framePointer.frameSlotIdx + /** @inheritdoc */ override def duplicate(): Option[Metadata] = { diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index 9bd5ea3aba5a..944c51849019 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -51,6 +51,7 @@ import org.enso.compiler.pass.analyse.{ AliasAnalysis, BindingAnalysis, DataflowAnalysis, + FramePointerAnalysis, TailCall } import org.enso.compiler.pass.analyse.alias.AliasMetadata @@ -302,13 +303,12 @@ class IrToTruffle( val unprocessedArg = atomDefn.arguments(idx) val checkNode = checkAsTypes(unprocessedArg) val arg = argFactory.run(unprocessedArg, idx, checkNode) - val occInfo = unprocessedArg + val fp = unprocessedArg .unsafeGetMetadata( - AliasAnalysis, - "No occurrence on an argument definition." + FramePointerAnalysis, + "No frame pointer on an argument definition." ) - .unsafeAs[alias.AliasMetadata.Occurrence] - val slotIdx = localScope.getVarSlotIdx(occInfo.id) + val slotIdx = fp.frameSlotIdx() argDefs(idx) = arg val readArg = ReadArgumentNode.build( @@ -1823,17 +1823,14 @@ class IrToTruffle( private def processBinding( binding: Expression.Binding ): RuntimeExpression = { - val occInfo = binding + val fp = binding .unsafeGetMetadata( - AliasAnalysis, - "Binding with missing occurrence information." + FramePointerAnalysis, + "Binding with missing frame pointer." ) - .unsafeAs[AliasMetadata.Occurrence] currentVarName = binding.name.name - - val slotIdx = scope.getVarSlotIdx(occInfo.id) - + val slotIdx = fp.frameSlotIdx() setLocation( AssignmentNode.build(this.run(binding.expression, true, true), slotIdx), binding.location @@ -1889,7 +1886,11 @@ class IrToTruffle( val nameExpr = name match { case literalName: Name.Literal => val resolver = new RuntimeNameResolution() - resolver.resolveName(literalName) + val fpMeta = literalName.passData.get(FramePointerAnalysis) match { + case Some(meta: FramePointerAnalysis.FramePointerMeta) => meta + case _ => null + } + resolver.resolveName(literalName, fpMeta) case Name.MethodReference( None, Name.Literal(nameStr, _, _, _, _, _), @@ -1945,11 +1946,22 @@ class IrToTruffle( } private class RuntimeNameResolution - extends NameResolutionAlgorithm[RuntimeExpression, FramePointer] { + extends NameResolutionAlgorithm[ + RuntimeExpression, + FramePointer, + FramePointerAnalysis.FramePointerMeta + ] { override protected def findLocalLink( - occurrenceMetadata: org.enso.compiler.pass.analyse.alias.AliasMetadata.Occurrence - ): Option[FramePointer] = - scope.getFramePointer(occurrenceMetadata.id) + fpMeta: FramePointerAnalysis.FramePointerMeta + ): Option[FramePointer] = { + if (scope.flattenToParent && fpMeta.parentLevel() > 0) { + Some( + new FramePointer(fpMeta.parentLevel() - 1, fpMeta.frameSlotIdx()) + ) + } else { + Some(fpMeta.framePointer) + } + } override protected def resolveLocalName( localLink: FramePointer @@ -2191,14 +2203,12 @@ class IrToTruffle( val checkNode = checkAsTypes(unprocessedArg) val arg = argFactory.run(unprocessedArg, idx, checkNode) argDefinitions(idx) = arg - val occInfo = unprocessedArg + val fp = unprocessedArg .unsafeGetMetadata( - AliasAnalysis, - "No occurrence on an argument definition." + FramePointerAnalysis, + "No frame pointer on an argument definition." ) - .unsafeAs[AliasMetadata.Occurrence] - - val slotIdx = scope.getVarSlotIdx(occInfo.id) + val slotIdx = fp.frameSlotIdx() val readArg = ReadArgumentNode.build( idx, From 9f4e71ffdf03e6777eb212206564f8ffb577b607 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 14 Aug 2024 18:54:14 +0200 Subject: [PATCH 31/39] Remove unused methods from LocalScope --- .../enso/compiler/context/LocalScope.scala | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala index d05d89b3e1de..0c1d441e52d5 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala @@ -73,42 +73,6 @@ class LocalScope( ) } - /** Get a frame slot index for a given identifier. - * - * The identifier must be present in the local scope. - * - * @param id the identifier of a variable definition occurrence from alias - * analysis. - * @return the frame slot index for `id`. - */ - def getVarSlotIdx(id: AliasGraph.Id): Int = { - assert( - localFrameSlotIdxs.contains(id), - "Cannot find " + id + " in " + localFrameSlotIdxs - ) - localFrameSlotIdxs(id) - } - - /** Obtains the frame pointer for a given identifier from the current scope, or from - * any parent scopes. - * - * @param id the identifier of a variable usage occurrence from alias - * analysis - * @return the frame pointer for `id`, if it exists - */ - def getFramePointer(id: AliasGraph.Id): Option[FramePointer] = { - aliasingGraph - .defLinkFor(id) - .flatMap { link => - val slotIdx = allFrameSlotIdxs.get(link.target) - slotIdx.map( - new FramePointer( - if (flattenToParent) link.scopeCount - 1 else link.scopeCount, - _ - ) - ) - } - } /** Collects all the bindings in the current stack of scopes, accounting for * shadowing. From b5f809f29ac8135aca71c549123380c7b356a3ff Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 14 Aug 2024 19:12:02 +0200 Subject: [PATCH 32/39] Remove unused import --- .../main/scala/org/enso/interpreter/runtime/IrToTruffle.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index 944c51849019..c53fbdca986b 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -47,7 +47,6 @@ import org.enso.compiler.data.{BindingsMap, CompilerConfig} import org.enso.compiler.exception.BadPatternMatch import org.enso.compiler.pass.analyse.alias.graph.Graph.{Scope => AliasScope} import org.enso.compiler.pass.analyse.{ - alias, AliasAnalysis, BindingAnalysis, DataflowAnalysis, From c32dc5f04eccfe2348f7d2ddbf6976d92cf5c2b4 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Wed, 14 Aug 2024 19:19:15 +0200 Subject: [PATCH 33/39] fmt --- .../src/main/scala/org/enso/compiler/context/LocalScope.scala | 1 - .../main/scala/org/enso/interpreter/runtime/IrToTruffle.scala | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala index 0c1d441e52d5..fef5292b5677 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala @@ -73,7 +73,6 @@ class LocalScope( ) } - /** Collects all the bindings in the current stack of scopes, accounting for * shadowing. * diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index c53fbdca986b..580c51a4a384 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -2207,7 +2207,7 @@ class IrToTruffle( FramePointerAnalysis, "No frame pointer on an argument definition." ) - val slotIdx = fp.frameSlotIdx() + val slotIdx = fp.frameSlotIdx() val readArg = ReadArgumentNode.build( idx, From e08f77ac8c565c52894132163980b15b1e6b0a43 Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 15 Aug 2024 09:33:30 +0200 Subject: [PATCH 34/39] Fix scala.MatchError for case patterns --- .../org/enso/compiler/pass/analyse/FramePointerAnalysis.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 1822ba2b1550..1e481f9c63a9 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -195,6 +195,7 @@ case object FramePointerAnalysis extends IRPass { processCasePattern(field, graph) } case _: Pattern.Documentation => () + case _ => () } } From f52fe9bfd26c25922b7627d7ff2b8a9976cdfddc Mon Sep 17 00:00:00 2001 From: Pavel Marek Date: Thu, 15 Aug 2024 10:42:31 +0200 Subject: [PATCH 35/39] Do not use absolute code locations in the test. Test on Windows fails because of different length of new lines. --- .../analyse/FramePointerAnalysisTest.scala | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala index 7a4cc66f5d47..f5c3c44c77c0 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/FramePointerAnalysisTest.scala @@ -371,15 +371,12 @@ class FramePointerAnalysisTest extends CompilerTest { val allOcc = collectAllOccurences(ir) val fps = collectAllFramePointers(ir) // `xLit` is the literal used in the `(x + 1)` expression - val xLit = findIRElement[Name.Literal]( + val xLit = findIRElements[Name.Literal]( ir, lit => { - lit.name == "x" && - lit.location.isDefined && - lit.location.get.location().start() == 13 && - lit.location.get.location().end() == 14 + lit.name == "x" } - ) + ).last expectFramePointer(xLit, new FramePointer(1, 2)) allOcc shouldNot be(null) fps shouldNot be(null) @@ -392,15 +389,13 @@ class FramePointerAnalysisTest extends CompilerTest { |find data_link_instance (if_not_supported = (Error.throw (Illegal_Argument.Error "The "+(data_link_name data_link_instance)+" cannot be opened as a stream."))) = | 42 |""".stripMargin.preprocessModule.analyse - val dataLinkInstanceLit = findIRElement[Name.Literal]( + // The literal used at the end of the line + val dataLinkInstanceLit = findIRElements[Name.Literal]( ir, lit => { - lit.name == "data_link_instance" && - lit.location.isDefined && - lit.location.get.location().start() == 105 && - lit.location.get.location().end() == 123 + lit.name == "data_link_instance" } - ) + ).last expectFramePointer(dataLinkInstanceLit, new FramePointer(5, 2)) } @@ -428,10 +423,11 @@ class FramePointerAnalysisTest extends CompilerTest { |method = | My_Type |""".stripMargin.preprocessModule.analyse - val myTypeLit = findIRElement[Name.Literal]( + // `My_TYpe` literal used in the method body + val myTypeLit = findIRElements[Name.Literal]( ir, - lit => lit.name == "My_Type" && hasLocation(lit, 27, 34) - ) + lit => lit.name == "My_Type" + ).last withClue("No frame pointer attached to a symbol with global occurence") { myTypeLit.passData.get(FramePointerAnalysis) shouldNot be(defined) } @@ -454,10 +450,10 @@ class FramePointerAnalysisTest extends CompilerTest { | My_Type |""".stripMargin.preprocessModule.analyse // literal used in the annotation - val myTypeLit = findIRElement[Name.Literal]( + val myTypeLit = findIRElements[Name.Literal]( ir, - lit => lit.name == "My_Type" && hasLocation(lit, 18, 25) - ) + lit => lit.name == "My_Type" + ).apply(1) withClue("No frame pointer attached to a symbol with global occurence") { myTypeLit.passData.get(FramePointerAnalysis) shouldNot be(defined) } @@ -470,18 +466,6 @@ class FramePointerAnalysisTest extends CompilerTest { } } - private def hasLocation( - ir: IR, - start: Int, - end: Int - ): Boolean = { - ir.location() match { - case Some(loc) => - loc.start() == start && loc.end() == end - case None => false - } - } - /** Find the first IR element of the given `T` type by the given `filterCondition`. * @param filterCondition Filter condition will be applied to all the elements of the desired type. * The first element that matches the condition will be returned @@ -502,6 +486,20 @@ class FramePointerAnalysisTest extends CompilerTest { .head } + private def findIRElements[T <: IR: ClassTag]( + rootIr: IR, + filterCondition: T => Boolean + ): List[T] = { + rootIr + .preorder() + .flatMap { + case childIr: T => + Some(childIr).filter(filterCondition) + case _ => None + } + .toList + } + /** Asserts that the given `ir` has the given `framePointer` attached as metadata. */ private def expectFramePointer( From c8faf9fb751bed01fe460e99a8e153bd1922efd5 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Fri, 16 Aug 2024 14:34:48 +0000 Subject: [PATCH 36/39] Delay AliasAnalysis instantiation --- .../enso/compiler/context/LocalScope.scala | 23 +++---- .../compiler/pass/analyse/AliasAnalysis.scala | 14 ++-- .../pass/analyse/DataflowAnalysis.scala | 2 +- .../pass/analyse/FramePointerAnalysis.scala | 2 +- .../enso/interpreter/node/EnsoRootNode.java | 2 +- .../interpreter/runtime/IrToTruffle.scala | 66 ++++++++++--------- 6 files changed, 58 insertions(+), 51 deletions(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala index fef5292b5677..2700d4a7a035 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala @@ -31,9 +31,9 @@ import scala.jdk.CollectionConverters._ */ class LocalScope( final val parentScope: Option[LocalScope], - final val aliasingGraph: AliasGraph, - final val scope: AliasGraph.Scope, - final val dataflowInfo: DataflowAnalysis.Metadata, + final val aliasingGraph: () => AliasGraph, + final val scope: () => AliasGraph.Scope, + final val dataflowInfo: () => DataflowAnalysis.Metadata, final val flattenToParent: Boolean = false, private val parentFrameSlotIdxs: Map[AliasGraph.Id, Int] = Map() ) { @@ -50,7 +50,7 @@ class LocalScope( * * @return a child of this scope */ - def createChild(): LocalScope = createChild(scope.addChild()) + def createChild(): LocalScope = createChild(() => scope().addChild()) /** Creates a child using a known aliasing scope. * @@ -60,7 +60,7 @@ class LocalScope( * @return a child of this scope */ def createChild( - childScope: AliasGraph.Scope, + childScope: () => AliasGraph.Scope, flattenToParent: Boolean = false ): LocalScope = { new LocalScope( @@ -88,7 +88,7 @@ class LocalScope( * internal slots, that are prepended to every frame. */ private def gatherLocalFrameSlotIdxs(): Map[AliasGraph.Id, Int] = { - scope.allDefinitions.zipWithIndex.map { case (definition, i) => + scope().allDefinitions.zipWithIndex.map { case (definition, i) => definition.id -> (i + LocalScope.internalSlotsSize) }.toMap } @@ -105,7 +105,7 @@ class LocalScope( .flatMap(scope => Some(scope.flattenBindingsWithLevel(level + 1))) .getOrElse(Map()) - scope.occurrences.foreach { + scope().occurrences.foreach { case (id, x: GraphOccurrence.Def) => parentResult += x.symbol -> new FramePointer( level, @@ -126,13 +126,14 @@ object LocalScope { * * @return a defaulted local scope */ - def root: LocalScope = { + val root: LocalScope = { val graph = new AliasGraph + val info = DataflowAnalysis.DependencyInfo() new LocalScope( None, - graph, - graph.rootScope, - DataflowAnalysis.DependencyInfo() + () => graph, + () => graph.rootScope, + () => info ) } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala index 0f10a8bf6f2e..c7d7165e2f9e 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala @@ -126,17 +126,19 @@ case object AliasAnalysis extends IRPass { inlineContext.localScope .map { localScope => val scope = - if (shouldWriteState) localScope.scope + if (shouldWriteState) localScope.scope() else - localScope.scope + localScope + .scope() .deepCopy(mutable.Map()) - .withParent(localScope.scope) + .withParent(localScope.scope()) + val ag = localScope.aliasingGraph() val graph = - if (shouldWriteState) localScope.aliasingGraph + if (shouldWriteState) ag else { - val mapping = mutable.Map(localScope.scope -> scope) - localScope.aliasingGraph.deepCopy(mapping) + val mapping = mutable.Map(localScope.scope() -> scope) + ag.deepCopy(mapping) } val result = analyseExpression(ir, graph, scope) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala index e6a62412f09b..a9932d6fbec7 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala @@ -95,7 +95,7 @@ case object DataflowAnalysis extends IRPass { "A valid local scope is required for the inline flow." ) ) - analyseExpression(ir, localScope.dataflowInfo) + analyseExpression(ir, localScope.dataflowInfo()) } /** @inheritdoc */ diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala index 1e481f9c63a9..6d1569b40694 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/FramePointerAnalysis.scala @@ -376,7 +376,7 @@ case object FramePointerAnalysis extends IRPass { "Local scope must be provided for frame pointer analysis" ) case Some(localScope) => - val graph = localScope.aliasingGraph + val graph = localScope.aliasingGraph() processExpression(exprIr, graph) exprIr } diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java index b202ac952629..38c11ee02ed3 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java @@ -65,7 +65,7 @@ protected EnsoRootNode( private static FrameDescriptor buildFrameDescriptor(LocalScope localScope) { var descriptorBuilder = FrameDescriptor.newBuilder(); descriptorBuilder.addSlot(FrameSlotKind.Object, LocalScope.monadicStateSlotName(), null); - for (var definition : ScalaConversions.asJava(localScope.scope().allDefinitions())) { + for (var definition : ScalaConversions.asJava(localScope.scope().apply().allDefinitions())) { descriptorBuilder.addSlot(FrameSlotKind.Illegal, definition.symbol(), null); } descriptorBuilder.defaultValue(DataflowError.UNINITIALIZED); diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index 580c51a4a384..3b605f114504 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -270,21 +270,21 @@ class IrToTruffle( atomCons: AtomConstructor, atomDefn: Definition.Data ): Unit = { - val scopeInfo = atomDefn + def scopeInfo() = atomDefn .unsafeGetMetadata( AliasAnalysis, "No root scope on an atom definition." ) .unsafeAs[AliasMetadata.RootScope] - val dataflowInfo = atomDefn.unsafeGetMetadata( + def dataflowInfo() = atomDefn.unsafeGetMetadata( DataflowAnalysis, "No dataflow information associated with an atom." ) val localScope = new LocalScope( None, - scopeInfo.graph, - scopeInfo.graph.rootScope, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, dataflowInfo ) @@ -333,8 +333,8 @@ class IrToTruffle( scopeElements.mkString(Constants.SCOPE_SEPARATOR) val expressionProcessor = new ExpressionProcessor( scopeName, - scopeInfo.graph, - scopeInfo.graph.rootScope, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, dataflowInfo, atomDefn.name.name ) @@ -373,14 +373,14 @@ class IrToTruffle( } methodDefs.foreach(methodDef => { - val scopeInfo = methodDef + def scopeInfo() = methodDef .unsafeGetMetadata( AliasAnalysis, s"Missing scope information for method " + s"`${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`." ) .unsafeAs[AliasMetadata.RootScope] - val dataflowInfo = methodDef.unsafeGetMetadata( + def dataflowInfo() = methodDef.unsafeGetMetadata( DataflowAnalysis, "Method definition missing dataflow information." ) @@ -414,8 +414,8 @@ class IrToTruffle( cons.getName ++ Constants.SCOPE_SEPARATOR ++ methodDef.methodName.name val expressionProcessor = new ExpressionProcessor( fullMethodDefName, - scopeInfo.graph, - scopeInfo.graph.rootScope, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, dataflowInfo, fullMethodDefName ) @@ -548,7 +548,7 @@ class IrToTruffle( ) val scopeName = scopeElements.mkString(Constants.SCOPE_SEPARATOR) - val scopeInfo = annotation + def scopeInfo() = annotation .unsafeGetMetadata( AliasAnalysis, s"Missing scope information for annotation " + @@ -557,7 +557,7 @@ class IrToTruffle( .mkString(Constants.SCOPE_SEPARATOR) ) .unsafeAs[AliasMetadata.RootScope] - val dataflowInfo = annotation.unsafeGetMetadata( + def dataflowInfo() = annotation.unsafeGetMetadata( DataflowAnalysis, "Missing dataflow information for annotation " + s"${annotation.name} of method " + @@ -566,8 +566,8 @@ class IrToTruffle( ) val expressionProcessor = new ExpressionProcessor( scopeName, - scopeInfo.graph, - scopeInfo.graph.rootScope, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, dataflowInfo, methodDef.methodName.name ) @@ -767,14 +767,14 @@ class IrToTruffle( // Register the conversion definitions in scope conversionDefs.foreach(methodDef => { - val scopeInfo = methodDef + def scopeInfo() = methodDef .unsafeGetMetadata( AliasAnalysis, s"Missing scope information for conversion " + s"`${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`." ) .unsafeAs[AliasMetadata.RootScope] - val dataflowInfo = methodDef.unsafeGetMetadata( + def dataflowInfo() = methodDef.unsafeGetMetadata( DataflowAnalysis, "Method definition missing dataflow information." ) @@ -790,8 +790,8 @@ class IrToTruffle( toOpt.zip(fromOpt).foreach { case (toType, fromType) => val expressionProcessor = new ExpressionProcessor( toType.getName ++ Constants.SCOPE_SEPARATOR ++ methodDef.methodName.name, - scopeInfo.graph, - scopeInfo.graph.rootScope, + () => scopeInfo().graph, + () => scopeInfo().graph.rootScope, dataflowInfo, methodDef.methodName.name ) @@ -1225,9 +1225,9 @@ class IrToTruffle( */ def this( scopeName: String, - graph: AliasGraph, - scope: AliasScope, - dataflowInfo: DataflowAnalysis.Metadata, + graph: () => AliasGraph, + scope: () => AliasScope, + dataflowInfo: () => DataflowAnalysis.Metadata, initialName: String ) = { this( @@ -1246,7 +1246,7 @@ class IrToTruffle( */ def createChild( name: String, - scope: AliasScope, + scope: () => AliasScope, initialName: String ): ExpressionProcessor = { new ExpressionProcessor(this.scope.createChild(scope), name, initialName) @@ -1332,7 +1332,7 @@ class IrToTruffle( */ private def processBlock(block: Expression.Block): RuntimeExpression = { if (block.suspended) { - val scopeInfo = block + def scopeInfo() = block .unsafeGetMetadata( AliasAnalysis, "Missing scope information on block." @@ -1341,7 +1341,7 @@ class IrToTruffle( val childFactory = this.createChild( "suspended-block", - scopeInfo.scope, + () => scopeInfo().scope, "suspended " + currentVarName ) val childScope = childFactory.scope @@ -1446,7 +1446,7 @@ class IrToTruffle( def processCaseBranch( branch: Case.Branch ): Either[BadPatternMatch, BranchNode] = { - val scopeInfo = branch + def scopeInfo() = branch .unsafeGetMetadata( AliasAnalysis, "No scope information on a case branch." @@ -1456,7 +1456,7 @@ class IrToTruffle( val childProcessor = this.createChild( "case_branch", - scopeInfo.scope, + () => scopeInfo().scope, "case " + currentVarName ) @@ -1846,7 +1846,7 @@ class IrToTruffle( function: Function, binding: Boolean ): RuntimeExpression = { - val scopeInfo = function + def scopeInfo() = function .unsafeGetMetadata(AliasAnalysis, "No scope info on a function.") .unsafeAs[AliasMetadata.ChildScope] @@ -1864,7 +1864,11 @@ class IrToTruffle( } val child = - this.createChild(scopeName, scopeInfo.scope, "case " + currentVarName) + this.createChild( + scopeName, + () => scopeInfo().scope, + "case " + currentVarName + ) val fn = child.processFunctionBody( function.arguments, @@ -2434,7 +2438,7 @@ class IrToTruffle( _, _ ) => - val scopeInfo = arg + def scopeInfo() = arg .unsafeGetMetadata( AliasAnalysis, "No scope attached to a call argument." @@ -2452,10 +2456,10 @@ class IrToTruffle( } val childScope = if (shouldCreateClosureRootNode) { - scope.createChild(scopeInfo.scope) + scope.createChild(() => scopeInfo().scope) } else { // Note [Scope Flattening] - scope.createChild(scopeInfo.scope, flattenToParent = true) + scope.createChild(() => scopeInfo().scope, flattenToParent = true) } val argumentExpression = new ExpressionProcessor(childScope, scopeName, initialName) From 32837477440b19d38ea0f1a9c1ffe0e16f9cf8f2 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 17 Aug 2024 07:12:29 +0200 Subject: [PATCH 37/39] scope() is a Function0 --- .../enso/compiler/benchmarks/inline/InlineContextResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/compiler/benchmarks/inline/InlineContextResource.java b/engine/runtime-benchmarks/src/main/java/org/enso/compiler/benchmarks/inline/InlineContextResource.java index 9144a01d3807..db20404da126 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/compiler/benchmarks/inline/InlineContextResource.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/compiler/benchmarks/inline/InlineContextResource.java @@ -16,7 +16,7 @@ public void close() throws IOException { .localScope() .foreach( s -> { - s.scope().removeScopeFromParent(); + s.scope().apply().removeScopeFromParent(); return null; }); } From 32344c1ffb359b53b3a5b233039c6238667be5a6 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 17 Aug 2024 08:57:02 +0200 Subject: [PATCH 38/39] Using provider and lazy val to guarantee idempotent results --- .../inline/InlineContextResource.java | 2 +- .../enso/compiler/context/LocalScope.scala | 15 ++- .../compiler/pass/analyse/AliasAnalysis.scala | 9 +- .../pass/analyse/DataflowAnalysis.scala | 2 +- .../enso/interpreter/node/EnsoRootNode.java | 2 +- .../interpreter/runtime/IrToTruffle.scala | 117 ++++++++++-------- 6 files changed, 82 insertions(+), 65 deletions(-) diff --git a/engine/runtime-benchmarks/src/main/java/org/enso/compiler/benchmarks/inline/InlineContextResource.java b/engine/runtime-benchmarks/src/main/java/org/enso/compiler/benchmarks/inline/InlineContextResource.java index db20404da126..9144a01d3807 100644 --- a/engine/runtime-benchmarks/src/main/java/org/enso/compiler/benchmarks/inline/InlineContextResource.java +++ b/engine/runtime-benchmarks/src/main/java/org/enso/compiler/benchmarks/inline/InlineContextResource.java @@ -16,7 +16,7 @@ public void close() throws IOException { .localScope() .foreach( s -> { - s.scope().apply().removeScopeFromParent(); + s.scope().removeScopeFromParent(); return null; }); } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala index 2700d4a7a035..7571f046ecbe 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala @@ -32,11 +32,14 @@ import scala.jdk.CollectionConverters._ class LocalScope( final val parentScope: Option[LocalScope], final val aliasingGraph: () => AliasGraph, - final val scope: () => AliasGraph.Scope, - final val dataflowInfo: () => DataflowAnalysis.Metadata, + final private val scopeProvider: () => AliasGraph.Scope, + final private val dataflowInfoProvider: () => DataflowAnalysis.Metadata, final val flattenToParent: Boolean = false, private val parentFrameSlotIdxs: Map[AliasGraph.Id, Int] = Map() ) { + lazy val scope: AliasGraph.Scope = scopeProvider() + lazy val dataflowInfo: DataflowAnalysis.Metadata = dataflowInfoProvider() + private lazy val localFrameSlotIdxs: Map[AliasGraph.Id, Int] = gatherLocalFrameSlotIdxs() @@ -50,7 +53,7 @@ class LocalScope( * * @return a child of this scope */ - def createChild(): LocalScope = createChild(() => scope().addChild()) + def createChild(): LocalScope = createChild(() => scope.addChild()) /** Creates a child using a known aliasing scope. * @@ -67,7 +70,7 @@ class LocalScope( Some(this), aliasingGraph, childScope, - dataflowInfo, + () => dataflowInfo, flattenToParent, allFrameSlotIdxs ) @@ -88,7 +91,7 @@ class LocalScope( * internal slots, that are prepended to every frame. */ private def gatherLocalFrameSlotIdxs(): Map[AliasGraph.Id, Int] = { - scope().allDefinitions.zipWithIndex.map { case (definition, i) => + scope.allDefinitions.zipWithIndex.map { case (definition, i) => definition.id -> (i + LocalScope.internalSlotsSize) }.toMap } @@ -105,7 +108,7 @@ class LocalScope( .flatMap(scope => Some(scope.flattenBindingsWithLevel(level + 1))) .getOrElse(Map()) - scope().occurrences.foreach { + scope.occurrences.foreach { case (id, x: GraphOccurrence.Def) => parentResult += x.symbol -> new FramePointer( level, diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala index c7d7165e2f9e..548c78624f72 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala @@ -126,18 +126,17 @@ case object AliasAnalysis extends IRPass { inlineContext.localScope .map { localScope => val scope = - if (shouldWriteState) localScope.scope() + if (shouldWriteState) localScope.scope else - localScope - .scope() + localScope.scope .deepCopy(mutable.Map()) - .withParent(localScope.scope()) + .withParent(localScope.scope) val ag = localScope.aliasingGraph() val graph = if (shouldWriteState) ag else { - val mapping = mutable.Map(localScope.scope() -> scope) + val mapping = mutable.Map(localScope.scope -> scope) ag.deepCopy(mapping) } val result = analyseExpression(ir, graph, scope) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala index a9932d6fbec7..e6a62412f09b 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala @@ -95,7 +95,7 @@ case object DataflowAnalysis extends IRPass { "A valid local scope is required for the inline flow." ) ) - analyseExpression(ir, localScope.dataflowInfo()) + analyseExpression(ir, localScope.dataflowInfo) } /** @inheritdoc */ diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java index 38c11ee02ed3..b202ac952629 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/EnsoRootNode.java @@ -65,7 +65,7 @@ protected EnsoRootNode( private static FrameDescriptor buildFrameDescriptor(LocalScope localScope) { var descriptorBuilder = FrameDescriptor.newBuilder(); descriptorBuilder.addSlot(FrameSlotKind.Object, LocalScope.monadicStateSlotName(), null); - for (var definition : ScalaConversions.asJava(localScope.scope().apply().allDefinitions())) { + for (var definition : ScalaConversions.asJava(localScope.scope().allDefinitions())) { descriptorBuilder.addSlot(FrameSlotKind.Illegal, definition.symbol(), null); } descriptorBuilder.defaultValue(DataflowError.UNINITIALIZED); diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index 3b605f114504..d6f46a659393 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -270,12 +270,14 @@ class IrToTruffle( atomCons: AtomConstructor, atomDefn: Definition.Data ): Unit = { - def scopeInfo() = atomDefn - .unsafeGetMetadata( - AliasAnalysis, - "No root scope on an atom definition." - ) - .unsafeAs[AliasMetadata.RootScope] + def scopeInfo() = { + atomDefn + .unsafeGetMetadata( + AliasAnalysis, + "No root scope on an atom definition." + ) + .unsafeAs[AliasMetadata.RootScope] + } def dataflowInfo() = atomDefn.unsafeGetMetadata( DataflowAnalysis, @@ -373,13 +375,15 @@ class IrToTruffle( } methodDefs.foreach(methodDef => { - def scopeInfo() = methodDef - .unsafeGetMetadata( - AliasAnalysis, - s"Missing scope information for method " + - s"`${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`." - ) - .unsafeAs[AliasMetadata.RootScope] + def scopeInfo() = { + methodDef + .unsafeGetMetadata( + AliasAnalysis, + s"Missing scope information for method " + + s"`${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`." + ) + .unsafeAs[AliasMetadata.RootScope] + } def dataflowInfo() = methodDef.unsafeGetMetadata( DataflowAnalysis, "Method definition missing dataflow information." @@ -548,15 +552,17 @@ class IrToTruffle( ) val scopeName = scopeElements.mkString(Constants.SCOPE_SEPARATOR) - def scopeInfo() = annotation - .unsafeGetMetadata( - AliasAnalysis, - s"Missing scope information for annotation " + - s"${annotation.name} of method " + - scopeElements.init - .mkString(Constants.SCOPE_SEPARATOR) - ) - .unsafeAs[AliasMetadata.RootScope] + def scopeInfo() = { + annotation + .unsafeGetMetadata( + AliasAnalysis, + s"Missing scope information for annotation " + + s"${annotation.name} of method " + + scopeElements.init + .mkString(Constants.SCOPE_SEPARATOR) + ) + .unsafeAs[AliasMetadata.RootScope] + } def dataflowInfo() = annotation.unsafeGetMetadata( DataflowAnalysis, "Missing dataflow information for annotation " + @@ -767,13 +773,15 @@ class IrToTruffle( // Register the conversion definitions in scope conversionDefs.foreach(methodDef => { - def scopeInfo() = methodDef - .unsafeGetMetadata( - AliasAnalysis, - s"Missing scope information for conversion " + - s"`${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`." - ) - .unsafeAs[AliasMetadata.RootScope] + def scopeInfo() = { + methodDef + .unsafeGetMetadata( + AliasAnalysis, + s"Missing scope information for conversion " + + s"`${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`." + ) + .unsafeAs[AliasMetadata.RootScope] + } def dataflowInfo() = methodDef.unsafeGetMetadata( DataflowAnalysis, "Method definition missing dataflow information." @@ -1332,12 +1340,14 @@ class IrToTruffle( */ private def processBlock(block: Expression.Block): RuntimeExpression = { if (block.suspended) { - def scopeInfo() = block - .unsafeGetMetadata( - AliasAnalysis, - "Missing scope information on block." - ) - .unsafeAs[AliasMetadata.ChildScope] + def scopeInfo() = { + block + .unsafeGetMetadata( + AliasAnalysis, + "Missing scope information on block." + ) + .unsafeAs[AliasMetadata.ChildScope] + } val childFactory = this.createChild( "suspended-block", @@ -1446,12 +1456,14 @@ class IrToTruffle( def processCaseBranch( branch: Case.Branch ): Either[BadPatternMatch, BranchNode] = { - def scopeInfo() = branch - .unsafeGetMetadata( - AliasAnalysis, - "No scope information on a case branch." - ) - .unsafeAs[AliasMetadata.ChildScope] + def scopeInfo() = { + branch + .unsafeGetMetadata( + AliasAnalysis, + "No scope information on a case branch." + ) + .unsafeAs[AliasMetadata.ChildScope] + } val childProcessor = this.createChild( @@ -1846,10 +1858,11 @@ class IrToTruffle( function: Function, binding: Boolean ): RuntimeExpression = { - def scopeInfo() = function - .unsafeGetMetadata(AliasAnalysis, "No scope info on a function.") - .unsafeAs[AliasMetadata.ChildScope] - + def scopeInfo() = { + function + .unsafeGetMetadata(AliasAnalysis, "No scope info on a function.") + .unsafeAs[AliasMetadata.ChildScope] + } if (function.body.isInstanceOf[Function]) { throw new CompilerError( "Lambda found directly as function body. It looks like Lambda " + @@ -2438,12 +2451,14 @@ class IrToTruffle( _, _ ) => - def scopeInfo() = arg - .unsafeGetMetadata( - AliasAnalysis, - "No scope attached to a call argument." - ) - .unsafeAs[AliasMetadata.ChildScope] + def scopeInfo() = { + arg + .unsafeGetMetadata( + AliasAnalysis, + "No scope attached to a call argument." + ) + .unsafeAs[AliasMetadata.ChildScope] + } def valueHasSomeTypeCheck() = value.getMetadata(TypeSignatures).isDefined From 38bc732ec1dd3e60280c7b6b5a169ce8d40e8642 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Sat, 17 Aug 2024 09:33:37 +0200 Subject: [PATCH 39/39] LocalScope.root cannot be a singleton --- .../src/main/scala/org/enso/compiler/context/LocalScope.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala index 7571f046ecbe..828c1aac9af3 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/context/LocalScope.scala @@ -129,7 +129,7 @@ object LocalScope { * * @return a defaulted local scope */ - val root: LocalScope = { + def root: LocalScope = { val graph = new AliasGraph val info = DataflowAnalysis.DependencyInfo() new LocalScope(