From 84e59f14030de84d69937212204b35bf3840e8b7 Mon Sep 17 00:00:00 2001 From: Dmitry Bushev Date: Tue, 2 May 2023 15:22:06 +0100 Subject: [PATCH] DataflowAnalysis preserves dependencies order (#6493) close #6324 Changelog - feat: DataflowAnalysis compiler pass preserves the order of dependencies. This way when attaching the visualization to the sub-expression, the engine can find the first cached parent node, and properly invalidate it. - update: runtime visualization test is updated to reproduce the issue # Important Notes The dropdown for the column `"LOCATION"` is available right after the Restaurants project startup. ![2023-05-01-171700_1386x975_scrot](https://user-images.githubusercontent.com/357683/235466166-9d25cfa5-0e39-49a3-9c41-93cda59edb81.png) --- .../RuntimeVisualizationsTest.scala | 25 +++++++++-- .../pass/analyse/DataflowAnalysis.scala | 41 +++++++++++++------ 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala b/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala index 9a755b3b1ce..bc574d48802 100644 --- a/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala +++ b/engine/runtime-with-polyglot/src/test/scala/org/enso/interpreter/test/instrument/RuntimeVisualizationsTest.scala @@ -3138,8 +3138,12 @@ class RuntimeVisualizationsTest val moduleName = "Enso_Test.Test.Main" val metadata = new Metadata - val idX = metadata.addItem(65, 1, "aa") - val idY = metadata.addItem(65, 7, "ab") + val idX = metadata.addItem(65, 1, "aa") + val idY = metadata.addItem(65, 7, "ab") + val idS = metadata.addItem(81, 1) + val idZ = metadata.addItem(91, 5, "ac") + val idZexprS = metadata.addItem(93, 1) + val idZexpr1 = metadata.addItem(95, 1) val code = """type T @@ -3150,7 +3154,11 @@ class RuntimeVisualizationsTest |main = | x = T.C | y = x.inc 7 - | y + | s = 1 + | z = p y s + | z + | + |p x y = x + y |""".stripMargin.linesIterator.mkString("\n") val contents = metadata.appendToCode(code) val mainFile = context.writeMain(contents) @@ -3177,7 +3185,7 @@ class RuntimeVisualizationsTest Api.Request(requestId, Api.PushContextRequest(contextId, item1)) ) context.receiveNIgnorePendingExpressionUpdates( - 5 + 9 ) should contain theSameElementsAs Seq( Api.Response(Api.BackgroundJobsStartedNotification()), Api.Response(requestId, Api.PushContextResponse(contextId)), @@ -3188,6 +3196,15 @@ class RuntimeVisualizationsTest ConstantsGen.INTEGER_BUILTIN, Api.MethodPointer(moduleName, s"$moduleName.T", "inc") ), + TestMessages.update(contextId, idS, ConstantsGen.INTEGER_BUILTIN), + TestMessages.update( + contextId, + idZ, + ConstantsGen.INTEGER_BUILTIN, + Api.MethodPointer(moduleName, moduleName, "p") + ), + TestMessages.update(contextId, idZexprS, ConstantsGen.INTEGER_BUILTIN), + TestMessages.update(contextId, idZexpr1, ConstantsGen.INTEGER_BUILTIN), context.executionComplete(contextId) ) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala index 3e6a170c44c..0ba001c66d5 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/DataflowAnalysis.scala @@ -10,6 +10,7 @@ import org.enso.compiler.exception.CompilerError import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.analyse.DataflowAnalysis.DependencyInfo.Type.asStatic +import scala.collection.immutable.ListSet import scala.collection.mutable /** This pass implements dataflow analysis for Enso. @@ -826,23 +827,39 @@ case object DataflowAnalysis extends IRPass { * @return the set of all associations with `key`, if key exists */ def get(key: DependencyInfo.Type): Option[Set[DependencyInfo.Type]] = { - val visited = mutable.LinkedHashSet[DependencyInfo.Type]() - def go(key: DependencyInfo.Type): Set[DependencyInfo.Type] = { - if (!visited.contains(key)) { - visited += key - - mapping.get(key) match { - case Some(deps) => deps ++ deps.map(go).reduceLeft(_ ++ _) - case None => Set() + @scala.annotation.tailrec + def go( + queue: mutable.Queue[DependencyInfo.Type], + visited: mutable.Set[DependencyInfo.Type], + result: mutable.Set[DependencyInfo.Type] + ): Set[DependencyInfo.Type] = + if (queue.isEmpty) result.to(ListSet) + else { + val elem = queue.dequeue() + if (visited.contains(elem)) go(queue, visited, result) + else { + mapping.get(elem) match { + case Some(deps) => + go( + queue.enqueueAll(deps), + visited.addOne(elem), + result.addAll(deps) + ) + case None => + go(queue, visited.addOne(elem), result) + } } - } else { - Set() } - } if (mapping.contains(key)) { - Some(go(key)) + Some( + go( + mutable.Queue(key), + mutable.HashSet(), + mutable.LinkedHashSet() + ) + ) } else { None }