Add macOS ARM64 deferred stack tracing support for crossplay#827
Add macOS ARM64 deferred stack tracing support for crossplay#827
Conversation
Add ARM64 frame-pointer-based stack walking for desync tracing, enabling Mac ARM64 users to play with x64 Windows/Linux players. ARM64 implementation: - GetFp() for frame pointer detection (equivalent to x64 GetRbp) - TraceImplArm64() using FP chain walking (simpler than x64 since ARM64 ABI always uses frame pointers) - InitArm64Offset() with ARM64 instruction pattern detection (STR/STUR with X29 base register) - LmfPtr = -1 signals ARM64 FP-only mode, all ARM64 code paths gated behind NativeArch.ARM64 checks (inert on x64) Cross-platform desync detection fix: - The trace comparison hash previously included architecture-dependent values (hashIn, depth) which differ between ARM64 FP-chain walking and x64 RBP+LMF walking, causing false desync detection - Comparison hash now uses only rng state (architecture-independent) - Full hash including trace info is still stored for diagnostic display Tested with ~2 weeks of ARM64 Mac <-> x64 Windows crossplay.
e1dce17 to
47cc7c2
Compare
mibac138
left a comment
There was a problem hiding this comment.
I'm happy to see that you are working on it, having native support is quite important. While the general direction looks good, I have a few questions about some details
| return; | ||
|
|
||
| // ARM64 macOS doesn't use LMF - signal FP-only mode to DeferredStackTracingImpl | ||
| if (CurrentArch == NativeArch.ARM64 && os == NativeOS.OSX) |
There was a problem hiding this comment.
Is there any particular limitation of the current implementation that makes it only support MacOS? Or is it just that it's untested on other OSes?
| // implementations, causing false desync detection in cross-platform play. | ||
| // thingId and tick recover some non-RNG divergence sensitivity; rngState covers | ||
| // the common case. | ||
| var comparisonHash = Gen.HashCombineInt(item.thingId, item.tick, (int)(item.rngState >> 32), (int)item.rngState); |
There was a problem hiding this comment.
I'm not convinced that this is sufficient. Admittedly, I'm not sure how much desyncs are caused by trace mismatches, but I'm wary this change can delay or mask desyncs and make it harder to diagnose issues. Why would the traces differ? Is it about not following native frames or is there anything else?
| int depth = 0; | ||
| int index = 0; | ||
|
|
||
| while (fp != 0 && depth < traceIn.Length + skipFrames + 100) |
There was a problem hiding this comment.
What's the reasoning behind this condition depth < traceIn.Length + skipFrames + 100?
| ref var info = ref hashTable.GetOrCreateAddrInfo(ret); | ||
| if (info.addr == 0) UpdateNewElementArm64(ref info, ret); | ||
|
|
||
| // Stop at unmanaged frames - no LMF chain walking on ARM64 |
There was a problem hiding this comment.
Why is there no LMF chain walking? Does Unity not support it?
Summary
Approach
On macOS ARM64, stack walking follows the X29 frame-pointer chain rather than the x64 RBP+LMF path. The main cross-platform issue was the trace comparison hash: trace-derived values such as stack depth and method hashes vary between architectures, so the comparison hash now uses
thingId + tick + rngStateinstead, which are architecture-independent.Limitations
Testing
Tested over ~2 weeks of regular gameplay on Apple Silicon macOS (M4 Pro, macOS 26.3.1) in crossplay with a Windows x64 client. This eliminated the cross-architecture trace-hash false positives we were hitting and sessions were stable throughout.