Un-nest AliasAnalysis Info and Graph to allow easier usage from Java (#9451)

This commit is contained in:
Radosław Waśko 2024-03-18 16:16:24 +01:00 committed by GitHub
parent de9f2764f9
commit 90b3003312
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1000 additions and 922 deletions

View File

@ -1,7 +1,8 @@
package org.enso.compiler.pass.analyse;
import java.io.IOException;
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph;
import org.enso.compiler.pass.analyse.alias.Graph;
import org.enso.compiler.pass.analyse.alias.Info;
import org.enso.compiler.pass.resolve.DocumentationComments;
import org.enso.compiler.pass.resolve.DocumentationComments$;
import org.enso.compiler.pass.resolve.ExpressionAnnotations$;
@ -54,12 +55,24 @@ import scala.Option;
@Persistable(clazz = GenericAnnotations$.class, id = 1216)
@Persistable(clazz = ExpressionAnnotations$.class, id = 1217)
@Persistable(clazz = FullyQualifiedNames$.class, id = 1218)
@Persistable(clazz = AliasAnalysis$Info$Occurrence.class, id = 1261, allowInlining = false)
@Persistable(clazz = AliasAnalysis$Info$Scope$Root.class, id = 1262, allowInlining = false)
@Persistable(clazz = AliasAnalysis$Info$Scope$Child.class, id = 1263, allowInlining = false)
@Persistable(clazz = AliasAnalysis$Graph$Occurrence$Use.class, id = 1264, allowInlining = false)
@Persistable(clazz = AliasAnalysis$Graph$Occurrence$Def.class, id = 1265, allowInlining = false)
@Persistable(clazz = AliasAnalysis$Graph$Link.class, id = 1266, allowInlining = false)
@Persistable(clazz = Info.Occurrence.class, id = 1261, allowInlining = false)
@Persistable(
clazz = org.enso.compiler.pass.analyse.alias.Info$Scope$Root.class,
id = 1262,
allowInlining = false)
@Persistable(
clazz = org.enso.compiler.pass.analyse.alias.Info$Scope$Child.class,
id = 1263,
allowInlining = false)
@Persistable(
clazz = org.enso.compiler.pass.analyse.alias.Graph$Occurrence$Use.class,
id = 1264,
allowInlining = false)
@Persistable(
clazz = org.enso.compiler.pass.analyse.alias.Graph$Occurrence$Def.class,
id = 1265,
allowInlining = false)
@Persistable(clazz = Graph.Link.class, id = 1266, allowInlining = false)
public final class PassPersistance {
private PassPersistance() {}
@ -106,26 +119,22 @@ public final class PassPersistance {
}
@org.openide.util.lookup.ServiceProvider(service = Persistance.class)
public static final class PersistAliasAnalysisGraphScope
extends Persistance<org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope> {
public static final class PersistAliasAnalysisGraphScope extends Persistance<Graph.Scope> {
public PersistAliasAnalysisGraphScope() {
super(org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope.class, false, 1267);
super(Graph.Scope.class, false, 1267);
}
@Override
@SuppressWarnings("unchecked")
protected org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope readObject(Input in)
throws IOException {
protected Graph.Scope readObject(Input in) throws IOException {
var childScopes = in.readInline(scala.collection.immutable.List.class);
var occurrences = (scala.collection.immutable.Set) in.readObject();
var allDefinitions = in.readInline(scala.collection.immutable.List.class);
var parent =
new org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope(
childScopes, occurrences, allDefinitions);
var parent = new Graph.Scope(childScopes, occurrences, allDefinitions);
var optionParent = Option.apply(parent);
childScopes.forall(
(object) -> {
var ch = (org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope) object;
var ch = (Graph.Scope) object;
ch.parent_$eq(optionParent);
return null;
});
@ -134,9 +143,7 @@ public final class PassPersistance {
@Override
@SuppressWarnings("unchecked")
protected void writeObject(
org.enso.compiler.pass.analyse.AliasAnalysis$Graph$Scope obj, Output out)
throws IOException {
protected void writeObject(Graph.Scope obj, Output out) throws IOException {
out.writeInline(scala.collection.immutable.List.class, obj.childScopes());
out.writeObject(obj.occurrences());
out.writeInline(scala.collection.immutable.List.class, obj.allDefinitions());
@ -153,7 +160,7 @@ public final class PassPersistance {
protected Graph readObject(Input in) throws IOException {
var g = new Graph();
var rootScope = (AliasAnalysis$Graph$Scope) in.readObject();
var rootScope = (Graph.Scope) in.readObject();
assignParents(rootScope);
g.rootScope_$eq(rootScope);
@ -175,7 +182,7 @@ public final class PassPersistance {
out.writeInt(obj.nextIdCounter());
}
private static void assignParents(AliasAnalysis$Graph$Scope scope) {
private static void assignParents(Graph.Scope scope) {
var option = Option.apply(scope);
scope
.childScopes()

View File

@ -1,12 +1,7 @@
package org.enso.compiler.context
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{
Id,
Occurrence,
Scope => AliasScope
}
import org.enso.compiler.pass.analyse.{AliasAnalysis, DataflowAnalysis}
import org.enso.compiler.pass.analyse.DataflowAnalysis
import org.enso.compiler.pass.analyse.alias.{Graph => AliasGraph}
import scala.jdk.CollectionConverters._
@ -33,19 +28,19 @@ import scala.jdk.CollectionConverters._
*/
class LocalScope(
final val parentScope: Option[LocalScope],
final val aliasingGraph: AliasAnalysis.Graph,
final val scope: AliasAnalysis.Graph.Scope,
final val aliasingGraph: AliasGraph,
final val scope: AliasGraph.Scope,
final val dataflowInfo: DataflowAnalysis.Metadata,
final val flattenToParent: Boolean = false,
private val parentFrameSlotIdxs: Map[Graph.Id, Int] = Map()
final val flattenToParent: Boolean = false,
private val parentFrameSlotIdxs: Map[AliasGraph.Id, Int] = Map()
) {
private lazy val localFrameSlotIdxs: Map[Graph.Id, Int] =
private lazy val localFrameSlotIdxs: Map[AliasGraph.Id, Int] =
gatherLocalFrameSlotIdxs()
/** All frame slot indexes, including local and all the parents.
* Useful for quick searching for [[FramePointer]] of parent scopes.
*/
private lazy val allFrameSlotIdxs: Map[Graph.Id, Int] =
private lazy val allFrameSlotIdxs: Map[AliasGraph.Id, Int] =
parentFrameSlotIdxs ++ localFrameSlotIdxs
/** Creates a new child with a new aliasing scope.
@ -62,7 +57,7 @@ class LocalScope(
* @return a child of this scope
*/
def createChild(
childScope: AliasScope,
childScope: AliasGraph.Scope,
flattenToParent: Boolean = false
): LocalScope = {
new LocalScope(
@ -83,7 +78,7 @@ class LocalScope(
* analysis.
* @return the frame slot index for `id`.
*/
def getVarSlotIdx(id: Graph.Id): Int = {
def getVarSlotIdx(id: AliasGraph.Id): Int = {
assert(
localFrameSlotIdxs.contains(id),
"Cannot find " + id + " in " + localFrameSlotIdxs
@ -98,7 +93,7 @@ class LocalScope(
* analysis
* @return the frame pointer for `id`, if it exists
*/
def getFramePointer(id: Graph.Id): Option[FramePointer] = {
def getFramePointer(id: AliasGraph.Id): Option[FramePointer] = {
aliasingGraph
.defLinkFor(id)
.flatMap { link =>
@ -126,7 +121,7 @@ class LocalScope(
* indexes in the frame. Takes into account all the
* internal slots, that are prepended to every frame.
*/
private def gatherLocalFrameSlotIdxs(): Map[Id, Int] = {
private def gatherLocalFrameSlotIdxs(): Map[AliasGraph.Id, Int] = {
scope.allDefinitions.zipWithIndex.map { case (definition, i) =>
definition.id -> (i + LocalScope.internalSlotsSize)
}.toMap
@ -139,13 +134,13 @@ class LocalScope(
*/
private def flattenBindingsWithLevel(
level: Int
): Map[Graph.Symbol, FramePointer] = {
var parentResult: Map[Graph.Symbol, FramePointer] = parentScope
): Map[AliasGraph.Symbol, FramePointer] = {
var parentResult: Map[AliasGraph.Symbol, FramePointer] = parentScope
.flatMap(scope => Some(scope.flattenBindingsWithLevel(level + 1)))
.getOrElse(Map())
scope.occurrences.foreach {
case x: Occurrence.Def =>
case x: AliasGraph.Occurrence.Def =>
parentResult += x.symbol -> new FramePointer(
level,
allFrameSlotIdxs(x.id)
@ -166,7 +161,7 @@ object LocalScope {
* @return a defaulted local scope
*/
def root: LocalScope = {
val graph = new AliasAnalysis.Graph
val graph = new AliasGraph
new LocalScope(
None,
graph,

View File

@ -78,7 +78,7 @@ object AutomaticParallelism extends IRPass {
ir: Expression,
parallelismStatus: ParallelismStatus,
id: Int,
assignment: Option[AliasAnalysis.Graph.Id],
assignment: Option[alias.Graph.Id],
dependencies: Set[Int],
blockAssignment: Option[BlockAssignment]
)
@ -215,7 +215,7 @@ object AutomaticParallelism extends IRPass {
AliasAnalysis,
"Alias analysis left a binding behind"
)
.asInstanceOf[AliasAnalysis.Info.Occurrence]
.asInstanceOf[alias.Info.Occurrence]
line.copy(assignment = Some(aaInfo.id))
case _ => line
}
@ -233,7 +233,7 @@ object AutomaticParallelism extends IRPass {
n
}
.flatMap(_.getMetadata(AliasAnalysis))
.collect { case occ: AliasAnalysis.Info.Occurrence =>
.collect { case occ: alias.Info.Occurrence =>
occ
}
.flatMap(occ => occ.graph.defLinkFor(occ.id))

View File

@ -548,7 +548,7 @@ case object DataflowAnalysis extends IRPass {
"Name occurrence with missing aliasing information."
)
)
.asInstanceOf[AliasAnalysis.Info.Occurrence]
.asInstanceOf[alias.Info.Occurrence]
name match {
case _: Name.Blank =>
@ -560,7 +560,7 @@ case object DataflowAnalysis extends IRPass {
val key: DependencyInfo.Type = defIdForName match {
case Some(defLink) =>
aliasInfo.graph.getOccurrence(defLink.target) match {
case Some(AliasAnalysis.Graph.Occurrence.Def(_, _, id, ext, _)) =>
case Some(alias.Graph.Occurrence.Def(_, _, id, ext, _)) =>
DependencyInfo.Type.Static(id, ext)
case _ =>
DependencyInfo.Type.Dynamic(name.name, None)

View File

@ -202,7 +202,7 @@ case object DemandAnalysis extends IRPass {
AliasAnalysis,
"Missing alias occurrence information for a name usage"
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[alias.Info.Occurrence]
aliasInfo.graph.defLinkFor(aliasInfo.id).isDefined
}

View File

@ -0,0 +1,717 @@
package org.enso.compiler.pass.analyse.alias
import org.enso.compiler.core.{CompilerError, ExternalID, Identifier}
import org.enso.syntax.text.Debug
import org.enso.compiler.pass.analyse.alias.Graph.{Occurrence, Scope}
import java.util.UUID
import scala.collection.mutable
import scala.reflect.ClassTag
/** A graph containing aliasing information for a given root scope in Enso. */
sealed class Graph extends Serializable {
var rootScope: Graph.Scope = new Graph.Scope()
var links: Set[Graph.Link] = Set()
var nextIdCounter = 0
private var globalSymbols: Map[Graph.Symbol, Occurrence.Global] =
Map()
/** @return a deep structural copy of `this` */
def deepCopy(
scope_mapping: mutable.Map[Scope, Scope] = mutable.Map()
): Graph = {
val copy = new Graph
copy.rootScope = this.rootScope.deepCopy(scope_mapping)
copy.links = this.links
copy.globalSymbols = this.globalSymbols
copy.nextIdCounter = this.nextIdCounter
copy
}
/** Registers a requested global symbol in the aliasing scope.
*
* @param sym the symbol occurrence
*/
def addGlobalSymbol(sym: Occurrence.Global): Unit = {
if (!globalSymbols.contains(sym.symbol)) {
globalSymbols = globalSymbols + (sym.symbol -> sym)
}
}
/** Creates a deep copy of the aliasing graph structure.
*
* @return a copy of the graph structure
*/
def copy: Graph = {
val graph = new Graph
graph.links = links
graph.rootScope = rootScope.deepCopy(mutable.Map())
graph.nextIdCounter = nextIdCounter
graph
}
/** Determines whether `this` is equal to `obj`.
*
* @param obj the object to compare against.
* @return `true` if `this == obj`, otherwise `false`
*/
override def equals(obj: Any): Boolean =
obj match {
case that: Graph =>
(this.links == that.links) && (this.rootScope == that.rootScope)
case _ => false
}
/** Generates a new identifier for a node in the graph.
*
* @return a unique identifier for this graph
*/
def nextId(): Graph.Id = {
val nextId = nextIdCounter
nextIdCounter += 1
nextId
}
/** Resolves any links for the given usage of a symbol, assuming the symbol
* is a local variable.
*
* @param occurrence the symbol usage
* @return the link, if it exists
*/
def resolveLocalUsage(
occurrence: Graph.Occurrence.Use
): Option[Graph.Link] = {
scopeFor(occurrence.id).flatMap(_.resolveUsage(occurrence).map { link =>
links += link
link
})
}
/** Resolves any links for the given usage of a symbol, assuming the symbol
* is global (i.e. method, constructor etc.)
*
* @param occurrence the symbol usage
* @return the link, if it exists
*/
def resolveGlobalUsage(
occurrence: Graph.Occurrence.Use
): Option[Graph.Link] = {
scopeFor(occurrence.id) match {
case Some(scope) =>
globalSymbols
.get(occurrence.symbol)
.map(g => Graph.Link(occurrence.id, scope.scopesToRoot + 1, g.id))
case None => None
}
}
/** Returns a string representation of the graph.
*
* @return a string representation of `this`
*/
override def toString: String =
s"Graph(links = $links, rootScope = $rootScope)"
/** Pretty prints the graph.
*
* @return a pretty-printed string representation of the graph
*/
def pprint: String = {
val original = toString
Debug.pretty(original)
}
/** Gets all links in which the provided `id` is a participant.
*
* @param id the identifier for the symbol
* @return a list of links in which `id` occurs
*/
def linksFor(id: Graph.Id): Set[Graph.Link] = {
links.filter(l => l.source == id || l.target == id)
}
/** Finds all links in the graph where `symbol` appears in the role
* specified by `T`.
*
* @param symbol the symbol to find links for
* @tparam T the role in which `symbol` should occur
* @return a set of all links in which `symbol` occurs with role `T`
*/
def linksFor[T <: Occurrence: ClassTag](
symbol: Graph.Symbol
): Set[Graph.Link] = {
val idsForSym = rootScope.symbolToIds[T](symbol)
links.filter(l =>
idsForSym.contains(l.source) || idsForSym.contains(l.target)
)
}
/** Obtains the occurrence for a given ID, from whichever scope in which it
* occurs.
*
* @param id the occurrence identifier
* @return the occurrence for `id`, if it exists
*/
def getOccurrence(id: Graph.Id): Option[Occurrence] =
scopeFor(id).flatMap(_.getOccurrence(id))
/** Gets the link from an id to the definition of the symbol it represents.
*
* @param id the identifier to find the definition link for
* @return the definition link for `id` if it exists
*/
def defLinkFor(id: Graph.Id): Option[Graph.Link] = {
linksFor(id).find { edge =>
val occ = getOccurrence(edge.target)
occ match {
case Some(Occurrence.Def(_, _, _, _, _)) => true
case _ => false
}
}
}
/** Gets the scope where a given ID is defined in the graph.
*
* @param id the id to find the scope for
* @return the scope where `id` occurs
*/
def scopeFor(id: Graph.Id): Option[Graph.Scope] = {
rootScope.scopeFor(id)
}
/** Finds the scopes in which a name occurs with a given role.
*
* @param symbol the symbol
* @tparam T the role in which `symbol` occurs
* @return all the scopes where `symbol` occurs with role `T`
*/
def scopesFor[T <: Graph.Occurrence: ClassTag](
symbol: Graph.Symbol
): List[Graph.Scope] = {
rootScope.scopesForSymbol[T](symbol)
}
/** Counts the number of scopes in this scope.
*
* @return the number of scopes that are either this scope or children of
* it
*/
def numScopes: Int = {
rootScope.scopeCount
}
/** Determines the maximum nesting depth of scopes through this scope.
*
* @return the maximum nesting depth of scopes through this scope.
*/
def nesting: Int = {
rootScope.maxNesting
}
/** Determines if the provided ID is capable of shadowing other bindings
*
* @param id the occurrence identifier
* @return `true` if `id` shadows other bindings, otherwise `false`
*/
def canShadow(id: Graph.Id): Boolean = {
scopeFor(id)
.flatMap(
_.getOccurrence(id).flatMap {
case d: Occurrence.Def => Some(d)
case _ => None
}
)
.isDefined
}
/** Computes the bindings that are shadowed by the binding with the provided
* `definition`.
*
* Please note that just because [[canShadow]] states that an identifier is
* _capable_ of shadowing, that does not mean that it is necessarily known
* to do so.
*
* @param definition the definition to find the 'shadowees' of
* @return the bindings shadowed by `definition`
*/
def knownShadowedDefinitions(
definition: Occurrence
): Set[Graph.Occurrence] = {
def getShadowedIds(
scope: Graph.Scope
): Set[Graph.Occurrence] = {
scope.occurrences.collect {
case d: Occurrence.Def if d.symbol == definition.symbol => d
case g: Occurrence.Global if g.symbol == definition.symbol => g
} ++ scope.parent.map(getShadowedIds).getOrElse(Set())
}
definition match {
case d: Occurrence.Def =>
scopeFor(d.id).flatMap(_.parent) match {
case Some(scope) => getShadowedIds(scope) // + globals
case None => Set()
}
case _: Occurrence.Global => Set()
case _: Occurrence.Use => Set()
}
}
/** Determines if the provided id is linked to a binding that shadows
* another binding.
*
* @param id the identifier to check
* @return `true` if the definition of the symbol for `id` shadows another
* binding for the same symbol, `false`, otherwise
*/
def linkedToShadowingBinding(id: Graph.Id): Boolean = {
defLinkFor(id).isDefined
}
/** Gets all symbols defined in the graph.
*
* @return the set of symbols defined in this graph
*/
def symbols: Set[Graph.Symbol] = {
rootScope.symbols
}
/** Goes from a symbol to all identifiers that relate to that symbol in
* the role specified by `T`.
*
* @param symbol the symbol to find identifiers for
* @tparam T the role in which `symbol` should occur
* @return a list of identifiers for that symbol
*/
def symbolToIds[T <: Occurrence: ClassTag](
symbol: Graph.Symbol
): List[Graph.Id] = {
rootScope.symbolToIds[T](symbol)
}
/** Goes from an identifier to the associated symbol.
*
* @param id the identifier of an occurrence
* @return the symbol associated with `id`, if it exists
*/
def idToSymbol(
id: Graph.Id
): Option[Graph.Symbol] = {
rootScope.idToSymbol(id)
}
}
object Graph {
/** The type of symbols on the graph. */
type Symbol = String
/** The type of identifiers on the graph. */
type Id = Int
/** A representation of a local scope in Enso.
*
* @param childScopes all scopes that are _direct_ children of `this`
* @param occurrences all symbol occurrences in `this` scope
* @param allDefinitions all definitions in this scope, including synthetic ones.
* Note that there may not be a link for all these definitions.
*/
sealed class Scope(
var childScopes: List[Scope] = List(),
var occurrences: Set[Occurrence] = Set(),
var allDefinitions: List[Occurrence.Def] = List()
) extends Serializable {
var parent: Option[Scope] = None
/** Counts the number of scopes from this scope to the root.
*
* This count includes the root scope, but not the current scope.
*
* @return the number of scopes from this scope to the root
*/
def scopesToRoot: Int = {
parent.flatMap(scope => Some(scope.scopesToRoot + 1)).getOrElse(0)
}
/** Sets the parent of the scope.
*
* The parent scope must not be redefined.
*
* @return this scope with parent scope set
*/
def withParent(parentScope: Scope): this.type = {
assert(parent.isEmpty)
this.parent = Some(parentScope)
this
}
/** Creates a structural copy of this scope, ensuring that replicated
* scopes are memoised.
*
* @return a copy of `this`
*/
def deepCopy(
mapping: mutable.Map[Scope, Scope] = mutable.Map()
): Scope = {
mapping.get(this) match {
case Some(newCorrespondingScope) => newCorrespondingScope
case None =>
val childScopeCopies: mutable.ListBuffer[Scope] =
mutable.ListBuffer()
this.childScopes.foreach(scope =>
childScopeCopies += scope.deepCopy(mapping)
)
val newScope =
new Scope(childScopeCopies.toList, occurrences, allDefinitions)
mapping.put(this, newScope)
newScope
}
}
/** Checks whether `this` is equal to `obj`.
*
* @param obj the object to compare `this` against
* @return `true` if `this == obj`, otherwise `false`
*/
override def equals(obj: Any): Boolean =
obj match {
case that: Scope =>
if (this.childScopes.length == that.childScopes.length) {
val childScopesEqual =
this.childScopes.zip(that.childScopes).forall(t => t._1 == t._2)
val occurrencesEqual = this.occurrences == that.occurrences
childScopesEqual && occurrencesEqual
} else {
false
}
case _ => false
}
/** Creates and returns a scope that is a child of this one.
*
* @return a scope that is a child of `this`
*/
def addChild(): Scope = {
val scope = new Scope()
scope.parent = Some(this)
childScopes ::= scope
scope
}
/** Adds the specified symbol occurrence to this scope.
*
* @param occurrence the occurrence to add
*/
def add(occurrence: Occurrence): Unit = {
occurrences += occurrence
}
/** Adds a definition, including a definition with synthetic name, without
* any links.
*
* @param definition The definition to add.
*/
def addDefinition(definition: Occurrence.Def): Unit = {
allDefinitions = allDefinitions ++ List(definition)
}
/** Finds an occurrence for the provided ID in the current scope, if it
* exists.
*
* @param id the occurrence identifier
* @return the occurrence for `id`, if it exists
*/
def getOccurrence(id: Graph.Id): Option[Occurrence] = {
occurrences.find(o => o.id == id)
}
/** Finds any occurrences for the provided symbol in the current scope, if
* it exists.
*
* @param symbol the symbol of the occurrence
* @tparam T the role for the symbol
* @return the occurrences for `name`, if they exist
*/
def getOccurrences[T <: Occurrence: ClassTag](
symbol: Graph.Symbol
): Set[Occurrence] = {
occurrences.collect {
case o: T if o.symbol == symbol => o
}
}
/** Unsafely gets the occurrence for the provided ID in the current scope.
*
* Please note that this will crash if the ID is not defined in this
* scope.
*
* @param id the occurrence identifier
* @return the occurrence for `id`
*/
def unsafeGetOccurrence(id: Graph.Id): Occurrence = {
getOccurrence(id).get
}
/** Checks whether a symbol occurs in a given role in the current scope.
*
* @param symbol the symbol to check for
* @tparam T the role for it to occur in
* @return `true` if `symbol` occurs in role `T` in this scope, `false`
* otherwise
*/
def hasSymbolOccurrenceAs[T <: Occurrence: ClassTag](
symbol: Graph.Symbol
): Boolean = {
occurrences.collect { case x: T if x.symbol == symbol => x }.nonEmpty
}
/** Resolves usages of symbols into links where possible, creating an edge
* from the usage site to the definition site.
*
* @param occurrence the symbol usage
* @param parentCounter the number of scopes that the link has traversed
* @return the link from `occurrence` to the definition of that symbol, if it
* exists
*/
def resolveUsage(
occurrence: Graph.Occurrence.Use,
parentCounter: Int = 0
): Option[Graph.Link] = {
val definition = occurrences.find {
case Graph.Occurrence.Def(_, name, _, _, _) =>
name == occurrence.symbol
case _ => false
}
definition match {
case None =>
parent.flatMap(_.resolveUsage(occurrence, parentCounter + 1))
case Some(target) =>
Some(Graph.Link(occurrence.id, parentCounter, target.id))
}
}
/** Creates a string representation of the scope.
*
* @return a string representation of `this`
*/
override def toString: String =
s"Scope(occurrences = $occurrences, childScopes = $childScopes)"
/** Counts the number of scopes in this scope.
*
* @return the number of scopes that are either this scope or children of
* it
*/
def scopeCount: Int = {
childScopes.map(_.scopeCount).sum + 1
}
/** Determines the maximum nesting depth of scopes through this scope.
*
* @return the maximum nesting depth of scopes through this scope.
*/
def maxNesting: Int = {
childScopes.map(_.maxNesting).foldLeft(0)(Math.max) + 1
}
/** Gets the scope where a given ID is defined in the graph.
*
* @param id the id to find the scope for
* @return the scope where `id` occurs
*/
def scopeFor(id: Graph.Id): Option[Scope] = {
val possibleCandidates = occurrences.filter(o => o.id == id)
if (possibleCandidates.isEmpty) {
if (childScopes.isEmpty) {
None
} else {
var childCandidate: Scope = null
val iter = childScopes.iterator
var moreThanOne = false
while (iter.hasNext && !moreThanOne) {
iter.next().scopeFor(id) match {
case Some(s) =>
if (childCandidate == null) {
childCandidate = s
} else {
moreThanOne = true
}
case None =>
}
}
if (childCandidate == null) {
None
} else if (moreThanOne) {
throw new CompilerError(s"ID $id defined in multiple scopes.")
} else {
Some(childCandidate)
}
}
} else if (possibleCandidates.size == 1) {
Some(this)
} else {
throw new CompilerError(s"Multiple occurrences found for ID $id.")
}
}
/** Gets the n-th parent of `this` scope.
*
* @param n the number of scopes to walk up
* @return the n-th parent of `this` scope, if present
*/
def nThParent(n: Int): Option[Scope] = {
if (n == 0) Some(this) else this.parent.flatMap(_.nThParent(n - 1))
}
/** Finds the scopes in which a symbol occurs with a given role.
*
* Users of this function _must_ explicitly specify `T`, otherwise the
* results will be an empty list.
*
* @param symbol the symbol
* @tparam T the role in which `name` occurs
* @return all the scopes where `name` occurs with role `T`
*/
def scopesForSymbol[T <: Occurrence: ClassTag](
symbol: Graph.Symbol
): List[Scope] = {
val occursInThisScope = hasSymbolOccurrenceAs[T](symbol)
val occurrencesInChildScopes =
childScopes.flatMap(_.scopesForSymbol[T](symbol))
if (occursInThisScope) {
this +: occurrencesInChildScopes
} else {
occurrencesInChildScopes
}
}
/** Gets the set of all symbols in this scope and its children.
*
* @return the set of symbols
*/
def symbols: Set[Graph.Symbol] = {
val symbolsInThis = occurrences.map(_.symbol)
val symbolsInChildScopes = childScopes.flatMap(_.symbols)
symbolsInThis ++ symbolsInChildScopes
}
/** Goes from a symbol to all identifiers that relate to that symbol in
* the role specified by `T`.
*
* @param symbol the symbol to find identifiers for
* @tparam T the role in which `symbol` should occur
* @return a list of identifiers for that symbol
*/
def symbolToIds[T <: Occurrence: ClassTag](
symbol: Graph.Symbol
): List[Graph.Id] = {
val scopes =
scopesForSymbol[T](symbol).flatMap(_.getOccurrences[T](symbol))
scopes.map(_.id)
}
/** Goes from an identifier to the associated symbol.
*
* @param id the identifier of an occurrence
* @return the symbol associated with `id`, if it exists
*/
def idToSymbol(
id: Graph.Id
): Option[Graph.Symbol] = {
scopeFor(id).flatMap(_.getOccurrence(id)).map(_.symbol)
}
/** Checks if `this` scope is a child of the provided `scope`.
*
* @param scope the potential parent scope
* @return `true` if `this` is a child of `scope`, otherwise `false`
*/
def isChildOf(scope: Scope): Boolean = {
val isDirectChildOf = scope.childScopes.contains(this)
val isChildOfChildren = scope.childScopes
.map(scope => this.isChildOf(scope))
.foldLeft(false)(_ || _)
isDirectChildOf || isChildOfChildren
}
}
/** A link in the [[Graph]].
*
* The source of the link should always be an [[Occurrence.Use]] while the
* target of the link should always be an [[Occurrence.Def]].
*
* @param source the source ID of the link in the graph
* @param scopeCount the number of scopes that the link traverses
* @param target the target ID of the link in the graph
*/
sealed case class Link(source: Id, scopeCount: Int, target: Id)
extends Serializable
/** An occurrence of a given symbol in the aliasing graph. */
sealed trait Occurrence extends Serializable {
val id: Id
val symbol: Graph.Symbol
}
object Occurrence {
/** The definition of a symbol in the aliasing graph.
*
* @param id the identifier of the name in the graph
* @param symbol the text of the name
* @param identifier the identifier of the symbol
* @param externalId the external identifier for the IR node defining
* the symbol
* @param isLazy whether or not the symbol is defined as lazy
*/
sealed case class Def(
override val id: Id,
override val symbol: Graph.Symbol,
identifier: UUID @Identifier,
externalId: Option[UUID @ExternalID],
isLazy: Boolean = false
) extends Occurrence
/** A usage of a symbol in the aliasing graph
*
* Name usages _need not_ correspond to name definitions, as dynamic
* symbol resolution means that a name used at runtime _may not_ be
* statically visible in the scope.
*
* @param id the identifier of the name in the graph
* @param symbol the text of the name
* @param identifier the identifier of the symbol
* @param externalId the external identifier for the IR node defining
* the symbol
*/
sealed case class Use(
override val id: Id,
override val symbol: Graph.Symbol,
identifier: UUID @Identifier,
externalId: Option[UUID @ExternalID]
) extends Occurrence
// TODO [AA] At some point the analysis should make use of these.
/** Represents a global symbol that has been _asked for_ in the program.
*
* @param id the identifier of the name in the graph
* @param symbol the text of the name
*/
sealed case class Global(
override val id: Id,
override val symbol: Graph.Symbol
) extends Occurrence
}
}

View File

@ -0,0 +1,89 @@
package org.enso.compiler.pass.analyse.alias
import org.enso.compiler.pass.IRPass
/** Information about the aliasing state for a given IR node. */
sealed trait Info extends IRPass.IRMetadata {
/** The aliasing graph. */
val graph: Graph
}
object Info {
sealed trait Scope extends Info
object Scope {
/** Aliasing information for a root scope.
*
* A root scope has a 1:1 correspondence with a top-level binding.
*
* @param graph the graph containing the alias information for that node
*/
sealed case class Root(override val graph: Graph) extends Scope {
override val metadataName: String = "Info.Scope.Root"
/** @inheritdoc */
override def prepareForSerialization(
compiler: Compiler
): Root = this
/** @inheritdoc */
override def restoreFromSerialization(
compiler: Compiler
): Option[Root] = Some(this)
/** @inheritdoc */
override def duplicate(): Option[IRPass.IRMetadata] = None
}
/** Aliasing information about a child scope.
*
* @param graph the graph
* @param scope the child scope in `graph`
*/
sealed case class Child(
override val graph: Graph,
scope: Graph.Scope
) extends Scope {
override val metadataName: String = "Info.Scope.Child"
/** @inheritdoc */
override def prepareForSerialization(
compiler: Compiler
): Child = this
/** @inheritdoc */
override def restoreFromSerialization(
compiler: Compiler
): Option[Child] = Some(this)
/** @inheritdoc */
override def duplicate(): Option[IRPass.IRMetadata] = None
}
}
/** Aliasing information for a piece of [[IR]] that is contained within a
* [[Scope]].
*
* @param graph the graph in which this IR node can be found
* @param id the identifier of this IR node in `graph`
*/
sealed case class Occurrence(
override val graph: Graph,
id: Graph.Id
) extends Info {
override val metadataName: String = "Info.Occurrence"
/** @inheritdoc */
override def prepareForSerialization(
compiler: Compiler
): Occurrence = this
/** @inheritdoc */
override def restoreFromSerialization(
compiler: Compiler
): Option[Occurrence] = Some(this)
/** @inheritdoc */
override def duplicate(): Option[IRPass.IRMetadata] = None
}
}

View File

@ -16,6 +16,7 @@ import org.enso.compiler.core.ir.expression.{errors, warnings, Case, Foreign}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.analyse.alias.{Info => AliasInfo}
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.optimise.LambdaConsolidate
import org.enso.compiler.pass.resolve.{ExpressionAnnotations, IgnoredBindings}
@ -109,7 +110,7 @@ case object UnusedBindings extends IRPass {
AliasAnalysis,
"Aliasing information is required for linting."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val isUsed = aliasInfo.graph.linksFor(aliasInfo.id).nonEmpty
if (!isIgnored && !isUsed) {
@ -188,7 +189,7 @@ case object UnusedBindings extends IRPass {
"Aliasing information missing from function argument but is " +
"required for linting."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val isUsed = aliasInfo.graph.linksFor(aliasInfo.id).nonEmpty
argument match {
@ -270,7 +271,7 @@ case object UnusedBindings extends IRPass {
"Aliasing information missing from pattern but is " +
"required for linting."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val isUsed = aliasInfo.graph.linksFor(aliasInfo.id).nonEmpty
if (!isIgnored && !isUsed) {
@ -300,7 +301,7 @@ case object UnusedBindings extends IRPass {
"Aliasing information missing from pattern but is " +
"required for linting."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val isUsed = aliasInfo.graph.linksFor(aliasInfo.id).nonEmpty
if (!isIgnored && !isUsed) {

View File

@ -22,6 +22,10 @@ import org.enso.compiler.pass.analyse.{
DemandAnalysis,
TailCall
}
import org.enso.compiler.pass.analyse.alias.{
Graph => AliasGraph,
Info => AliasInfo
}
import org.enso.compiler.pass.desugar._
import org.enso.compiler.pass.resolve.IgnoredBindings
@ -150,7 +154,7 @@ case object LambdaConsolidate extends IRPass {
AliasAnalysis,
"Missing aliasing information for an argument definition"
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
shadowedBindingIds.contains(aliasInfo.id)
}
@ -345,7 +349,7 @@ case object LambdaConsolidate extends IRPass {
*/
def getShadowedBindingIds(
args: List[DefinitionArgument]
): Set[AliasAnalysis.Graph.Id] = {
): Set[AliasGraph.Id] = {
args
.map { case spec: DefinitionArgument.Specified =>
val aliasInfo =
@ -354,13 +358,13 @@ case object LambdaConsolidate extends IRPass {
AliasAnalysis,
"Missing aliasing information for an argument definition."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
aliasInfo.graph
.getOccurrence(aliasInfo.id)
.flatMap(occ => Some(aliasInfo.graph.knownShadowedDefinitions(occ)))
.getOrElse(Set())
}
.foldLeft(Set[AliasAnalysis.Graph.Occurrence]())(_ ++ _)
.foldLeft(Set[AliasGraph.Occurrence]())(_ ++ _)
.map(_.id)
}
@ -382,7 +386,7 @@ case object LambdaConsolidate extends IRPass {
AliasAnalysis,
"Missing aliasing information for an argument definition."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
// Empty set is used to indicate that it isn't shadowed
val usageIds =
@ -393,7 +397,7 @@ case object LambdaConsolidate extends IRPass {
.map(link => aliasInfo.graph.getOccurrence(link.source))
.collect {
case Some(
AliasAnalysis.Graph.Occurrence.Use(_, _, identifier, _)
AliasGraph.Occurrence.Use(_, _, identifier, _)
) =>
identifier
}

View File

@ -26,6 +26,7 @@ import org.enso.compiler.core.Implicits.{AsDiagnostics, AsMetadata}
import org.enso.compiler.core.ir.expression.Application
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis}
import org.enso.compiler.pass.analyse.alias.{Info => AliasInfo}
import org.enso.compiler.pass.desugar.Imports
import org.enso.editions.LibraryName
@ -407,7 +408,7 @@ case object FullyQualifiedNames extends IRPass {
AliasAnalysis,
"no alias analysis info on a name"
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val defLink = aliasInfo.graph.defLinkFor(aliasInfo.id)
defLink.isDefined
}

View File

@ -27,6 +27,7 @@ import org.enso.compiler.core.ConstantsNames
import org.enso.compiler.core.ir.expression.Application
import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis}
import org.enso.compiler.pass.analyse.alias.{Info => AliasInfo}
/** Resolves name occurences in non-pattern contexts.
*
@ -435,7 +436,7 @@ case object GlobalNames extends IRPass {
AliasAnalysis,
"no alias analysis info on a name"
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val defLink = aliasInfo.graph.defLinkFor(aliasInfo.id)
defLink.isDefined
}

View File

@ -16,8 +16,8 @@ import org.enso.compiler.core.ir.module.scope.Definition
import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.pass.PassConfiguration._
import org.enso.compiler.pass.analyse.AliasAnalysis
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{Link, Occurrence}
import org.enso.compiler.pass.analyse.AliasAnalysis.{Graph, Info}
import org.enso.compiler.pass.analyse.alias.Graph.{Link, Occurrence}
import org.enso.compiler.pass.analyse.alias.{Graph, Info}
import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager}
import org.enso.compiler.test.CompilerTest
@ -413,7 +413,7 @@ class AliasAnalysisTest extends CompilerTest {
.members
.head
val goodMeta = goodAtom.getMetadata(AliasAnalysis)
val goodGraph = goodMeta.get.unsafeAs[AliasAnalysis.Info.Scope.Root].graph
val goodGraph = goodMeta.get.unsafeAs[Info.Scope.Root].graph
val badAtom =
"""
@ -424,7 +424,7 @@ class AliasAnalysisTest extends CompilerTest {
.members
.head
val badMeta = badAtom.getMetadata(AliasAnalysis)
val badGraph = badMeta.get.unsafeAs[AliasAnalysis.Info.Scope.Root].graph
val badGraph = badMeta.get.unsafeAs[Info.Scope.Root].graph
"assign Info.Scope.Root metadata to the atom" in {
goodMeta shouldBe defined

View File

@ -45,14 +45,17 @@ import org.enso.compiler.data.BindingsMap.{
}
import org.enso.compiler.data.{BindingsMap, CompilerConfig}
import org.enso.compiler.exception.BadPatternMatch
import org.enso.compiler.pass.analyse.AliasAnalysis.Graph.{Scope => AliasScope}
import org.enso.compiler.pass.analyse.AliasAnalysis.{Graph => AliasGraph}
import org.enso.compiler.pass.analyse.alias.Graph.{Scope => AliasScope}
import org.enso.compiler.pass.analyse.{
AliasAnalysis,
BindingAnalysis,
DataflowAnalysis,
TailCall
}
import org.enso.compiler.pass.analyse.alias.{
Graph => AliasGraph,
Info => AliasInfo
}
import org.enso.compiler.pass.resolve.{
ExpressionAnnotations,
GenericAnnotations,
@ -90,7 +93,7 @@ import org.enso.interpreter.node.{
}
import org.enso.interpreter.runtime.EnsoContext
import org.enso.interpreter.runtime.callable
import org.enso.interpreter.runtime.callable.argument.{ArgumentDefinition}
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition
import org.enso.interpreter.runtime.data.atom.{Atom, AtomConstructor}
import org.enso.interpreter.runtime.callable.function.{
FunctionSchema,
@ -104,7 +107,7 @@ import org.enso.interpreter.runtime.callable.{
}
import org.enso.interpreter.runtime.data.Type
import org.enso.interpreter.runtime.data.text.Text
import org.enso.interpreter.runtime.scope.{ModuleScope}
import org.enso.interpreter.runtime.scope.ModuleScope
import org.enso.interpreter.{Constants, EnsoLanguage}
import java.math.BigInteger
@ -259,7 +262,7 @@ class IrToTruffle(
AliasAnalysis,
"No root scope on an atom definition."
)
.unsafeAs[AliasAnalysis.Info.Scope.Root]
.unsafeAs[AliasInfo.Scope.Root]
val dataflowInfo = atomDefn.unsafeGetMetadata(
DataflowAnalysis,
@ -290,7 +293,7 @@ class IrToTruffle(
AliasAnalysis,
"No occurrence on an argument definition."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val slotIdx = localScope.getVarSlotIdx(occInfo.id)
argDefs(idx) = arg
val readArg =
@ -359,7 +362,7 @@ class IrToTruffle(
s"Missing scope information for method " +
s"`${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`."
)
.unsafeAs[AliasAnalysis.Info.Scope.Root]
.unsafeAs[AliasInfo.Scope.Root]
val dataflowInfo = methodDef.unsafeGetMetadata(
DataflowAnalysis,
"Method definition missing dataflow information."
@ -595,7 +598,7 @@ class IrToTruffle(
scopeElements.init
.mkString(Constants.SCOPE_SEPARATOR)
)
.unsafeAs[AliasAnalysis.Info.Scope.Root]
.unsafeAs[AliasInfo.Scope.Root]
val dataflowInfo = annotation.unsafeGetMetadata(
DataflowAnalysis,
"Missing dataflow information for annotation " +
@ -672,7 +675,7 @@ class IrToTruffle(
s"Missing scope information for conversion " +
s"`${methodDef.typeName.map(_.name + ".").getOrElse("")}${methodDef.methodName.name}`."
)
.unsafeAs[AliasAnalysis.Info.Scope.Root]
.unsafeAs[AliasInfo.Scope.Root]
val dataflowInfo = methodDef.unsafeGetMetadata(
DataflowAnalysis,
"Method definition missing dataflow information."
@ -1122,7 +1125,7 @@ class IrToTruffle(
AliasAnalysis,
"Missing scope information on block."
)
.unsafeAs[AliasAnalysis.Info.Scope.Child]
.unsafeAs[AliasInfo.Scope.Child]
val childFactory = this.createChild("suspended-block", scopeInfo.scope)
val childScope = childFactory.scope
@ -1232,7 +1235,7 @@ class IrToTruffle(
AliasAnalysis,
"No scope information on a case branch."
)
.unsafeAs[AliasAnalysis.Info.Scope.Child]
.unsafeAs[AliasInfo.Scope.Child]
val childProcessor = this.createChild("case_branch", scopeInfo.scope)
@ -1601,7 +1604,7 @@ class IrToTruffle(
AliasAnalysis,
"Binding with missing occurrence information."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
currentVarName = binding.name.name
@ -1625,7 +1628,7 @@ class IrToTruffle(
): RuntimeExpression = {
val scopeInfo = function
.unsafeGetMetadata(AliasAnalysis, "No scope info on a function.")
.unsafeAs[AliasAnalysis.Info.Scope.Child]
.unsafeAs[AliasInfo.Scope.Child]
if (function.body.isInstanceOf[Function]) {
throw new CompilerError(
@ -1665,7 +1668,7 @@ class IrToTruffle(
AliasAnalysis,
"No occurrence on variable usage."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val framePointer = scope.getFramePointer(useInfo.id)
val global = name.getMetadata(GlobalNames)
@ -1946,7 +1949,7 @@ class IrToTruffle(
AliasAnalysis,
"No occurrence on an argument definition."
)
.unsafeAs[AliasAnalysis.Info.Occurrence]
.unsafeAs[AliasInfo.Occurrence]
val slotIdx = scope.getVarSlotIdx(occInfo.id)
val readArg =
@ -2176,7 +2179,7 @@ class IrToTruffle(
AliasAnalysis,
"No scope attached to a call argument."
)
.unsafeAs[AliasAnalysis.Info.Scope.Child]
.unsafeAs[AliasInfo.Scope.Child]
val shouldCreateClosureRootNode = value match {
case _: Name => false