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)
This commit is contained in:
Dmitry Bushev 2023-05-02 15:22:06 +01:00 committed by GitHub
parent 5eb9c3a843
commit 84e59f1403
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 16 deletions

View File

@ -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)
)

View File

@ -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
}