Repository navigation
Keep callee register facts across decompiles and warm them for every function - #1422
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On a large image, most of a decompile goes to its callees.
computeCallRegisterEffectstakes the body of every callee in a closure four levels deep: 50 to 220 functions on ntoskrnl. It computes each one's register effect.So most callees were lifted again in every decompile, and every reuse proved the same calls again.
[lowir] Prove each call of a reused body once per cache
validatedSummaryranusesContextandeffect, and each walks the graphs of the callee's callees. What it reads is whether the call refuses the body.X87CallGraphCache::refusesCall, under the request's context.answersHoldkeeps an incomplete-lift graph valid.warmCallGraphsalso keeps this answer for each function it warms.[pipeline] Keep each callee's register facts per analysis cache
computeCallRegisterEffectsreads of a callee's body is kept per analysis cache, inCalleeFactsCache(256 MiB, least recently used out):collectPointerVariableCallees, an image-wide scan) are found once per analysis cache rather than once per run.joinPlainStepsreleases the steps it joins, so a kept effect takes a few kilobytes. Join each run of plain register steps into one #1412 kept their capacity.[worker] Warm every function after the body cache is full
"full". It now goes on to the last function, because each page still keeps callee facts."full"or"complete", as before.neverd_warm_functions_jsondocuments the change.Measurements
Setup:
devat Join each run of plain register steps into one #1412, built from the same tree.ntoskrnl with ntkrnlmp.pdb: 20 functions spread over the image, timing the 19 after the first. Two runs per side, alternating:
devOther images, CPU time of the decompiles:
devxcpt4, every function (306)The sources are identical on every function of all five images, in both ntoskrnl runs.
I also ran a diagnostic that is not part of this change. It recomputes, from scratch, every refusal and every reused callee's facts beside the kept answer, and aborts on any difference. Over ntoskrnl's full warming and the 20 decompiles it found no difference:
Tests
X87CallGraphCache.AReusedBodyProvesEachCallOnceWhileItsAnswersHold:SysVCallContract.ASharedCallTakesTheFormatAnotherBodyGivesIt:ajumps into the formatted call ofb.bbeforeamust not reuse the factsagot without the format.NeverDLiftTests, all 10 shards: 7345/7345.NeverDSessionCAPITests: 187/187, with its ntoskrnl tests.NeverDPipelineOutcomeTests: 42/42. These include the warmed and cached selection tests.Win64CallContract.AThunkJumpingThroughTheSlotForwardsTheArgumentsfails ondevalready, from the stub redirect in [highc][llvmc][libc] Keep C library copies apart; spell traps and stubs as headers do #1403.scripts/check_clang_format.pypasses.