This is an automated email from the ASF dual-hosted git repository. greg-dove pushed a commit to branch local_var_resolution_issue in repository https://gitbox.apache.org/repos/asf/royale-compiler.git
commit 4a12b1ec4c35e69ba21bbb3f26aa4fc02ba3c166 Author: greg-dove <[email protected]> AuthorDate: Thu May 14 13:46:37 2026 +1200 Implement performance caching for name resolution and improve Workspace thread-safety. -Add result caching to IdentifierNode.resolve() when performance caching is enabled. -Refine ASScopeCache with null-safe dependency tracking and improved debug string representation for multiname lookups. -Enhance CompilerProject with granular scope cache invalidation and a single-scope optimization path. -Update ASScope.reconnectScopeNode() to trigger cache invalidation across all projects in the workspace. -Make Workspace.getProjects() public and add synchronization to project lifecycle methods to ensure thread-safe access during cache invalidation. --- .../internal/projects/CompilerProject.java | 50 +++++++++++++++---- .../royale/compiler/internal/scopes/ASScope.java | 18 ++++++- .../compiler/internal/scopes/ASScopeCache.java | 56 ++++++++++++---------- .../compiler/internal/tree/as/IdentifierNode.java | 25 ++++++++-- .../compiler/internal/workspaces/Workspace.java | 21 +++++--- 5 files changed, 126 insertions(+), 44 deletions(-) diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/projects/CompilerProject.java b/compiler/src/main/java/org/apache/royale/compiler/internal/projects/CompilerProject.java index 397bb6bf3..eb0c24260 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/internal/projects/CompilerProject.java +++ b/compiler/src/main/java/org/apache/royale/compiler/internal/projects/CompilerProject.java @@ -238,12 +238,20 @@ public abstract class CompilerProject implements ICompilerProject scopeRequests.add(unit.getFileScopeRequest()); } - scopeCaches.invalidateAll(); - initThreadLocalCaches(); + invalidateAllScopeCaches(); projectScope.addAllExternallyVisibleDefinitions(scopeRequests); } + /** + * Invalidates all scope caches for this project, including thread-local caches. + */ + public void invalidateAllScopeCaches() + { + scopeCaches.invalidateAll(); + initThreadLocalCaches(); + } + /** * Adds compilation units to the project and updates the public and private * definitions. Eventually this method should be removed, but it is @@ -535,8 +543,7 @@ public abstract class CompilerProject implements ICompilerProject compilationUnit.clean(null, cusToUpdate, true); } - scopeCaches.invalidateAll(); - initThreadLocalCaches(); + invalidateAllScopeCaches(); } finally { @@ -715,14 +722,41 @@ public abstract class CompilerProject implements ICompilerProject resetScopeCaches(relatedScopes); } - private void resetScopeCaches(Iterable<IASScope> scopes) + /** + * Resets all the {@link ASScopeCache}s associated with the specified + * {@link IASScope}s. + * + * @param scopes {@link IASScope}s whose scope caches should be cleared + */ + public void resetScopeCaches(Iterable<IASScope> scopes) { assert scopes != null; - for (IASScope scope : scopes) + boolean invalidated = false; + // Optimization: if it's a single scope, avoid overhead of iterator or complex checks + if (scopes instanceof Collection && ((Collection<?>)scopes).size() == 1) { - scopeCaches.invalidate(scope); + IASScope scope = scopes.iterator().next(); + if (scopeCaches.asMap().containsKey(scope)) + { + scopeCaches.invalidate(scope); + invalidated = true; + } + } + else + { + for (IASScope scope : scopes) + { + if (scopeCaches.asMap().containsKey(scope)) + { + scopeCaches.invalidate(scope); + invalidated = true; + } + } + } + if (invalidated) + { + initThreadLocalCaches(); } - initThreadLocalCaches(); } public void addGlobalUsedNamespacesToNamespaceSet(Set<INamespaceDefinition> nsSet) diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java index 33bcc66ef..eb3d96e36 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java +++ b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScope.java @@ -37,6 +37,8 @@ import org.apache.royale.compiler.constants.IASLanguageConstants; import org.apache.royale.compiler.definitions.IClassDefinition; import org.apache.royale.compiler.definitions.IDefinition; import org.apache.royale.compiler.definitions.INamespaceDefinition; +import org.apache.royale.compiler.definitions.INamespaceDefinition.IProtectedNamespaceDefinition; +import org.apache.royale.compiler.definitions.INamespaceDefinition.IStaticProtectedNamespaceDefinition; import org.apache.royale.compiler.definitions.IQualifiers; import org.apache.royale.compiler.definitions.IScopedDefinition; import org.apache.royale.compiler.definitions.references.INamespaceReference; @@ -286,12 +288,23 @@ public abstract class ASScope extends ASScopeBase public void reconnectScopeNode(IScopedNode node) { scopedNodeRef.reconnectNode(node); + IWorkspace w = getWorkspace(); + if (w instanceof Workspace) + { + CompilerProject[] projects = ((Workspace)w).getProjects(); + for (CompilerProject project : projects) + { + project.resetScopeCaches(Collections.singleton(this)); + } + } } @Override public IScopedNode getScopeNode() { IWorkspace w = getWorkspace(); + if (w == null) + return null; return (IScopedNode)scopedNodeRef.getNode(w, this); } @@ -317,7 +330,7 @@ public abstract class ASScope extends ASScopeBase * For debugging only. */ @Override - protected String toStringHeader() + public String toStringHeader() { StringBuilder sb = new StringBuilder(); @@ -1757,7 +1770,8 @@ public abstract class ASScope extends ASScopeBase */ public IWorkspace getWorkspace() { - return getFileScope().getWorkspace(); + ASFileScope fileScope = getFileScope(); + return fileScope != null ? fileScope.getWorkspace() : null; } public String getContainingSourcePath(String qName, ICompilerProject project) diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeCache.java b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeCache.java index 59fa5aeba..4b0311761 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeCache.java +++ b/compiler/src/main/java/org/apache/royale/compiler/internal/scopes/ASScopeCache.java @@ -23,7 +23,6 @@ import org.apache.royale.compiler.common.DependencyType; import org.apache.royale.compiler.config.CompilerDiagnosticsConstants; import org.apache.royale.compiler.constants.IASLanguageConstants; import org.apache.royale.compiler.definitions.IDefinition; -import org.apache.royale.compiler.definitions.IFunctionDefinition; import org.apache.royale.compiler.definitions.IInterfaceDefinition; import org.apache.royale.compiler.definitions.INamespaceDefinition; import org.apache.royale.compiler.definitions.ITypeDefinition; @@ -125,12 +124,9 @@ public class ASScopeCache /** * Version of findProperty that uses a cache. Checks the cache first, and - * only queries the scope if the we don't have a cached result. + * only queries the scope if we don't have a cached result. * - * @param scope The scope to perform the lookup in * @param name Name of the property to find - * @param cache ASDefinitionCache to use for the lookup - this is only used - * to get at the ICompilerProject * @param dt Which type of dependency to introduce when we do the lookup * @return The IDefinition for the property, or null if it wasn't found */ @@ -142,19 +138,24 @@ public class ASScopeCache if (result != null) { // We found a cached result - we're done - // after making sure it has a dependency - if (result instanceof ITypeDefinition) - { - ICompilationUnit from = scope.getFileScope().getCompilationUnit(); - assert result.isInProject(project); - - String qname = result.getQualifiedName(); - ICompilationUnit to = ((ASProjectScope)project.getScope()).getCompilationUnitForDefinition(result); - if (to == null && !(qname.contentEquals("void") || qname.contentEquals("*"))) - System.err.println("No compilation unit for " + qname); - if (to != null) - project.addDependency(from, to, dt, qname); - } + // after making sure it has a dependency + if (result instanceof ITypeDefinition) + { + ASFileScope fileScope = scope.getFileScope(); + //if it is null, it might be partially detached or in the process of reconnection + if (fileScope != null) + { + ICompilationUnit from = fileScope.getCompilationUnit(); + assert result.isInProject(project); + + String qname = result.getQualifiedName(); + ICompilationUnit to = ((ASProjectScope)project.getScope()).getCompilationUnitForDefinition(result); + if (to == null && !(qname.contentEquals("void") || qname.contentEquals("*"))) + System.err.println("No compilation unit for " + qname); + if (to != null) + project.addDependency(from, to, dt, qname); + } + } return result; } @@ -180,7 +181,7 @@ public class ASScopeCache assert def.isInProject(project); break; default: - wasAmbiguous = true; + wasAmbiguous = true; IDefinition d = AmbiguousDefinition.resolveAmbiguities(project, defs, favorTypes); if (d != null) def = d; @@ -189,7 +190,9 @@ public class ASScopeCache { def = project.doubleCheckAmbiguousDefinition(scope, name, defs.get(0), defs.get(1)); if (def != null) + { return def; + } } def = AmbiguousDefinition.get(); } @@ -215,7 +218,6 @@ public class ASScopeCache } } return result; - } private ConcurrentMap<String, IDefinition> getScopeChainMap() @@ -266,12 +268,10 @@ public class ASScopeCache /** * Version of findPropertyQualified that uses a cache. Checks the cache - * first, and only queries the scope if the we don't have a cached result. + * first, and only queries the scope if we don't have a cached result. * - * @param scope The scope to perform the lookup in + * @param qualifier The namespace qualifier * @param name Name of the property to find - * @param cache ASDefinitionCache to use for the lookup - this is only used - * to get at the ICompilerProject * @param dt Which type of dependency to introduce when we do the lookup * @return The IDefinition for the property, or null if it wasn't found */ @@ -360,7 +360,9 @@ public class ASScopeCache ConcurrentMap<IResolvedQualifiersReference, IDefinition> cache = getMultinameLookupMap(); IDefinition result = cache.get(ref); if (result != null) + { return result; + } IDefinition def; @@ -728,5 +730,11 @@ public class ASScopeCache } return false; } + + @Override + public String toString() + { + return ns.toString() + "::" + name; + } } } diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/IdentifierNode.java b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/IdentifierNode.java index df434c321..9ae379395 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/IdentifierNode.java +++ b/compiler/src/main/java/org/apache/royale/compiler/internal/tree/as/IdentifierNode.java @@ -39,10 +39,13 @@ import org.apache.royale.abc.semantics.Nsset; import org.apache.royale.compiler.common.DependencyType; import org.apache.royale.compiler.config.Configuration; import org.apache.royale.compiler.constants.IASLanguageConstants; +import org.apache.royale.compiler.definitions.IAccessorDefinition; import org.apache.royale.compiler.definitions.IClassDefinition; import org.apache.royale.compiler.definitions.IDefinition; +import org.apache.royale.compiler.definitions.IFunctionDefinition; import org.apache.royale.compiler.definitions.INamespaceDefinition; import org.apache.royale.compiler.definitions.ITypeDefinition; +import org.apache.royale.compiler.definitions.IVariableDefinition; import org.apache.royale.compiler.definitions.IQualifiers; import org.apache.royale.compiler.definitions.IVariableDefinition.VariableClassification; import org.apache.royale.compiler.definitions.references.INamespaceReference; @@ -58,6 +61,8 @@ import org.apache.royale.compiler.internal.definitions.NamespaceDefinition; import org.apache.royale.compiler.internal.definitions.VariableDefinition; import org.apache.royale.compiler.internal.projects.RoyaleProject; import org.apache.royale.compiler.internal.scopes.ASScope; +import org.apache.royale.compiler.internal.scopes.ASScopeBase; +import org.apache.royale.compiler.internal.scopes.FunctionScope; import org.apache.royale.compiler.internal.semantics.PostProcessStep; import org.apache.royale.compiler.internal.semantics.SemanticUtils; import org.apache.royale.compiler.internal.tree.as.metadata.DefaultPropertyTagNode; @@ -315,9 +320,17 @@ public class IdentifierNode extends ExpressionNodeBase implements IIdentifierNod @Override public IDefinition resolve(ICompilerProject project) { - if (DefinitionBase.getPerformanceCachingEnabled() && idDef != null) - return idDef; - + IDefinition currentIdDef = idDef; + if (DefinitionBase.getPerformanceCachingEnabled() && currentIdDef != null) + { + return currentIdDef; + } + + return resolveInternal(project); + } + + private IDefinition resolveInternal(ICompilerProject project) + { ASScope asScope = getASScope(); if (asScope == null) @@ -451,7 +464,11 @@ public class IdentifierNode extends ExpressionNodeBase implements IIdentifierNod ((RoyaleProject)project).addToAPIReport(result); } - idDef = result; + if (DefinitionBase.getPerformanceCachingEnabled()) + { + idDef = result; + } + return result; } diff --git a/compiler/src/main/java/org/apache/royale/compiler/internal/workspaces/Workspace.java b/compiler/src/main/java/org/apache/royale/compiler/internal/workspaces/Workspace.java index fa6f4a7ef..283c80cd4 100644 --- a/compiler/src/main/java/org/apache/royale/compiler/internal/workspaces/Workspace.java +++ b/compiler/src/main/java/org/apache/royale/compiler/internal/workspaces/Workspace.java @@ -227,9 +227,12 @@ public final class Workspace implements IWorkspace return executorService; } - private CompilerProject[] getProjects() + public CompilerProject[] getProjects() { - return projects.keySet().toArray(new CompilerProject[0]); + synchronized (projects) + { + return projects.keySet().toArray(new CompilerProject[0]); + } } @Override @@ -1024,7 +1027,10 @@ public final class Workspace implements IWorkspace */ public void deleteProject(ICompilerProject compilerProject) { - projects.remove(compilerProject); + synchronized (projects) + { + projects.remove(compilerProject); + } } @Override @@ -1237,8 +1243,11 @@ public final class Workspace implements IWorkspace public void addProject(CompilerProject project) { - // Need to give a non-null value, the class object for Object - // is a good a non-value as anything. - projects.put(project, Object.class); + synchronized (projects) + { + // Need to give a non-null value, the class object for Object + // is a good a non-value as anything. + projects.put(project, Object.class); + } } }
