mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-25 07:16:12 -04:00
faster deterministic translation (#358)
This commit is contained in:
@@ -139,6 +139,15 @@ TranslationOptions WithProjectFrontEndPolicy(
|
||||
LeafInliningBlockedTargets = leafInliningBlockedTargets.Value
|
||||
};
|
||||
string? translateModInputCacheDirectory = null;
|
||||
// translate-mod reads the same (large) base metadata from several places.
|
||||
var baseMetadataCache = new Dictionary<string, BaseTranslationOutputMetadata>(StringComparer.OrdinalIgnoreCase);
|
||||
BaseTranslationOutputMetadata ReadBaseMetadataCached(string path)
|
||||
{
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
if (!baseMetadataCache.TryGetValue(fullPath, out var metadata))
|
||||
baseMetadataCache[fullPath] = metadata = BaseTranslationOutputMetadataFile.Read(fullPath);
|
||||
return metadata;
|
||||
}
|
||||
string? translateModPublishedOutputDirectory = null;
|
||||
List<AddressRange>? _executableRanges = null;
|
||||
// Callback discovery now runs on the discovery worker pool, so the range cache
|
||||
@@ -472,10 +481,6 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
var pruneStale = HasFlag(argsTail, "--prune-stale");
|
||||
var threadsOption = OptionValue(argsTail, "--threads");
|
||||
var maxThreads = threadsOption != null ? Math.Max(1, ParseInt(threadsOption)) : Math.Max(1, Environment.ProcessorCount);
|
||||
// Discovery batches: cheap front-end-only work items, so the batch is sized
|
||||
// generously to keep queue overhead off the hot path. Deliberately larger
|
||||
// than the mod emission wave below, which carries whole C++ emissions.
|
||||
var batchSize = Math.Max(64, maxThreads * 8);
|
||||
Console.WriteLine($"[translator] Using up to {maxThreads} worker thread(s).");
|
||||
|
||||
// Log start time
|
||||
@@ -493,7 +498,6 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
// callees are decoded separately and never appear here). Feeds the
|
||||
// fall-through interior-entry detection for the base manifest.
|
||||
var translatedFlowCoverage = new Dictionary<uint, uint[]>();
|
||||
var gqrUnknownEntryRoots = new HashSet<uint> { startAddr };
|
||||
var translatedFunctionEnds = new Dictionary<uint, uint>();
|
||||
// Every map entry is a function start, so the whole boundary set is known
|
||||
// before the first decode instead of accumulating as discovery proceeds.
|
||||
@@ -565,13 +569,15 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
|
||||
void ProcessQueue(bool speculative = false)
|
||||
{
|
||||
// Each round decodes the whole frontier: the output depends only on the final
|
||||
// boundary set (boundary repair re-decodes stale owners), never on batching.
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var batch = new List<(uint Address, int Depth, string Name)>(batchSize);
|
||||
var batch = new List<(uint Address, int Depth, string Name)>(queue.Count);
|
||||
var batchAddresses = new HashSet<uint>();
|
||||
var candidates = QueueBatch.Dequeue(
|
||||
queue,
|
||||
batchSize,
|
||||
queue.Count,
|
||||
next => ShouldSkip(next) || !batchAddresses.Add(next.addr));
|
||||
foreach (var next in candidates)
|
||||
{
|
||||
@@ -598,23 +604,19 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
}
|
||||
}
|
||||
|
||||
// Batches drain one at a time so a callee of a prior speculative entry arrives with a known
|
||||
// caller/GQR state instead of as another unknown root. Everything here is speculative, so a
|
||||
// "call" decoded from an unreached map entry isn't proven code; treat failures as soft, not fatal.
|
||||
// Everything here is speculative, so a "call" decoded from an unreached map entry
|
||||
// isn't proven code; treat failures as soft, not fatal.
|
||||
void ProcessSpeculativeSeeds()
|
||||
{
|
||||
while (speculativeSeeds.Count > 0)
|
||||
{
|
||||
var batch = new List<(uint Address, int Depth, string Name)>(batchSize);
|
||||
var batch = new List<(uint Address, int Depth, string Name)>(speculativeSeeds.Count);
|
||||
var candidates = QueueBatch.Dequeue(
|
||||
speculativeSeeds,
|
||||
batchSize,
|
||||
speculativeSeeds.Count,
|
||||
address => translated.Contains(address) || !visited.Add(address));
|
||||
foreach (var address in candidates)
|
||||
{
|
||||
// Nothing calls this from the translated set, so its entry
|
||||
// GQR state is unconstrained.
|
||||
gqrUnknownEntryRoots.Add(address);
|
||||
batch.Add((address, 0, ResolveFunctionName(address, preferred: null, $"func_{address:X8}")));
|
||||
}
|
||||
|
||||
@@ -652,22 +654,42 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
// decoded out of one - is dropped with a counted warning.
|
||||
void TranslateBatch(List<(uint Address, int Depth, string Name)> batch, bool speculative = false)
|
||||
{
|
||||
var results = new FunctionDiscoveryResult?[batch.Count];
|
||||
IndexedParallel.For(batch.Count, parallelOptions, i => results[i] = Attempt(batch[i], speculative));
|
||||
var metrics = new TranslationMetrics?[batch.Count];
|
||||
var entryPoints = new uint[batch.Count];
|
||||
var discoveryTargets = new uint[batch.Count][];
|
||||
var swallowedSiblingEntries = new uint[batch.Count][];
|
||||
var summaries = new GqrFunctionSummary[batch.Count];
|
||||
var functionEnds = new uint[batch.Count];
|
||||
var flowCoverage = new uint[batch.Count][];
|
||||
|
||||
// Everything kept from a decode is derived in the decode's own pass, so the full
|
||||
// result is never retained; the few results a newly revealed boundary invalidates
|
||||
// are simply decoded again. Commits stay serial and in batch order.
|
||||
void Decode(int i, bool findSwallowedSiblings)
|
||||
{
|
||||
var result = Attempt(batch[i], speculative);
|
||||
metrics[i] = result?.Metrics;
|
||||
if (result is null)
|
||||
{
|
||||
canonicalIrStore.Remove(batch[i].Address);
|
||||
return;
|
||||
}
|
||||
entryPoints[i] = result.EntryPoint;
|
||||
if (findSwallowedSiblings)
|
||||
swallowedSiblingEntries[i] = DiscoverSwallowedSiblingEntries(result).ToArray();
|
||||
discoveryTargets[i] = DiscoverDiscoveryTranslationTargets(result).ToArray();
|
||||
summaries[i] = GqrFunctionSummary.Create(result.Ssa.Function);
|
||||
canonicalIrStore.Put(batch[i].Address, result.Ssa.Function);
|
||||
functionEnds[i] = GetFunctionInstructionEnd(result);
|
||||
flowCoverage[i] = CoalesceInstructionRanges(result);
|
||||
}
|
||||
|
||||
IndexedParallel.For(batch.Count, parallelOptions, i => Decode(i, findSwallowedSiblings: true));
|
||||
|
||||
// Re-run only translations that swallowed a newly revealed sibling entry, or direct
|
||||
// branches could bypass a mod overlay registered there. Both scans are pure per-result
|
||||
// so they run on the same worker pool; the dedup filter below keeps first sighting.
|
||||
var discoveryTargets = new uint[results.Length][];
|
||||
var swallowedSiblingEntries = new uint[results.Length][];
|
||||
IndexedParallel.For(results.Length, parallelOptions, i =>
|
||||
{
|
||||
if (results[i] is not { } result) return;
|
||||
discoveryTargets[i] = DiscoverDiscoveryTranslationTargets(result).ToArray();
|
||||
swallowedSiblingEntries[i] = DiscoverSwallowedSiblingEntries(result).ToArray();
|
||||
});
|
||||
var newlyKnownEntries = Enumerable.Range(0, results.Length)
|
||||
.Where(i => results[i] is not null)
|
||||
// branches could bypass a mod overlay registered there. The dedup filter keeps first sighting.
|
||||
var newlyKnownEntries = Enumerable.Range(0, batch.Count)
|
||||
.Where(i => metrics[i] is not null)
|
||||
.SelectMany(i => discoveryTargets[i].Concat(swallowedSiblingEntries[i]))
|
||||
.Where(knownBaseFunctionEntryPoints.Add)
|
||||
.ToHashSet();
|
||||
@@ -675,49 +697,39 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
{
|
||||
// The boundary set is final for this batch once the filter above has run,
|
||||
// so the affected re-translations are independent of one another.
|
||||
var affected = new List<int>();
|
||||
for (var i = 0; i < results.Length; i++)
|
||||
{
|
||||
if (results[i] is not { } result) continue;
|
||||
var entry = result.EntryPoint;
|
||||
if (result.Instructions.Any(instruction =>
|
||||
instruction.Address != entry && newlyKnownEntries.Contains(instruction.Address)))
|
||||
{
|
||||
affected.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
IndexedParallel.For(affected.Count, parallelOptions, k =>
|
||||
{
|
||||
var i = affected[k];
|
||||
results[i] = Attempt(batch[i], speculative);
|
||||
if (results[i] is { } result)
|
||||
{
|
||||
discoveryTargets[i] = DiscoverDiscoveryTranslationTargets(result).ToArray();
|
||||
}
|
||||
});
|
||||
var sortedNewEntries = newlyKnownEntries.Order().ToArray();
|
||||
var affected = Enumerable.Range(0, batch.Count)
|
||||
.Where(i => metrics[i] is not null &&
|
||||
CoversInteriorInstruction(flowCoverage[i], entryPoints[i], sortedNewEntries))
|
||||
.ToArray();
|
||||
IndexedParallel.For(affected.Length, parallelOptions, k => Decode(affected[k], findSwallowedSiblings: false));
|
||||
}
|
||||
|
||||
// The compact GQR projection is a pure walk of the final SSA graph and the
|
||||
// canonical store serializes its own dictionary, so both move off the serial
|
||||
// commit. Every address in a batch is distinct and untranslated, so the store
|
||||
// sees one Put per key regardless of completion order.
|
||||
var summaries = new GqrFunctionSummary[results.Length];
|
||||
IndexedParallel.For(results.Length, parallelOptions, i =>
|
||||
{
|
||||
if (results[i] is not { } result) return;
|
||||
summaries[i] = GqrFunctionSummary.Create(result.Ssa.Function);
|
||||
canonicalIrStore.Put(batch[i].Address, result.Ssa.Function);
|
||||
});
|
||||
|
||||
// Update state sequentially (thread-safe)
|
||||
for (var i = 0; i < batch.Count; i++)
|
||||
{
|
||||
if (results[i] is not { } result) continue;
|
||||
CommitResult(batch[i], result, discoveryTargets[i], summaries[i]);
|
||||
if (metrics[i] is not { } functionMetrics) continue;
|
||||
CommitResult(batch[i], functionMetrics, discoveryTargets[i], summaries[i], functionEnds[i], flowCoverage[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// True when an instruction of the coalesced [start, end) coverage, other than the
|
||||
// entry, starts at one of the sorted addresses.
|
||||
static bool CoversInteriorInstruction(uint[] coverage, uint entry, uint[] sortedAddresses)
|
||||
{
|
||||
for (var r = 0; r < coverage.Length; r += 2)
|
||||
{
|
||||
var index = Array.BinarySearch(sortedAddresses, coverage[r]);
|
||||
for (index = index >= 0 ? index : ~index;
|
||||
index < sortedAddresses.Length && sortedAddresses[index] < coverage[r + 1];
|
||||
index++)
|
||||
{
|
||||
var address = sortedAddresses[index];
|
||||
if (address != entry && (address & 3u) == 0) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FunctionDiscoveryResult? Attempt((uint Address, int Depth, string Name) work, bool speculative)
|
||||
{
|
||||
if (!speculative)
|
||||
@@ -773,16 +785,18 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
|
||||
void CommitResult(
|
||||
(uint Address, int Depth, string Name) work,
|
||||
FunctionDiscoveryResult result,
|
||||
TranslationMetrics functionMetrics,
|
||||
IReadOnlyList<uint> discoveryTargets,
|
||||
GqrFunctionSummary summary)
|
||||
GqrFunctionSummary summary,
|
||||
uint functionEnd,
|
||||
uint[] flowCoverage)
|
||||
{
|
||||
// File writing moved to parallel section above
|
||||
totals.Add(result.Metrics);
|
||||
totals.Add(functionMetrics);
|
||||
count++;
|
||||
translated.Add(work.Address);
|
||||
translatedFunctionEnds[work.Address] = GetFunctionInstructionEnd(result);
|
||||
translatedFlowCoverage[work.Address] = CoalesceInstructionRanges(result);
|
||||
translatedFunctionEnds[work.Address] = functionEnd;
|
||||
translatedFlowCoverage[work.Address] = flowCoverage;
|
||||
|
||||
foreach (var target in discoveryTargets)
|
||||
{
|
||||
@@ -821,6 +835,7 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
// list that existed to paper over the addresses they still missed.
|
||||
ProcessQueue();
|
||||
var walkReachable = count;
|
||||
var walkTranslated = translated.ToHashSet();
|
||||
ProcessSpeculativeSeeds();
|
||||
if (functionMap is not null)
|
||||
{
|
||||
@@ -864,26 +879,31 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
if (boundaryRepairAddresses.Length != 0)
|
||||
{
|
||||
Console.WriteLine($"[translator] Reanalyzing {boundaryRepairAddresses.Length:N0} function(s) affected by later-discovered boundaries...");
|
||||
var repaired = new FunctionDiscoveryResult[boundaryRepairAddresses.Length];
|
||||
var repairedSummaries = new GqrFunctionSummary[boundaryRepairAddresses.Length];
|
||||
var repairedEnds = new uint[boundaryRepairAddresses.Length];
|
||||
var repairedCoverage = new uint[boundaryRepairAddresses.Length][];
|
||||
IndexedParallel.For(boundaryRepairAddresses.Length, parallelOptions, i =>
|
||||
{
|
||||
var address = boundaryRepairAddresses[i];
|
||||
var work = baseWork[address];
|
||||
repaired[i] = translator.Value.Discover(address, WithProjectFrontEndPolicy(new TranslationOptions(
|
||||
var repaired = translator.Value.Discover(address, WithProjectFrontEndPolicy(new TranslationOptions(
|
||||
work.Name,
|
||||
MaxInstructions: work.MaxInstructions,
|
||||
MaxBytes: work.MaxBytes,
|
||||
AllowUnsupportedInstructions: work.AllowUnsupported,
|
||||
KnownFunctionEntryPoints: knownBaseFunctionEntryPoints),
|
||||
RequireProject().Translation));
|
||||
repairedSummaries[i] = GqrFunctionSummary.Create(repaired.Ssa.Function);
|
||||
canonicalIrStore.Put(address, repaired.Ssa.Function);
|
||||
repairedEnds[i] = GetFunctionInstructionEnd(repaired);
|
||||
repairedCoverage[i] = CoalesceInstructionRanges(repaired);
|
||||
});
|
||||
for (var i = 0; i < boundaryRepairAddresses.Length; i++)
|
||||
{
|
||||
var address = boundaryRepairAddresses[i];
|
||||
baseSummaries[address] = GqrFunctionSummary.Create(repaired[i].Ssa.Function);
|
||||
canonicalIrStore.Put(address, repaired[i].Ssa.Function);
|
||||
translatedFunctionEnds[address] = GetFunctionInstructionEnd(repaired[i]);
|
||||
translatedFlowCoverage[address] = CoalesceInstructionRanges(repaired[i]);
|
||||
baseSummaries[address] = repairedSummaries[i];
|
||||
translatedFunctionEnds[address] = repairedEnds[i];
|
||||
translatedFlowCoverage[address] = repairedCoverage[i];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -906,6 +926,12 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
}
|
||||
|
||||
Console.WriteLine("[translator] Computing interprocedural GQR constants and callee write summaries...");
|
||||
// Map entries the walk never reached have an unconstrained entry GQR state unless some
|
||||
// translated function calls them directly.
|
||||
var gqrUnknownEntryRoots = GqrInterproceduralAnalysis.SelectUnknownEntryRoots(
|
||||
baseSummaries,
|
||||
[startAddr],
|
||||
(functionMap?.Addresses ?? Array.Empty<uint>()).Where(address => !walkTranslated.Contains(address)));
|
||||
var gqrAnalysis = GqrInterproceduralAnalysis.Analyze(
|
||||
baseSummaries,
|
||||
gqrUnknownEntryRoots);
|
||||
@@ -1183,7 +1209,7 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
// Populate the recursive ABI inference cache once, in stable global order, before
|
||||
// refinement and parallel emission, so cyclic call graphs don't depend on thread count.
|
||||
LogPhase("state-free selection + leaf residency");
|
||||
ReportAbiPrewarm(inferredGuestAbi.Value.Prewarm(emissionAddresses));
|
||||
ReportAbiPrewarm(inferredGuestAbi.Value.Prewarm(emissionAddresses, maxThreads));
|
||||
LogPhase("ABI prewarm");
|
||||
|
||||
// State-free entries use the transformed leaf's exact live-at-entry
|
||||
@@ -1404,6 +1430,7 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
// source visible. `completionPulse` only wakes the writer; it carries
|
||||
// no state of its own.
|
||||
var emissionDone = new bool[pendingSources.Length];
|
||||
var unsupportedByIndex = new int[emissionAddresses.Length];
|
||||
using var completionPulse = new ManualResetEventSlim(false);
|
||||
var emissionAborted = false;
|
||||
Task? pendingBundleWrite = null;
|
||||
@@ -1459,6 +1486,8 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
var options = BuildEmissionOptions(address, stateFreeAbiContracts);
|
||||
if (!canonicalIrStore.TryGet(address, out var canonicalSsa))
|
||||
throw new InvalidOperationException($"Missing canonical SSA for final lowering at 0x{address:X8}.");
|
||||
unsupportedByIndex[i] = canonicalSsa.Blocks.Sum(block =>
|
||||
block.Instructions.Count(static instruction => instruction is IrUndefined));
|
||||
var emitted = translator.Value.LowerCanonical(address, canonicalSsa, options);
|
||||
var path = work.Path;
|
||||
if (outputMetadata is not null)
|
||||
@@ -1517,16 +1546,7 @@ int RunTranslateRecursive(string[] argsTail)
|
||||
: Path.GetRelativePath(
|
||||
Path.GetDirectoryName(Path.GetFullPath(outputMetadataPath!))!,
|
||||
Path.GetFullPath(productionSourceBundlePath)).Replace('\\', '/');
|
||||
var metadataUnsupportedInstructionCount = emissionAddresses.Sum(address =>
|
||||
{
|
||||
if (!canonicalIrStore.TryGet(address, out var canonicalSsa))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Missing canonical SSA while computing translation quality for 0x{address:X8}.");
|
||||
}
|
||||
return canonicalSsa.Blocks.Sum(block =>
|
||||
block.Instructions.Count(static instruction => instruction is IrUndefined));
|
||||
});
|
||||
var metadataUnsupportedInstructionCount = unsupportedByIndex.Sum();
|
||||
completedOutputMetadata = BaseTranslationOutputMetadata.Create(
|
||||
outputMetadata,
|
||||
new TranslationQualityMetadata(
|
||||
@@ -1701,7 +1721,7 @@ int RunTranslateModCore(string[] argsTail, string? outputDirectoryOverride)
|
||||
// win (this shipped once as a character-select LinkList.h:573 panic).
|
||||
if (File.Exists(baseTranslationOutputMetadataPath) && File.Exists(codePul))
|
||||
{
|
||||
var guardedBaseMetadata = BaseTranslationOutputMetadataFile.Read(baseTranslationOutputMetadataPath);
|
||||
var guardedBaseMetadata = ReadBaseMetadataCached(baseTranslationOutputMetadataPath);
|
||||
var codePulSha256 = ChecksumUtilities.Sha256HexOfFile(codePul);
|
||||
var baseKnowsThisPul = guardedBaseMetadata.ModPatchAwareness is { } awareness &&
|
||||
awareness.Any(entry => string.Equals(entry.CodePulSha256, codePulSha256, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -2079,7 +2099,7 @@ void WriteResolvedDispatchProfile(
|
||||
var metadataPath = baseTranslationOutputMetadataPath;
|
||||
if (File.Exists(metadataPath))
|
||||
{
|
||||
var metadata = BaseTranslationOutputMetadataFile.Read(metadataPath);
|
||||
var metadata = ReadBaseMetadataCached(metadataPath);
|
||||
if (!string.IsNullOrWhiteSpace(metadata.SourceBundlePath))
|
||||
{
|
||||
var bundlePath = Path.IsPathRooted(metadata.SourceBundlePath)
|
||||
@@ -2154,7 +2174,7 @@ void WriteResolvedDispatchProfile(
|
||||
BaseTranslationOutputMetadata? metadata = null;
|
||||
if (File.Exists(baseTranslationOutputMetadataPath))
|
||||
{
|
||||
metadata = BaseTranslationOutputMetadataFile.Read(baseTranslationOutputMetadataPath);
|
||||
metadata = ReadBaseMetadataCached(baseTranslationOutputMetadataPath);
|
||||
}
|
||||
if (metadata is null || metadata.Functions.Any(static function => function.Build is null))
|
||||
{
|
||||
@@ -2326,12 +2346,9 @@ int EmitModCpp(
|
||||
: new CompositeGuestFunctionAbiProvider(inferredAbi);
|
||||
var modTranslator = new FunctionTranslator(modImage, abiProvider);
|
||||
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = maxThreads };
|
||||
// Mod emission waves: each item is a full translate+emit, and each wave is a
|
||||
// barrier that also drains newly discovered continuations, so it is
|
||||
// deliberately smaller than the discovery batch above - a long wave delays
|
||||
// the discoveries the next wave has to translate.
|
||||
var waveSize = Math.Max(16, maxThreads * 4);
|
||||
Console.WriteLine($" translating mod C++ with up to {maxThreads} worker thread(s) in waves of {waveSize}");
|
||||
// Each wave drains its whole queue: a wave is a barrier that publishes newly discovered
|
||||
// entries, and draining everything makes the output independent of any wave size.
|
||||
Console.WriteLine($" translating mod C++ with up to {maxThreads} worker thread(s)");
|
||||
|
||||
var cppRoot = Path.Combine(outDir, "cpp");
|
||||
if (Directory.Exists(cppRoot))
|
||||
@@ -2410,7 +2427,7 @@ int EmitModCpp(
|
||||
// Some dynamically discovered module/continuation waves bypass
|
||||
// ProcessBoundedWaves. Prewarm every parallel emission boundary as a
|
||||
// final deterministic guard; phase-prewarmed entries are cheap hits.
|
||||
ReportAbiPrewarm(inferredAbi.Prewarm(wave.Select(static work => work.Address)));
|
||||
ReportAbiPrewarm(inferredAbi.Prewarm(wave.Select(static work => work.Address), maxThreads));
|
||||
return ModTranslationWaveRunner.Translate(() => modTranslator, wave, parallelOptions);
|
||||
}
|
||||
|
||||
@@ -2447,7 +2464,7 @@ int EmitModCpp(
|
||||
// Populate the recursive ABI cache in a stable global order before any worker emits C++,
|
||||
// so shared closure reuse and cyclic call graphs never depend on thread count, wave size,
|
||||
// or worker order.
|
||||
ReportAbiPrewarm(inferredAbi.Prewarm(ordered.Select(static work => work.Address)));
|
||||
ReportAbiPrewarm(inferredAbi.Prewarm(ordered.Select(static work => work.Address), maxThreads));
|
||||
// Commit callbacks here only append to the discovered-continuation queue that a later
|
||||
// phase drains, so nothing in this phase reads back its own commits; wave barriers would
|
||||
// only cost idle workers, so it runs as one parallel batch and the runner re-sorts by the
|
||||
@@ -2592,7 +2609,7 @@ int EmitModCpp(
|
||||
{
|
||||
while (discoveredContinuationQueue.Count > 0)
|
||||
{
|
||||
var wave = QueueBatch.Dequeue(discoveredContinuationQueue, waveSize)
|
||||
var wave = QueueBatch.Dequeue(discoveredContinuationQueue, discoveredContinuationQueue.Count)
|
||||
.Select(CreateContinuationWork)
|
||||
.ToArray();
|
||||
CommitWave(TranslateWave(wave), (result, work) =>
|
||||
@@ -2611,10 +2628,10 @@ int EmitModCpp(
|
||||
var emittedModuleStarts = new HashSet<uint>();
|
||||
while (moduleQueue.Count > 0)
|
||||
{
|
||||
var wave = new List<ModTranslationWork>(waveSize);
|
||||
var wave = new List<ModTranslationWork>(moduleQueue.Count);
|
||||
foreach (var start in QueueBatch.Dequeue(
|
||||
moduleQueue,
|
||||
waveSize,
|
||||
moduleQueue.Count,
|
||||
start => !emittedModuleStarts.Add(start.Address)))
|
||||
{
|
||||
var name = ModuleFunctionName(start.Address);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||
<TieredPGO>false</TieredPGO>
|
||||
<VersionPrefix>0.5.0</VersionPrefix>
|
||||
<Company>WiiCompiled</Company>
|
||||
<Product>Mario Kart Wii Static Translator</Product>
|
||||
|
||||
@@ -102,12 +102,7 @@ public static class GuestMemoryRangeLowering
|
||||
.Any(group => group.Count() >= minimumAccesses))
|
||||
return function;
|
||||
|
||||
var cfg = IrCfg.Build(function);
|
||||
var dominators = ComputeDominators(cfg);
|
||||
var reachability = ComputeReachability(cfg);
|
||||
var cyclicBlocks = cfg.Blocks.Keys.Where(label =>
|
||||
cfg.Successors(label).Any(successor => reachability[successor].Contains(label)))
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
var graph = BlockGraph.Build(function);
|
||||
|
||||
var plans = new Dictionary<(int Block, int Instruction), Plan>();
|
||||
var resolves = new Dictionary<int, List<PlacedResolve>>();
|
||||
@@ -129,24 +124,35 @@ public static class GuestMemoryRangeLowering
|
||||
if (definitionSites.Count != 1) continue;
|
||||
definition = definitionSites[0];
|
||||
var defined = definitionSites[0];
|
||||
var definitionBlock = function.Blocks[defined.BlockIndex].Label;
|
||||
if (remaining.Any(candidate =>
|
||||
!dominators[candidate.AccessBlockLabel(function)].Contains(definitionBlock) ||
|
||||
!graph.Dominates(defined.BlockIndex, candidate.BlockIndex) ||
|
||||
candidate.BlockIndex == defined.BlockIndex &&
|
||||
candidate.Access.Index <= defined.InstructionIndex))
|
||||
continue;
|
||||
}
|
||||
// The pair predicate is invariant across rounds, so memoize it.
|
||||
var groupIndex = new Dictionary<FunctionAccess, int>(ReferenceEqualityComparer.Instance);
|
||||
foreach (var access in remaining) groupIndex.Add(access, groupIndex.Count);
|
||||
var compatible = remaining.Count <= 4096 ? new sbyte[remaining.Count * remaining.Count] : null;
|
||||
bool Compatible(FunctionAccess anchor, FunctionAccess candidate)
|
||||
{
|
||||
if (compatible is null)
|
||||
return DominatesAccess(graph, anchor, candidate) &&
|
||||
!HasInvalidatingBoundaryOnPath(graph, invalidators, anchor, candidate);
|
||||
var slot = groupIndex[anchor] * groupIndex.Count + groupIndex[candidate];
|
||||
if (compatible[slot] != 0) return compatible[slot] > 0;
|
||||
var value = DominatesAccess(graph, anchor, candidate) &&
|
||||
!HasInvalidatingBoundaryOnPath(graph, invalidators, anchor, candidate);
|
||||
compatible[slot] = value ? (sbyte)1 : (sbyte)-1;
|
||||
return value;
|
||||
}
|
||||
while (remaining.Count >= minimumAccesses)
|
||||
{
|
||||
FunctionAccess? bestAnchor = null;
|
||||
FunctionAccess[] bestMembers = [];
|
||||
foreach (var anchor in remaining)
|
||||
{
|
||||
var members = remaining.Where(candidate =>
|
||||
DominatesAccess(function, dominators, anchor, candidate) &&
|
||||
!HasInvalidatingBoundaryOnPath(
|
||||
function, reachability, cyclicBlocks,
|
||||
invalidators, anchor, candidate))
|
||||
var members = remaining.Where(candidate => Compatible(anchor, candidate))
|
||||
.OrderBy(candidate => candidate.Access.Index)
|
||||
.ToList();
|
||||
// Keep a bounded affine window around the real anchor. A
|
||||
@@ -248,15 +254,13 @@ public static class GuestMemoryRangeLowering
|
||||
}
|
||||
|
||||
private static bool DominatesAccess(
|
||||
IrFunction function,
|
||||
IReadOnlyDictionary<string, HashSet<string>> dominators,
|
||||
BlockGraph graph,
|
||||
FunctionAccess anchor,
|
||||
FunctionAccess consumer)
|
||||
{
|
||||
if (anchor.BlockIndex == consumer.BlockIndex)
|
||||
return anchor.Access.Index <= consumer.Access.Index;
|
||||
return dominators[consumer.AccessBlockLabel(function)].Contains(
|
||||
anchor.AccessBlockLabel(function));
|
||||
return graph.Dominates(anchor.BlockIndex, consumer.BlockIndex);
|
||||
}
|
||||
|
||||
private static bool HasOverlappingOppositeAccess(
|
||||
@@ -273,27 +277,25 @@ public static class GuestMemoryRangeLowering
|
||||
}
|
||||
|
||||
private static bool HasInvalidatingBoundaryOnPath(
|
||||
IrFunction function,
|
||||
IReadOnlyDictionary<string, HashSet<string>> reachability,
|
||||
IReadOnlySet<string> cyclicBlocks,
|
||||
BlockGraph graph,
|
||||
IReadOnlyList<InstructionSite> invalidators,
|
||||
FunctionAccess anchor,
|
||||
FunctionAccess consumer)
|
||||
{
|
||||
var anchorLabel = anchor.AccessBlockLabel(function);
|
||||
var consumerLabel = consumer.AccessBlockLabel(function);
|
||||
var anchorBlock = anchor.BlockIndex;
|
||||
var consumerBlock = consumer.BlockIndex;
|
||||
var consumerReentered = anchorBlock != consumerBlock && graph.Cyclic[consumerBlock];
|
||||
foreach (var invalidator in invalidators)
|
||||
{
|
||||
var boundaryLabel = function.Blocks[invalidator.BlockIndex].Label;
|
||||
var afterAnchor = invalidator.BlockIndex == anchor.BlockIndex
|
||||
var boundaryBlock = invalidator.BlockIndex;
|
||||
var afterAnchor = boundaryBlock == anchorBlock
|
||||
? invalidator.InstructionIndex > anchor.Access.Index
|
||||
: reachability[anchorLabel].Contains(boundaryLabel);
|
||||
var beforeConsumer = invalidator.BlockIndex == consumer.BlockIndex
|
||||
? invalidator.InstructionIndex < consumer.Access.Index ||
|
||||
anchor.BlockIndex != consumer.BlockIndex &&
|
||||
cyclicBlocks.Contains(consumerLabel)
|
||||
: reachability[boundaryLabel].Contains(consumerLabel);
|
||||
if (afterAnchor && beforeConsumer) return true;
|
||||
: graph.Reaches(anchorBlock, boundaryBlock);
|
||||
if (!afterAnchor) continue;
|
||||
var beforeConsumer = boundaryBlock == consumerBlock
|
||||
? invalidator.InstructionIndex < consumer.Access.Index || consumerReentered
|
||||
: graph.Reaches(boundaryBlock, consumerBlock);
|
||||
if (beforeConsumer) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -315,54 +317,117 @@ public static class GuestMemoryRangeLowering
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, HashSet<string>> ComputeDominators(IrCfg cfg)
|
||||
/// <summary>Dominator and reachability sets as bitsets over block ordinals.</summary>
|
||||
private sealed class BlockGraph
|
||||
{
|
||||
var comparer = StringComparer.OrdinalIgnoreCase;
|
||||
var labels = cfg.Blocks.Keys.ToArray();
|
||||
var all = labels.ToHashSet(comparer);
|
||||
var result = labels.ToDictionary(label => label,
|
||||
label => label.Equals(cfg.Entry, StringComparison.OrdinalIgnoreCase)
|
||||
? new HashSet<string>([label], comparer)
|
||||
: new HashSet<string>(all, comparer), comparer);
|
||||
bool changed;
|
||||
do
|
||||
{
|
||||
changed = false;
|
||||
foreach (var label in labels)
|
||||
{
|
||||
if (label.Equals(cfg.Entry, StringComparison.OrdinalIgnoreCase)) continue;
|
||||
var predecessors = cfg.Predecessors(label);
|
||||
var next = predecessors.Count == 0
|
||||
? new HashSet<string>(comparer)
|
||||
: new HashSet<string>(result[predecessors[0]], comparer);
|
||||
foreach (var predecessor in predecessors.Skip(1)) next.IntersectWith(result[predecessor]);
|
||||
next.Add(label);
|
||||
if (result[label].SetEquals(next)) continue;
|
||||
result[label] = next;
|
||||
changed = true;
|
||||
}
|
||||
} while (changed);
|
||||
return result;
|
||||
}
|
||||
private readonly ulong[][] _dominators;
|
||||
private readonly ulong[][] _reach;
|
||||
|
||||
private static Dictionary<string, HashSet<string>> ComputeReachability(IrCfg cfg)
|
||||
{
|
||||
var comparer = StringComparer.OrdinalIgnoreCase;
|
||||
var result = new Dictionary<string, HashSet<string>>(comparer);
|
||||
foreach (var start in cfg.Blocks.Keys)
|
||||
private BlockGraph(ulong[][] dominators, ulong[][] reach, bool[] cyclic)
|
||||
{
|
||||
var reachable = new HashSet<string>(comparer);
|
||||
var pending = new Stack<string>();
|
||||
pending.Push(start);
|
||||
while (pending.Count != 0)
|
||||
{
|
||||
var current = pending.Pop();
|
||||
if (!reachable.Add(current)) continue;
|
||||
foreach (var successor in cfg.Successors(current)) pending.Push(successor);
|
||||
}
|
||||
result[start] = reachable;
|
||||
_dominators = dominators;
|
||||
_reach = reach;
|
||||
Cyclic = cyclic;
|
||||
}
|
||||
|
||||
/// <summary>Blocks that can reach themselves again through a successor.</summary>
|
||||
public bool[] Cyclic { get; }
|
||||
|
||||
public bool Dominates(int dominator, int block) =>
|
||||
(_dominators[block][dominator >> 6] & (1UL << (dominator & 63))) != 0;
|
||||
|
||||
/// <summary>Reflexive: every block reaches itself.</summary>
|
||||
public bool Reaches(int from, int to) =>
|
||||
(_reach[from][to >> 6] & (1UL << (to & 63))) != 0;
|
||||
|
||||
public static BlockGraph Build(IrFunction function)
|
||||
{
|
||||
var cfg = IrCfg.Build(function);
|
||||
var count = function.Blocks.Count;
|
||||
var words = (count + 63) >> 6;
|
||||
var index = new Dictionary<string, int>(count, StringComparer.OrdinalIgnoreCase);
|
||||
for (var i = 0; i < count; i++) index.Add(function.Blocks[i].Label, i);
|
||||
var successors = new int[count][];
|
||||
var predecessors = new int[count][];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var label = function.Blocks[i].Label;
|
||||
successors[i] = cfg.Successors(label).Select(successor => index[successor]).ToArray();
|
||||
predecessors[i] = cfg.Predecessors(label).Select(predecessor => index[predecessor]).ToArray();
|
||||
}
|
||||
|
||||
var reach = new ulong[count][];
|
||||
var pending = new Stack<int>();
|
||||
for (var start = 0; start < count; start++)
|
||||
{
|
||||
var row = new ulong[words];
|
||||
pending.Push(start);
|
||||
while (pending.Count != 0)
|
||||
{
|
||||
var current = pending.Pop();
|
||||
ref var word = ref row[current >> 6];
|
||||
var bit = 1UL << (current & 63);
|
||||
if ((word & bit) != 0) continue;
|
||||
word |= bit;
|
||||
foreach (var successor in successors[current]) pending.Push(successor);
|
||||
}
|
||||
reach[start] = row;
|
||||
}
|
||||
|
||||
var cyclic = new bool[count];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
foreach (var successor in successors[i])
|
||||
{
|
||||
if ((reach[successor][i >> 6] & (1UL << (i & 63))) == 0) continue;
|
||||
cyclic[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var entry = index[cfg.Entry];
|
||||
var dominators = new ulong[count][];
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
dominators[i] = new ulong[words];
|
||||
if (i == entry)
|
||||
{
|
||||
dominators[i][i >> 6] = 1UL << (i & 63);
|
||||
continue;
|
||||
}
|
||||
Array.Fill(dominators[i], ulong.MaxValue);
|
||||
if ((count & 63) != 0) dominators[i][words - 1] = (1UL << (count & 63)) - 1;
|
||||
}
|
||||
var next = new ulong[words];
|
||||
bool changed;
|
||||
do
|
||||
{
|
||||
changed = false;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (i == entry) continue;
|
||||
var preds = predecessors[i];
|
||||
if (preds.Length == 0)
|
||||
{
|
||||
Array.Clear(next);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(dominators[preds[0]], next, words);
|
||||
for (var p = 1; p < preds.Length; p++)
|
||||
{
|
||||
var other = dominators[preds[p]];
|
||||
for (var w = 0; w < words; w++) next[w] &= other[w];
|
||||
}
|
||||
}
|
||||
next[i >> 6] |= 1UL << (i & 63);
|
||||
if (next.AsSpan().SequenceEqual(dominators[i])) continue;
|
||||
next.AsSpan().CopyTo(dominators[i]);
|
||||
changed = true;
|
||||
}
|
||||
} while (changed);
|
||||
return new BlockGraph(dominators, reach, cyclic);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static IrInstruction ToResolved(IrInstruction instruction, Access access, Plan plan)
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Translator.Core.Analysis;
|
||||
@@ -72,6 +73,9 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
/// <summary>Reusable backing list for <see cref="FrameTaint"/>; phase A is never reentrant.</summary>
|
||||
public readonly List<bool> FrameTaintBuffer = new();
|
||||
|
||||
/// <summary>Address per <see cref="FrameTaint"/> entry, innermost last.</summary>
|
||||
public readonly List<uint> FrameAddresses = new();
|
||||
|
||||
/// <summary>Addresses already known tainted, shared by every phase-A worker of the current Prewarm
|
||||
/// call and reset each invocation. Without this memoization an unpublishable address gets
|
||||
/// re-analyzed, subtree and all, by every caller that reaches it, which compounds combinatorially
|
||||
@@ -95,14 +99,21 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
return false;
|
||||
}
|
||||
|
||||
var state = _threadState.Value!;
|
||||
var frameTaint = state.FrameTaint;
|
||||
if (frameTaint is not null && state.FrameAddresses.Count > 0 && state.FrameAddresses[^1] == address)
|
||||
{
|
||||
// Direct self-recursion sees what a sequential run would, even once another thread has published it.
|
||||
abi = GuestFunctionAbi.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_cache.TryGetValue(address, out abi!))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var state = _threadState.Value!;
|
||||
var inProgress = state.InProgress;
|
||||
var frameTaint = state.FrameTaint;
|
||||
|
||||
if (frameTaint is not null && state.TaintedAddresses!.ContainsKey(address))
|
||||
{
|
||||
@@ -151,6 +162,7 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
|
||||
// Prewarm phase A: track taint for this frame and publish only if it stayed clean.
|
||||
frameTaint.Add(false);
|
||||
state.FrameAddresses.Add(address);
|
||||
var frameIndex = frameTaint.Count - 1;
|
||||
bool succeeded;
|
||||
bool tainted;
|
||||
@@ -172,6 +184,7 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
finally
|
||||
{
|
||||
inProgress.Remove(address);
|
||||
state.FrameAddresses.RemoveAt(frameIndex);
|
||||
tainted = frameTaint[frameIndex];
|
||||
frameTaint.RemoveAt(frameIndex);
|
||||
if (tainted && frameIndex > 0)
|
||||
@@ -274,6 +287,7 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
var state = _threadState.Value!;
|
||||
var frames = state.FrameTaintBuffer;
|
||||
frames.Clear();
|
||||
state.FrameAddresses.Clear();
|
||||
state.TaintedAddresses = taintedAddresses;
|
||||
state.FrameTaint = frames;
|
||||
try
|
||||
@@ -418,82 +432,102 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
private HashSet<string> InferArgumentRegisters(IrFunction function, IrCfg cfg)
|
||||
{
|
||||
var comparer = StringComparer.OrdinalIgnoreCase;
|
||||
var blockUses = new Dictionary<string, HashSet<string>>(comparer);
|
||||
var blockDefs = new Dictionary<string, HashSet<string>>(comparer);
|
||||
|
||||
foreach (var block in function.Blocks)
|
||||
// Interned register ids so the liveness fixpoint runs over bitsets.
|
||||
var ids = new Dictionary<string, int>(comparer);
|
||||
var names = new List<string>();
|
||||
int Id(string name)
|
||||
{
|
||||
var uses = new HashSet<string>(comparer);
|
||||
var defs = new HashSet<string>(comparer);
|
||||
if (!ids.TryGetValue(name, out var id))
|
||||
{
|
||||
id = names.Count;
|
||||
ids.Add(name, id);
|
||||
names.Add(name);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
var blockCount = function.Blocks.Count;
|
||||
var useLists = new List<int>[blockCount];
|
||||
var defLists = new List<int>[blockCount];
|
||||
var labelIndex = new Dictionary<string, int>(blockCount, comparer);
|
||||
for (var b = 0; b < blockCount; b++)
|
||||
{
|
||||
var block = function.Blocks[b];
|
||||
var uses = new List<int>();
|
||||
var defs = new List<int>();
|
||||
var defined = new HashSet<int>();
|
||||
foreach (var instruction in block.Instructions)
|
||||
{
|
||||
foreach (var use in InstructionRegisterUsesForArgumentInference(instruction))
|
||||
{
|
||||
if (!defs.Contains(use))
|
||||
{
|
||||
uses.Add(use);
|
||||
}
|
||||
var id = Id(use);
|
||||
if (!defined.Contains(id))
|
||||
uses.Add(id);
|
||||
}
|
||||
|
||||
foreach (var def in InstructionRegisterDefsForArgumentInference(instruction))
|
||||
{
|
||||
defs.Add(def);
|
||||
var id = Id(def);
|
||||
if (defined.Add(id))
|
||||
defs.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
blockUses[block.Label] = uses;
|
||||
blockDefs[block.Label] = defs;
|
||||
useLists[b] = uses;
|
||||
defLists[b] = defs;
|
||||
}
|
||||
|
||||
var liveIn = function.Blocks.ToDictionary(
|
||||
static block => block.Label,
|
||||
_ => new HashSet<string>(comparer),
|
||||
comparer);
|
||||
var liveOut = function.Blocks.ToDictionary(
|
||||
static block => block.Label,
|
||||
_ => new HashSet<string>(comparer),
|
||||
comparer);
|
||||
for (var b = 0; b < blockCount; b++)
|
||||
labelIndex.Add(function.Blocks[b].Label, b);
|
||||
|
||||
var words = Math.Max(1, (names.Count + 63) >> 6);
|
||||
var useBits = new ulong[blockCount * words];
|
||||
var keepBits = new ulong[blockCount * words];
|
||||
Array.Fill(keepBits, ulong.MaxValue);
|
||||
var successors = new int[blockCount][];
|
||||
for (var b = 0; b < blockCount; b++)
|
||||
{
|
||||
foreach (var id in useLists[b]) useBits[b * words + (id >> 6)] |= 1UL << (id & 63);
|
||||
foreach (var id in defLists[b]) keepBits[b * words + (id >> 6)] &= ~(1UL << (id & 63));
|
||||
var targets = new List<int>();
|
||||
foreach (var successor in cfg.Successors(function.Blocks[b].Label))
|
||||
if (labelIndex.TryGetValue(successor, out var index))
|
||||
targets.Add(index);
|
||||
successors[b] = targets.ToArray();
|
||||
}
|
||||
|
||||
var liveIn = new ulong[blockCount * words];
|
||||
var changed = true;
|
||||
while (changed)
|
||||
{
|
||||
changed = false;
|
||||
for (var blockIndex = function.Blocks.Count - 1; blockIndex >= 0; blockIndex--)
|
||||
for (var b = blockCount - 1; b >= 0; b--)
|
||||
{
|
||||
var block = function.Blocks[blockIndex];
|
||||
var newOut = new HashSet<string>(comparer);
|
||||
foreach (var successor in cfg.Successors(block.Label))
|
||||
var row = b * words;
|
||||
for (var w = 0; w < words; w++)
|
||||
{
|
||||
if (liveIn.TryGetValue(successor, out var successorLiveIn))
|
||||
ulong liveOut = 0;
|
||||
foreach (var successor in successors[b])
|
||||
liveOut |= liveIn[successor * words + w];
|
||||
var value = useBits[row + w] | (liveOut & keepBits[row + w]);
|
||||
if (value != liveIn[row + w])
|
||||
{
|
||||
newOut.UnionWith(successorLiveIn);
|
||||
liveIn[row + w] = value;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
var newIn = new HashSet<string>(blockUses[block.Label], comparer);
|
||||
var outMinusDefs = new HashSet<string>(newOut, comparer);
|
||||
outMinusDefs.ExceptWith(blockDefs[block.Label]);
|
||||
newIn.UnionWith(outMinusDefs);
|
||||
|
||||
if (!newOut.SetEquals(liveOut[block.Label]))
|
||||
{
|
||||
liveOut[block.Label] = newOut;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!newIn.SetEquals(liveIn[block.Label]))
|
||||
{
|
||||
liveIn[block.Label] = newIn;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return liveIn.TryGetValue(function.EntryLabel, out var entryLive)
|
||||
? entryLive
|
||||
.Where(IsGprOrFprRegister)
|
||||
.ToHashSet(comparer)
|
||||
: new HashSet<string>(comparer);
|
||||
var result = new HashSet<string>(comparer);
|
||||
if (labelIndex.TryGetValue(function.EntryLabel, out var entry))
|
||||
{
|
||||
for (var id = 0; id < names.Count; id++)
|
||||
{
|
||||
if ((liveIn[entry * words + (id >> 6)] & (1UL << (id & 63))) != 0 && IsGprOrFprRegister(names[id]))
|
||||
result.Add(names[id]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private IEnumerable<string> InstructionRegisterUsesForArgumentInference(IrInstruction instruction)
|
||||
@@ -755,45 +789,74 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
private HashSet<string> InferScalarFloatArguments(IrFunction function, IrCfg cfg)
|
||||
{
|
||||
var scalarArgs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var comparer = StringComparer.OrdinalIgnoreCase;
|
||||
var inMap = function.Blocks.ToDictionary(
|
||||
static b => b.Label,
|
||||
b => b.Label.Equals(function.EntryLabel, StringComparison.OrdinalIgnoreCase)
|
||||
? CreateInitialArgumentAliasState()
|
||||
: new Dictionary<string, HashSet<string>>(comparer),
|
||||
comparer);
|
||||
var outMap = function.Blocks.ToDictionary(
|
||||
static b => b.Label,
|
||||
b => b.Label.Equals(function.EntryLabel, StringComparison.OrdinalIgnoreCase)
|
||||
? CreateInitialArgumentAliasState()
|
||||
: new Dictionary<string, HashSet<string>>(comparer),
|
||||
comparer);
|
||||
var keys = new AliasKeys(function);
|
||||
var blockCount = function.Blocks.Count;
|
||||
var blockIndex = new Dictionary<string, int>(blockCount, StringComparer.OrdinalIgnoreCase);
|
||||
for (var b = 0; b < blockCount; b++)
|
||||
blockIndex[function.Blocks[b].Label] = b;
|
||||
var predecessors = new int[blockCount][];
|
||||
var isEntry = new bool[blockCount];
|
||||
for (var b = 0; b < blockCount; b++)
|
||||
{
|
||||
var block = function.Blocks[b];
|
||||
isEntry[b] = block.Label.Equals(function.EntryLabel, StringComparison.OrdinalIgnoreCase);
|
||||
var list = new List<int>();
|
||||
foreach (var predecessor in cfg.Predecessors(block.Label))
|
||||
if (blockIndex.TryGetValue(predecessor, out var index))
|
||||
list.Add(index);
|
||||
predecessors[b] = list.ToArray();
|
||||
}
|
||||
|
||||
// Per alias key: -1 when absent, otherwise the mask of ABI float argument sources.
|
||||
var initial = keys.InitialState();
|
||||
var inMap = new int[blockCount][];
|
||||
var outMap = new int[blockCount][];
|
||||
for (var b = 0; b < blockCount; b++)
|
||||
{
|
||||
inMap[b] = isEntry[b] ? (int[])initial.Clone() : keys.EmptyState();
|
||||
outMap[b] = isEntry[b] ? (int[])initial.Clone() : keys.EmptyState();
|
||||
}
|
||||
|
||||
var changed = true;
|
||||
while (changed)
|
||||
{
|
||||
changed = false;
|
||||
foreach (var block in function.Blocks)
|
||||
for (var b = 0; b < blockCount; b++)
|
||||
{
|
||||
var newIn = block.Label.Equals(function.EntryLabel, StringComparison.OrdinalIgnoreCase)
|
||||
? CreateInitialArgumentAliasState()
|
||||
: UnionPredecessorStates(cfg.Predecessors(block.Label), outMap);
|
||||
|
||||
var newOut = CopyAliasState(newIn);
|
||||
foreach (var ins in block.Instructions)
|
||||
int[] newIn;
|
||||
if (isEntry[b])
|
||||
{
|
||||
ProcessScalarArgumentInstruction(ins, newOut, scalarArgs);
|
||||
newIn = (int[])initial.Clone();
|
||||
}
|
||||
else
|
||||
{
|
||||
newIn = keys.EmptyState();
|
||||
foreach (var predecessor in predecessors[b])
|
||||
{
|
||||
var predState = outMap[predecessor];
|
||||
for (var key = 0; key < newIn.Length; key++)
|
||||
{
|
||||
if (predState[key] < 0) continue;
|
||||
newIn[key] = newIn[key] < 0 ? predState[key] : newIn[key] | predState[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!AliasStatesEqual(newIn, inMap[block.Label]))
|
||||
var newOut = (int[])newIn.Clone();
|
||||
foreach (var ins in function.Blocks[b].Instructions)
|
||||
{
|
||||
inMap[block.Label] = newIn;
|
||||
ProcessScalarArgumentInstruction(ins, keys, newOut, scalarArgs);
|
||||
}
|
||||
|
||||
if (!newIn.AsSpan().SequenceEqual(inMap[b]))
|
||||
{
|
||||
inMap[b] = newIn;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!AliasStatesEqual(newOut, outMap[block.Label]))
|
||||
if (!newOut.AsSpan().SequenceEqual(outMap[b]))
|
||||
{
|
||||
outMap[block.Label] = newOut;
|
||||
outMap[b] = newOut;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -802,63 +865,153 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
return scalarArgs;
|
||||
}
|
||||
|
||||
/// <summary>Interned float register names the scalar-argument alias analysis can track.
|
||||
/// Ids 0..12 are the ABI float argument registers, so a source set is a 13-bit mask.</summary>
|
||||
private sealed class AliasKeys
|
||||
{
|
||||
private readonly Dictionary<string, int> _ids = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public AliasKeys(IrFunction function)
|
||||
{
|
||||
foreach (var register in AbiFloatArgumentRegisters)
|
||||
_ids.Add(register, _ids.Count);
|
||||
foreach (var block in function.Blocks)
|
||||
{
|
||||
foreach (var instruction in block.Instructions)
|
||||
{
|
||||
switch (instruction)
|
||||
{
|
||||
case IrAssign assign:
|
||||
AddName(assign.Destination);
|
||||
AddValue(assign.Value);
|
||||
break;
|
||||
case IrBinary binary:
|
||||
AddValue(binary.Left);
|
||||
AddValue(binary.Right);
|
||||
AddName(binary.Destination);
|
||||
break;
|
||||
case IrLoad load:
|
||||
AddName(load.Destination);
|
||||
break;
|
||||
case IrStore store:
|
||||
AddValue(store.Source);
|
||||
break;
|
||||
case IrCall call:
|
||||
foreach (var argument in call.Arguments) AddValue(argument);
|
||||
AddName(call.Destination);
|
||||
break;
|
||||
case IrIndirectCall indirectCall:
|
||||
foreach (var argument in indirectCall.Arguments) AddValue(argument);
|
||||
AddName(indirectCall.Destination);
|
||||
break;
|
||||
case IrSetCrField setCr:
|
||||
AddValue(setCr.Left);
|
||||
AddValue(setCr.Right);
|
||||
break;
|
||||
case IrPhi phi:
|
||||
AddName(phi.Destination);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _ids.Count;
|
||||
|
||||
public int[] EmptyState()
|
||||
{
|
||||
var state = new int[_ids.Count];
|
||||
Array.Fill(state, -1);
|
||||
return state;
|
||||
}
|
||||
|
||||
public int[] InitialState()
|
||||
{
|
||||
var state = EmptyState();
|
||||
for (var i = 0; i < AbiFloatArgumentRegisters.Length; i++)
|
||||
state[i] = 1 << i;
|
||||
return state;
|
||||
}
|
||||
|
||||
public bool TryGet(string baseName, out int id) => _ids.TryGetValue(baseName, out id);
|
||||
|
||||
private void AddValue(IrValue? value)
|
||||
{
|
||||
if (value is { Kind: "register", RegisterName: { } name })
|
||||
AddName(name);
|
||||
}
|
||||
|
||||
private void AddName(string? name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return;
|
||||
var baseName = Base(name);
|
||||
if (IsFloatRegister(baseName))
|
||||
_ids.TryAdd(baseName, _ids.Count);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessScalarArgumentInstruction(
|
||||
IrInstruction ins,
|
||||
Dictionary<string, HashSet<string>> aliases,
|
||||
AliasKeys keys,
|
||||
int[] aliases,
|
||||
HashSet<string> scalarArgs)
|
||||
{
|
||||
switch (ins)
|
||||
{
|
||||
case IrAssign assign:
|
||||
PropagateFloatAlias(assign.Destination, assign.Value, aliases);
|
||||
PropagateFloatAlias(assign.Destination, assign.Value, keys, aliases);
|
||||
break;
|
||||
|
||||
case IrBinary bin:
|
||||
MarkScalarUse(bin.Left, aliases, scalarArgs);
|
||||
MarkScalarUse(bin.Right, aliases, scalarArgs);
|
||||
KillFloatAlias(bin.Destination, aliases);
|
||||
MarkScalarUse(bin.Left, keys, aliases, scalarArgs);
|
||||
MarkScalarUse(bin.Right, keys, aliases, scalarArgs);
|
||||
KillFloatAlias(bin.Destination, keys, aliases);
|
||||
break;
|
||||
|
||||
case IrLoad load:
|
||||
KillFloatAlias(load.Destination, aliases);
|
||||
KillFloatAlias(load.Destination, keys, aliases);
|
||||
break;
|
||||
|
||||
case IrStore store:
|
||||
if (store.SizeBytes is 4 or 8)
|
||||
{
|
||||
MarkScalarUse(store.Source, aliases, scalarArgs);
|
||||
MarkScalarUse(store.Source, keys, aliases, scalarArgs);
|
||||
}
|
||||
break;
|
||||
|
||||
case IrCall call:
|
||||
ProcessScalarArgumentCall(call.Target, call.Arguments, aliases, scalarArgs);
|
||||
KillFloatAlias(call.Destination, aliases);
|
||||
ProcessScalarArgumentCall(call.Target, call.Arguments, keys, aliases, scalarArgs);
|
||||
KillFloatAlias(call.Destination, keys, aliases);
|
||||
break;
|
||||
|
||||
case IrIndirectCall icall:
|
||||
foreach (var arg in icall.Arguments)
|
||||
{
|
||||
MarkScalarUse(arg, aliases, scalarArgs);
|
||||
MarkScalarUse(arg, keys, aliases, scalarArgs);
|
||||
}
|
||||
aliases.Remove("f1");
|
||||
KillFloatAlias(icall.Destination, aliases);
|
||||
aliases[F1AliasKey] = -1;
|
||||
KillFloatAlias(icall.Destination, keys, aliases);
|
||||
break;
|
||||
|
||||
case IrSetCrField setCr:
|
||||
MarkScalarUse(setCr.Left, aliases, scalarArgs);
|
||||
MarkScalarUse(setCr.Right, aliases, scalarArgs);
|
||||
MarkScalarUse(setCr.Left, keys, aliases, scalarArgs);
|
||||
MarkScalarUse(setCr.Right, keys, aliases, scalarArgs);
|
||||
break;
|
||||
|
||||
case IrPhi phi:
|
||||
KillFloatAlias(phi.Destination, aliases);
|
||||
KillFloatAlias(phi.Destination, keys, aliases);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Alias key of f1, the first ABI float argument register.</summary>
|
||||
private const int F1AliasKey = 0;
|
||||
|
||||
private void ProcessScalarArgumentCall(
|
||||
string target,
|
||||
IReadOnlyList<IrValue> arguments,
|
||||
Dictionary<string, HashSet<string>> aliases,
|
||||
AliasKeys keys,
|
||||
int[] aliases,
|
||||
HashSet<string> scalarArgs)
|
||||
{
|
||||
if (IsGuestCallTarget(target))
|
||||
@@ -873,14 +1026,14 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
arg.RegisterName != null &&
|
||||
abi.HasScalarFloatArgument(Base(arg.RegisterName)))
|
||||
{
|
||||
MarkScalarUse(arg, aliases, scalarArgs);
|
||||
MarkScalarUse(arg, keys, aliases, scalarArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writesFloatReturnRegister)
|
||||
{
|
||||
aliases.Remove("f1");
|
||||
aliases[F1AliasKey] = -1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -892,7 +1045,7 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
|
||||
foreach (var arg in arguments)
|
||||
{
|
||||
MarkScalarUse(arg, aliases, scalarArgs);
|
||||
MarkScalarUse(arg, keys, aliases, scalarArgs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1083,7 +1236,8 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
|
||||
private static void MarkScalarUse(
|
||||
IrValue value,
|
||||
IReadOnlyDictionary<string, HashSet<string>> aliases,
|
||||
AliasKeys keys,
|
||||
int[] aliases,
|
||||
HashSet<string> scalarArgs)
|
||||
{
|
||||
if (value.Kind != "register" || value.RegisterName == null)
|
||||
@@ -1092,24 +1246,22 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
}
|
||||
|
||||
var baseName = Base(value.RegisterName);
|
||||
if (!IsFloatRegister(baseName) || !aliases.TryGetValue(baseName, out var sources))
|
||||
if (!IsFloatRegister(baseName) || !keys.TryGet(baseName, out var key) || aliases[key] < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var source in sources)
|
||||
for (var sources = aliases[key]; sources != 0; sources &= sources - 1)
|
||||
{
|
||||
if (AbiFloatArgumentSet.Contains(source))
|
||||
{
|
||||
scalarArgs.Add(source);
|
||||
}
|
||||
scalarArgs.Add(AbiFloatArgumentRegisters[BitOperations.TrailingZeroCount(sources)]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PropagateFloatAlias(
|
||||
string destination,
|
||||
IrValue value,
|
||||
Dictionary<string, HashSet<string>> aliases)
|
||||
AliasKeys keys,
|
||||
int[] aliases)
|
||||
{
|
||||
var destBase = Base(destination);
|
||||
if (!IsFloatRegister(destBase))
|
||||
@@ -1117,24 +1269,27 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
return;
|
||||
}
|
||||
|
||||
keys.TryGet(destBase, out var destKey);
|
||||
if (value.Kind == "register" &&
|
||||
value.RegisterName != null &&
|
||||
IsFloatRegister(Base(value.RegisterName)) &&
|
||||
aliases.TryGetValue(Base(value.RegisterName), out var srcAliases))
|
||||
keys.TryGet(Base(value.RegisterName), out var sourceKey) &&
|
||||
aliases[sourceKey] >= 0)
|
||||
{
|
||||
aliases[destBase] = new HashSet<string>(srcAliases, StringComparer.OrdinalIgnoreCase);
|
||||
aliases[destKey] = aliases[sourceKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
aliases.Remove(destBase);
|
||||
aliases[destKey] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void KillFloatAlias(string destination, Dictionary<string, HashSet<string>> aliases)
|
||||
private static void KillFloatAlias(string destination, AliasKeys keys, int[] aliases)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(destination) && IsFloatRegister(Base(destination)))
|
||||
if (!string.IsNullOrWhiteSpace(destination) && IsFloatRegister(Base(destination)) &&
|
||||
keys.TryGet(Base(destination), out var key))
|
||||
{
|
||||
aliases.Remove(Base(destination));
|
||||
aliases[key] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1146,70 +1301,6 @@ public sealed class InferredGuestFunctionAbiProvider : IGuestFunctionAbiProvider
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<string, HashSet<string>> CreateInitialArgumentAliasState()
|
||||
{
|
||||
return AbiFloatArgumentRegisters.ToDictionary(
|
||||
static r => r,
|
||||
static r => new HashSet<string>(StringComparer.OrdinalIgnoreCase) { r },
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static Dictionary<string, HashSet<string>> UnionPredecessorStates(
|
||||
IReadOnlyList<string> predecessors,
|
||||
IReadOnlyDictionary<string, Dictionary<string, HashSet<string>>> outMap)
|
||||
{
|
||||
var result = new Dictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var pred in predecessors)
|
||||
{
|
||||
if (!outMap.TryGetValue(pred, out var predState))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var (register, sources) in predState)
|
||||
{
|
||||
if (!result.TryGetValue(register, out var destSources))
|
||||
{
|
||||
destSources = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
result[register] = destSources;
|
||||
}
|
||||
|
||||
destSources.UnionWith(sources);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Dictionary<string, HashSet<string>> CopyAliasState(
|
||||
IReadOnlyDictionary<string, HashSet<string>> state)
|
||||
{
|
||||
return state.ToDictionary(
|
||||
static kv => kv.Key,
|
||||
static kv => new HashSet<string>(kv.Value, StringComparer.OrdinalIgnoreCase),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool AliasStatesEqual(
|
||||
IReadOnlyDictionary<string, HashSet<string>> left,
|
||||
IReadOnlyDictionary<string, HashSet<string>> right)
|
||||
{
|
||||
if (left.Count != right.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var (key, leftSet) in left)
|
||||
{
|
||||
if (!right.TryGetValue(key, out var rightSet) || !leftSet.SetEquals(rightSet))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsGuestCallTarget(string target)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(target))
|
||||
|
||||
@@ -30,6 +30,14 @@ public sealed class CanonicalIrStore : ICanonicalIrProvider
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(uint entryPoint)
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
_entries.Remove(entryPoint);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGet(uint entryPoint, out IrFunction function)
|
||||
{
|
||||
byte[]? encoded;
|
||||
@@ -55,12 +63,16 @@ internal static class CanonicalIrBinaryCodec
|
||||
IndirectJump, JumpTable, Return, Comment, TracePpc, Undefined
|
||||
}
|
||||
|
||||
// Reused per thread so encoding doesn't regrow (and re-fault) a fresh buffer per function.
|
||||
[ThreadStatic] private static MemoryStream? _encodeStream;
|
||||
|
||||
public static byte[] Encode(IrFunction function)
|
||||
{
|
||||
var strings = BuildStringTable(function);
|
||||
var ids = strings.Select((value, index) => (value, index))
|
||||
.ToDictionary(static pair => pair.value, static pair => pair.index, StringComparer.Ordinal);
|
||||
using var stream = new MemoryStream();
|
||||
var stream = _encodeStream ??= new MemoryStream();
|
||||
stream.SetLength(0);
|
||||
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||
writer.Write(Magic);
|
||||
WriteVarUInt(writer, (uint)CanonicalIrStore.FormatVersion);
|
||||
@@ -85,9 +97,11 @@ internal static class CanonicalIrBinaryCodec
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public static IrFunction Decode(ReadOnlySpan<byte> encoded)
|
||||
public static IrFunction Decode(ReadOnlySpan<byte> encoded) => Decode(encoded.ToArray());
|
||||
|
||||
public static IrFunction Decode(byte[] encoded)
|
||||
{
|
||||
using var stream = new MemoryStream(encoded.ToArray(), writable: false);
|
||||
using var stream = new MemoryStream(encoded, writable: false);
|
||||
using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
|
||||
if (!reader.ReadBytes(Magic.Length).SequenceEqual(Magic) ||
|
||||
ReadVarUInt(reader) != CanonicalIrStore.FormatVersion)
|
||||
|
||||
@@ -497,12 +497,52 @@ public static class GqrInterproceduralAnalysis
|
||||
return masks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unknown-entry roots that depend only on the final call graph, never on discovery order:
|
||||
/// <paramref name="fixedRoots"/>, every candidate without a direct caller, then (ascending) any
|
||||
/// candidate the roots still don't reach, so no candidate is left without an entry fact source.
|
||||
/// </summary>
|
||||
public static HashSet<uint> SelectUnknownEntryRoots(
|
||||
IReadOnlyDictionary<uint, GqrFunctionSummary> functions,
|
||||
IEnumerable<uint> fixedRoots,
|
||||
IEnumerable<uint> candidates)
|
||||
{
|
||||
var called = new HashSet<uint>();
|
||||
foreach (var summary in functions.Values)
|
||||
foreach (var target in summary.DirectGuestTargets)
|
||||
called.Add(target);
|
||||
var ordered = candidates.Where(functions.ContainsKey).Distinct().OrderBy(static a => a).ToArray();
|
||||
var roots = fixedRoots.Where(functions.ContainsKey).ToHashSet();
|
||||
foreach (var address in ordered)
|
||||
if (!called.Contains(address))
|
||||
roots.Add(address);
|
||||
var reachable = ComputeCompactReachable(functions, roots);
|
||||
foreach (var address in ordered)
|
||||
{
|
||||
if (reachable.Contains(address)) continue;
|
||||
roots.Add(address);
|
||||
ExtendCompactReachable(functions, reachable, address);
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
private static HashSet<uint> ComputeCompactReachable(
|
||||
IReadOnlyDictionary<uint, GqrFunctionSummary> functions,
|
||||
IReadOnlySet<uint> roots)
|
||||
{
|
||||
var reachable = new HashSet<uint>();
|
||||
var queue = new Queue<uint>(roots);
|
||||
foreach (var root in roots)
|
||||
ExtendCompactReachable(functions, reachable, root);
|
||||
return reachable;
|
||||
}
|
||||
|
||||
private static void ExtendCompactReachable(
|
||||
IReadOnlyDictionary<uint, GqrFunctionSummary> functions,
|
||||
HashSet<uint> reachable,
|
||||
uint root)
|
||||
{
|
||||
var queue = new Queue<uint>();
|
||||
queue.Enqueue(root);
|
||||
while (queue.TryDequeue(out var address))
|
||||
{
|
||||
if (!reachable.Add(address)) continue;
|
||||
@@ -510,7 +550,6 @@ public static class GqrInterproceduralAnalysis
|
||||
if (functions.ContainsKey(target) && !reachable.Contains(target))
|
||||
queue.Enqueue(target);
|
||||
}
|
||||
return reachable;
|
||||
}
|
||||
|
||||
private static void TransferCompact(
|
||||
|
||||
@@ -167,6 +167,31 @@ public sealed class GqrConstantPropagationTests
|
||||
instruction => instruction is IrCall { Target: "PPC_PsqLKnown_00070007" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnknownEntryRootsDependOnlyOnTheCallGraph()
|
||||
{
|
||||
IrFunction Calls(params uint[] targets) => Function(targets
|
||||
.Select(target => (IrInstruction)new IrCall(string.Empty, $"0x{target:X8}", Array.Empty<IrValue>()))
|
||||
.Append(new IrReturn(null))
|
||||
.ToArray());
|
||||
var functions = new Dictionary<uint, IrFunction>
|
||||
{
|
||||
[0x80001000] = Calls(),
|
||||
[0x80002000] = Calls(0x80003000),
|
||||
[0x80003000] = Calls(),
|
||||
[0x80004000] = Calls(0x80005000),
|
||||
[0x80005000] = Calls(0x80004000)
|
||||
};
|
||||
var summaries = functions.ToDictionary(pair => pair.Key, pair => GqrFunctionSummary.Create(pair.Value));
|
||||
uint[] candidates = [0x80002000, 0x80003000, 0x80004000, 0x80005000];
|
||||
|
||||
var roots = GqrInterproceduralAnalysis.SelectUnknownEntryRoots(summaries, [0x80001000u], candidates);
|
||||
var reversed = GqrInterproceduralAnalysis.SelectUnknownEntryRoots(summaries, [0x80001000u], candidates.Reverse());
|
||||
|
||||
Assert.Equal(new uint[] { 0x80001000, 0x80002000, 0x80004000 }, roots.Order());
|
||||
Assert.Equal(roots.Order(), reversed.Order());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InterproceduralAnalysisIntersectsConflictingCallers()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user