perf(lsp): answer semantic tokens from what the last request resolved #186
Loading…
Reference in a new issue
No description provided.
Delete branch "loop/170-semantic-tokens"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Task
Closes #170
What and why
textDocument/semanticTokens/fullran the highlight resolver over the whole file on every request. Now each open document keeps what its last answer was made of (KeptTokensincompiler/src/language-server/tokens.trb, one per path inProject.tokens):updatedloop ofrefresh, which drops the kept answer (the resolver's tokens stay, they depend on the text only); closing the document drops everything.resolveTopLevelofcompiler/src/highlight/resolve.trb. A walk of one statement gives what the walk of the whole file gives for it, because the resolver reads theFileScopeand its own local scopes and nothing else, and none is open at the file level.When tokens are reused:
mappingBetweenof the kept text and the current one must exist, which is the outline check of section 7.fileScopeOfmust also be equal to the kept one. This guard is needed becausefileScopeOfcollects atype,use,trait,extendoraliaswritten inside a body as a name of the whole file, so a body-only edit can still change what every other statement colors. The test covers that case.After the reuse, the sharpening by the checker's resolutions, the
deprecatedmodifiers and the encoding run as before, so the answer for any text is byte-identical. The legend and capabilities are unchanged (fullonly).The server.trb diff is small: the handler (with its log line), one line in
closedand the invalidation inrefresh.view.trbandanalysis.trbare not touched.Two more commits:
fix(collections):Setequality passedother.containsas a function value, which the seed's native back end cannot build. Nothing in the compiler compared two sets before theFileScopeguard, and the bootstrap's first step failed on it. It is now a closure.perf(lsp)log: a request that makes its answer writes the cost of each part to standard error (semantic tokens of <path>: the global names and the mapping 2, walking 1 of 192 statement(s) 2, the sharpening 22, the deprecated uses 0, the encoding 35). An answer given again logs nothing, which the first test checks.Test
New tests in
compiler/tests/language-server.test.trb, group "Semantic tokens":semanticTokensOfwith nothing kept, over the same view and front. The kept resolver tokens equalresolveFileof the same parse.usechanged, and atypedeclared inside a body each walk the whole file. Each answer equals the from-scratch one.The existing token tests, "hover and tokens inside and outside of a changed body" and
tests/lsp/features.expected, are unchanged.Measurement (LANGUAGE-SERVER.md section 12)
expression.trb(4062 lines, 192 top-level statements, 7722 tokens), over stdio, with the binaries CI built formainand for this branch, two sessions each, taken by turns on a busy 8-core machine. Medians of ten:main(whole file walked every time)Where the time goes: the walk of the whole file is only 6 to 8 ms. The "about 100 ms" of section 15 was the whole request, not the resolver.
Reusing the walk therefore saves little after a keystroke: one statement takes 1 to 2 ms instead of 6 to 8, which is within the noise. The same text asked again is answered in a third to two thirds of the time.
What is left of a keystroke's request (from the new log, medians):
tokens.trb, about what a repeated request costs)Section 15 now names those three costs: the encoding, the sharpening and the JSON answer. Possible next steps are
semanticTokens/full/delta, or keeping the encoding and the sharpening per statement as the walk now is.CI: pending (gates of the forge CI).
CI red on
3a8df27:ci / bootstrap (linux-x64)failed after 5m51s (run 204, job 4);ci / pack the seedfailed after 3s (job 14);ci / gatesfailed as the aggregate and all downstream jobs (tier A/B, cross-compile, archives) were skipped. Cause not seen: I could not read the job logs (no log endpoint on the API for my token, and the CI log tool was unavailable). Please check job 4 first. The review is pending and will run again on the next CI result.CI on
3a8df27failed inbootstrap (linux-x64)step 1 (run 238, job 2910):error: contains used as a function value, which the back end cannot build an instance of ... (at std/collections/src/set.trb:184:39). Cause: this PR comparesFileScopevalues (found.scope == scope), which instantiatesSet.equals, and that usedall(other.contains), a method as a function value the seed's native back end cannot build yet (main never instantiated it in the compiler). Fix inda998a7:all { item => other.contains(item) }, the same closure formMap.equals,List.equalsandQueue.equalsuse; no behaviour change.pack the seedandgatesfailed as consequences of the bootstrap.bcd51a2e65f1dc94914eApproved at
5725ca3(CI: combined status success).resolveFile), outline /use/ body-localtypechanges (whole file walked). CI green for this head.resolve.trb: at file leveldeclareLocalis a no-op and top-level bindings are globals, so a top-level statement's tokens depend only onFileScope, as claimed. Invalidation holds: views are only replaced inrefresh(updatedloop, which now drops the kept answer) or removed viaforgetViews, which forces a whole re-check through the same loop. The answer depends on the view and the document's own file only.closedrops the kept data.movedTokensfalls back to a walk on any length, text or span mismatch.Set.equalsfix is required by the newFileScopecomparison (bootstrap step 1) and is the same closure form as the other collections.LANGUAGE-SERVER.mdsections 12 and 15 updated, with an honest measurement (keystroke gain within noise).Non-blocking:
resolveTopLevelsays "no local scope is open at the file level"; more precisely, locals are never declared there (declareLocal), which is the actual reason the claim holds.walks/walkedStatementsexist only for tests; fine, but worth a one-line note if they stay.