Skip to content

[XELP] Speed up scope validation when many scopes share files - #5

Merged
ljunkie merged 4 commits into
xelp/mainfrom
rob/scope-validation-perf
Oct 11, 2026
Merged

ljunkie merged 4 commits into
xelp/mainfrom
rob/scope-validation-perf

Conversation

@ljunkie

@ljunkie ljunkie commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Every component XML is its own scope, and BrighterScript repeated per-file work in each scope that includes a file. Plex's Roku client has 211 scopes sharing about 70k lines of fat scripts, so validation took about 20s on 0.73.6.

  • Skip the namespace collision walk in scopes with no namespaces.
  • Binary search the top-level functions to find a call's function scope, falling back to the linear search when the functions aren't cleanly ordered, such as after a parse error.
  • Resolve names declared in the file, and whether further checks are needed, once per file.
  • Find computed AA keys once per file instead of walking the whole tree in every scope.

Validation of the Roku client's fat build drops from about 20s to about 5.5s. Diagnostics are identical to plain 0.73.6 on the fat and slim builds.

Timing

Validation of the Roku client's fat build, without bslint, three alternating runs:

Run 0.70 (current) 0.73.6 0.73.6 with this branch
1 13.1s 20.4s 5.5s
2 13.3s 20.0s 5.5s
3 17.8s 19.9s 5.6s

The slim build drops from 13.6s to 5.0s.

How to test

  1. Run npm test.
  2. Run bsc against a Roku client fat build with and without this branch, and compare the time and the diagnostics.

detectVariableNamespaceCollisions walked every parameter and
assignment up to the root of the tree to find an enclosing namespace,
in every scope that includes the file. When the scope has no
namespaces, getNamespace can never match, so return before walking.
Copilot AI balanced review requested due to automatic review settings October 11, 2026 04:10
@ljunkie ljunkie self-assigned this Oct 11, 2026

This comment was marked as resolved.

Copilot AI balanced review requested due to automatic review settings October 11, 2026 04:51
@ljunkie
ljunkie force-pushed the rob/scope-validation-perf branch from f46e3d3 to 13d2c25 Compare October 11, 2026 04:51

This comment was marked as resolved.

findFunctionCalls looks up the function scope of every call, and the
lookup checked each function in the file in turn, so large files
spent most of their parse time here. When every range is present,
the top level functions are in order without overlapping, and each
nested function sits inside its parent, the first scope containing a
position is the top level one a binary search finds. Otherwise, such
as after a parse error, the lookup checks every scope as before.
Copilot AI balanced review requested due to automatic review settings October 11, 2026 05:02
@ljunkie
ljunkie force-pushed the rob/scope-validation-perf branch from 13d2c25 to dbd3f21 Compare October 11, 2026 05:02

This comment was marked as outdated.

iterateFileExpressions looked up the symbol table and the first name
of every expression again for each scope that includes the file. The
tables from an expression up to the file are the same in every scope;
only the file table links on to the scope. So the symbol table, and
whether the first name is declared in the file, are found once per
file. A name declared in the file is always known, and in a scope
with no namespaces or enums it has nothing further to check.

Expressions inside a namespace are left to the full check, since
namespace tables gain scope specific siblings.
validateComputedAAKeys walked the whole tree of every file again for
each scope that includes it, looking for AAIndexedMemberExpression
nodes. The nodes a file holds do not depend on the scope, so collect
them once per file and check each against the scope. Most files have
none, so the check costs nothing for them.
Copilot AI balanced review requested due to automatic review settings October 11, 2026 05:08
@ljunkie
ljunkie force-pushed the rob/scope-validation-perf branch from dbd3f21 to 33c600d Compare October 11, 2026 05:08
@ljunkie
ljunkie merged commit 2583f00 into xelp/main Oct 11, 2026
9 of 10 checks passed
@ljunkie
ljunkie deleted the rob/scope-validation-perf branch October 11, 2026 05:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread src/files/BrsFile.ts
Comment on lines 859 to +863
if (!functionScopes) {
functionScopes = this.functionScopes;
}
//binary search the top level functions instead of checking every function in the file, when they allow it
const topLevelScopes = position && functionScopes === this._functionScopes ? this.getSortedTopLevelFunctionScopes() : undefined;
Comment on lines +186 to 195
const symbolTable = expression.getSymbolTable();
const enclosingNamespaceNameLower = expression.findAncestor(isNamespaceStatement)?.getName(ParseMode.BrighterScript)?.toLowerCase();
result.push({
parts: parts,
expression: expression,
enclosingNamespaceNameLower: expression.findAncestor(isNamespaceStatement)?.getName(ParseMode.BrighterScript)?.toLowerCase()
symbolTable: symbolTable,
//namespace symbol tables gain scope specific siblings, so only resolve outside of namespaces
isDeclaredInFile: !enclosingNamespaceNameLower && this.isDeclaredInFile(file, symbolTable, parts[0].name?.text),
enclosingNamespaceNameLower: enclosingNamespaceNameLower
});
Comment thread src/files/BrsFile.ts
Comment on lines +821 to +825
private getSortedTopLevelFunctionScopes() {
return this.cache.getOrAdd('sortedTopLevelFunctionScopes', () => this.findSortedTopLevelFunctionScopes(this.functionScopes));
}

private findSortedTopLevelFunctionScopes(functionScopes: FunctionScope[]): FunctionScope[] | undefined {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants