Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion translator/src/Translator.Core/Analysis/StackAddressFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ private StackAddressFacts(Dictionary<string, int> temporaryOffsets)
public static StackAddressFacts Empty { get; } =
new(new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase));

public static StackAddressFacts Build(IrFunction function)
/// <param name="trustRegisterCopies">
/// False for a body that can be resumed at any instruction: a copy into a
/// guest GPR (mr r4,r1) belongs to an earlier instruction a resume can skip.
/// Lowering temporaries live inside one instruction and r1 stays the stack
/// pointer, so those still resolve.
/// </param>
public static StackAddressFacts Build(IrFunction function, bool trustRegisterCopies = true)
{
var temporaryOffsets = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
foreach (var block in function.Blocks)
Expand All @@ -33,11 +39,13 @@ public static StackAddressFacts Build(IrFunction function)
// SSA form makes an architectural register destination as stable as a compiler
// temporary (e.g. PPC prologues copying r1 to r11 for the save area).
case IrAssign assign when
(trustRegisterCopies || !IsGuestGpr(assign.Destination)) &&
TryResolveValue(assign.Value, temporaryOffsets, out var assignOffset):
temporaryOffsets[assign.Destination] = assignOffset;
break;

case IrBinary binary when
(trustRegisterCopies || !IsGuestGpr(binary.Destination)) &&
TryResolveBinary(binary, temporaryOffsets, out var binaryOffset):
temporaryOffsets[binary.Destination] = binaryOffset;
break;
Expand Down Expand Up @@ -169,6 +177,15 @@ private static bool TryApplyOffset(string op, int baseOffset, int constant, out
}
}

private static bool IsGuestGpr(string name)
{
var baseName = GetRegisterBaseName(name);
return baseName.Length > 1 &&
(baseName[0] == 'r' || baseName[0] == 'R') &&
int.TryParse(baseName.AsSpan(1), out var index) &&
index is >= 0 and < 32;
}

private static string GetRegisterBaseName(string name)
{
var underscore = name.IndexOf('_');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ private static void EmitInstruction(string currentLabel, int directCallOrdinal,
IReadOnlyDictionary<uint, string> stateFreeCallSymbols,
IReadOnlyDictionary<GuestStateFreeCallSiteKey, GuestStateFreeCallVariant> stateFreeCallSiteVariants,
IReadOnlySet<uint> modOverridableCallTargets,
bool shareLrContinuationDispatch)
bool shareLrContinuationDispatch,
IReadOnlyDictionary<string, HashSet<string>>? resumeLabelPairedStates)
{
if (ins is IrPhi)
{
return; // Phi nodes are eliminated before codegen.
}

// Only a body that a callee can resume mid-block has resume targets.
var resumableBody = resumeLabelPairedStates is not null;

var pad = IndentPad(indent);
switch (ins)
{
Expand Down Expand Up @@ -125,14 +129,18 @@ private static void EmitInstruction(string currentLabel, int directCallOrdinal,
srcExpr = $"PPC_PsToScalarInline({srcExpr})";
}

if (TryEmitKnownGpuFifoStore(store, srcExpr, isFloatStore, sb, pad, knownConstants))
// Both forms below trust the constant a static def produced: the
// FIFO form drops the address and the RAM form skips the MMIO test.
// A resumed body may not have run that def. The checked MMIO load
// above still reads the runtime address, so it stays.
if (!resumableBody && TryEmitKnownGpuFifoStore(store, srcExpr, isFloatStore, sb, pad, knownConstants))
{
break;
}

// A translate-time-constant address outside the MMIO window (gather-pipe already handled
// above) always takes the flat helper's plain-store arm, so the runtime MMIO test is provably dead.
var provenRamStore =
var provenRamStore = !resumableBody &&
TryGetKnownEffectiveAddress(store.Address, knownConstants, out var knownStoreAddress) &&
IsProvenRamStore(knownStoreAddress, store.SizeBytes);
string StoreHelper(int sizeBytes, bool isFloat) => provenRamStore
Expand Down Expand Up @@ -320,6 +328,18 @@ string Raw(string value, bool isFloat) => isFloat
{
if (TryEmitInlineGuestThunk(addr, sb, pad, types, inlineGuestThunkStackBase))
{
// A restore replaces those FPRs with scalar stack values.
// A resumable body relies on its tags matching the flow,
// which models the same reset.
if (resumableBody &&
TryGetInlineGuestThunkSpec(addr, out var thunkSpec) &&
thunkSpec.Kind == InlineGuestThunkKind.RestFpr)
{
for (var reg = thunkSpec.StartRegister; reg <= 31; reg++)
{
localPaired[$"f{reg}"] = false;
}
}
break;
}

Expand All @@ -338,7 +358,10 @@ string Raw(string value, bool isFloat) => isFloat
var stateFreeContract = hasCallSiteVariant
? callSiteVariant!.Contract
: stateFreeAbiContracts.GetValueOrDefault(addr);
var usesStateFreeCall = stateFreeSymbol is not null && stateFreeContract is not null &&
// State-free signatures and call-site variants are narrowed by
// CFG liveness, which does not see a resume reading an output.
var usesStateFreeCall = !resumableBody &&
stateFreeSymbol is not null && stateFreeContract is not null &&
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
IsStateFreeCallSafe(stateFreeContract);
var resumesThroughLinkRegister =
nonReturningCallTargets.Contains(addr) ||
Expand All @@ -364,7 +387,9 @@ _activeResidency is not null &&
EmitNormalizeGuestCallFloatReturn(call.Target, sb, pad, localPaired, guestAbiProvider);
break;
}
var boundarySync = usesStateFreeCall || argumentNormalization.Length != 0
// A resume through LR can run code outside this callee's
// contract, including another function reading ctx.
var boundarySync = usesStateFreeCall || argumentNormalization.Length != 0 || resumesThroughLinkRegister
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
? ResidencyBoundarySync.Full
: ResolveDirectCallBoundarySync(addr, guestAbiContracts, modOverridableCallTargets);
if (!boundarySync.IsFull &&
Expand Down Expand Up @@ -409,7 +434,9 @@ _activeResidency is not null &&
// The callee may resume this body through a local LR
// continuation label, so the locals must be refreshed
// from the context it just wrote. The exit paths below
// return without writing locals back.
// return without writing locals back. Both paths see the
// same f1 return normalization an ordinary call applies.
EmitNormalizeGuestCallFloatReturn(call.Target, sb, pad, localPaired, guestAbiProvider);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
AppendReload(sb, pad, boundarySync);
var fallbackPad = pad;
if (localFallthroughLr.HasValue)
Expand All @@ -420,11 +447,15 @@ _activeResidency is not null &&

if (shareLrContinuationDispatch)
{
// Shared only when every target expects scalar FPRs.
EmitConvertPairedState(
sb, fallbackPad, localPaired,
new HashSet<string>(StringComparer.OrdinalIgnoreCase));
sb.AppendLine($"{fallbackPad}goto lr_continuation_dispatch;");
}
else
{
EmitLocalLrContinuationDispatch(sb, fallbackPad, labelNames);
EmitLocalLrContinuationDispatch(sb, fallbackPad, labelNames, localPaired, resumeLabelPairedStates);
sb.AppendLine($"{fallbackPad}if (TranslatedFunctionRegistry::FindByAddressPtr(ctx->lr) != nullptr) {{");
sb.AppendLine($"{fallbackPad} InvokeIndirectCpu(ctx->lr, ctx);");
sb.AppendLine($"{fallbackPad}}}");
Expand Down Expand Up @@ -693,7 +724,9 @@ private static bool TryEmitKnownGpuFifoStore(
private static void EmitLocalLrContinuationDispatch(
StringBuilder sb,
string pad,
IReadOnlyDictionary<string, string> labelNames)
IReadOnlyDictionary<string, string> labelNames,
IReadOnlyDictionary<string, bool>? callSitePaired = null,
IReadOnlyDictionary<string, HashSet<string>>? labelPairedStates = null)
{
var localContinuations = labelNames
.Where(static pair => TryParseAddress(pair.Key, out _))
Expand All @@ -715,6 +748,11 @@ private static void EmitLocalLrContinuationDispatch(
{
RecordEmittedLocalLabel(label);
sb.AppendLine($"{pad}case 0x{address:X8}u:");
// Each case is an edge from the call site into that label.
if (callSitePaired is not null && labelPairedStates is not null)
{
EmitConvertPairedState(sb, pad + " ", callSitePaired, labelPairedStates[label]);
}
sb.AppendLine($"{pad} goto {label};");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,13 @@ private static string RemapAddress(
if (effective >= remap.LinkBase && effective < linkEnd)
{
var relocated = checked(remap.GuestBase + (effective - remap.LinkBase));
return $"0x{relocated:X8}u";
if (!remap.GuardAgainstResume)
{
return $"0x{relocated:X8}u";
}

var runtime = Address(addr, types);
return $"(static_cast<uint32_t>({runtime}) == 0x{effective:X8}u ? 0x{relocated:X8}u : static_cast<uint32_t>({runtime}))";
}
}

Expand Down
Loading
Loading