Skip to content
This repository has been archived by the owner on Nov 1, 2020. It is now read-only.

Commit

Permalink
ARM64&Unix: New unwinder (#8345)
Browse files Browse the repository at this point in the history
Implements a custom unwinder for ARM64 that does not need the generic CFI based libunwind functions. This way we only need to loop just once over all used registers instead of doing the complicated CFI process.This will improve the performances for everything that needs a stack walk.

Additional unwind data are generated and stored as part of the LSDA. 
-4 byte for a relative offset
-3 byte for the CFA
-3 byte for each register that is part of the prolog save
-1 byte end marker

We still keep the CFI data for the debugger. Maybe the could be removed for a build without debug information.

A possible future optimization would be to introduce a compact format for the information used by the most common cases. This could be stored in the relative offset. But compared to the replacement of libunwind the wins would be rather small.

The same can be done for any  other supported architecture but each one will require a custom unwind function
  • Loading branch information
RalfKornmannEnvision authored Nov 1, 2020
1 parent cb82940 commit ba4c439
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 309 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ namespace ILCompiler.DependencyAnalysis
[Flags]
public enum FrameInfoFlags
{
Handler = 0x01,
Filter = 0x02,
Handler = 0x01,
Filter = 0x02,

HasEHInfo = 0x04,
ReversePInvoke = 0x08,
HasAssociatedData = 0x10,
HasEHInfo = 0x04,
ReversePInvoke = 0x08,
HasAssociatedData = 0x10,
HasFullUnwindInfo = 0x20,
HasCompactUnwindInfo = 0x40
}

public struct FrameInfo
Expand All @@ -22,13 +24,15 @@ public struct FrameInfo
public readonly int StartOffset;
public readonly int EndOffset;
public readonly byte[] BlobData;
public readonly byte[] UnwindData;

public FrameInfo(FrameInfoFlags flags, int startOffset, int endOffset, byte[] blobData)
public FrameInfo(FrameInfoFlags flags, int startOffset, int endOffset, byte[] blobData, byte[] unwindData)
{
Flags = flags;
StartOffset = startOffset;
EndOffset = endOffset;
BlobData = blobData;
UnwindData = unwindData;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ public void BuildCFIMap(NodeFactory factory, ObjectNode node)
int end = frameInfo.EndOffset;
int len = frameInfo.BlobData.Length;
byte[] blob = frameInfo.BlobData;
byte[] unwindData = frameInfo.UnwindData;

ObjectNodeSection lsdaSection = LsdaSection;
if (ShouldShareSymbol(node))
Expand All @@ -626,6 +627,18 @@ public void BuildCFIMap(NodeFactory factory, ObjectNode node)
flags |= ehInfo != null ? FrameInfoFlags.HasEHInfo : 0;
flags |= associatedDataNode != null ? FrameInfoFlags.HasAssociatedData : 0;

if (unwindData != null)
{
if (unwindData.Length == 2)
{
flags |= FrameInfoFlags.HasCompactUnwindInfo;
}
else
{
flags |= FrameInfoFlags.HasFullUnwindInfo;
}
}

EmitIntValue((byte)flags, 1);

if (i != 0)
Expand All @@ -649,6 +662,18 @@ public void BuildCFIMap(NodeFactory factory, ObjectNode node)
EmitSymbolRef(_sb.Clear().Append("_ehInfo").Append(_currentNodeZeroTerminatedName), RelocType.IMAGE_REL_BASED_RELPTR32);
}

if (unwindData != null)
{
if (unwindData.Length == 2)
{
EmitBlob(unwindData);
}
else
{
EmitSymbolRef(_sb.Clear().Append("_uwInfo").Append(i.ToStringInvariant()).Append(_currentNodeZeroTerminatedName), RelocType.IMAGE_REL_BASED_RELPTR32);
}
}

if (gcInfo != null)
{
EmitBlob(gcInfo);
Expand All @@ -660,14 +685,22 @@ public void BuildCFIMap(NodeFactory factory, ObjectNode node)
// TODO: Place EHInfo into different section for better locality
Debug.Assert(ehInfo.Alignment == 1);
Debug.Assert(ehInfo.DefinedSymbols.Length == 0);
EmitSymbolDef(_sb /* ehInfo */);
EmitSymbolDef(_sb.Clear().Append("_ehInfo").Append(_currentNodeZeroTerminatedName));
EmitBlobWithRelocs(ehInfo.Data, ehInfo.Relocs);
ehInfo = null;
}

if (unwindData != null && unwindData.Length > 2)
{
// TODO: Place unwind into different section for better locality?
EmitSymbolDef(_sb.Clear().Append("_uwInfo").Append(i.ToStringInvariant()).Append(_currentNodeZeroTerminatedName));
EmitBlob(unwindData);
}

// Store the CFI data for the debugger even if we have or own unwinder for the target
// For Unix, we build CFI blob map for each offset.
Debug.Assert(len % CfiCodeSize == 0);

// Record start/end of frames which shouldn't be overlapped.
_offsetToCfiStart.Add(start);
_offsetToCfiEnd.Add(end);
Expand Down
194 changes: 189 additions & 5 deletions src/JitInterface/src/CorInfoImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

using ILCompiler;
using ILCompiler.DependencyAnalysis;
using System.Threading;

#if READYTORUN
using System.Reflection.Metadata.Ecma335;
Expand Down Expand Up @@ -2692,6 +2693,7 @@ private void allocUnwindInfo(byte* pHotCode, byte* pColdCode, uint startOffset,
}

byte[] blobData = new byte[unwindSize];
byte[] unwindData = null;

for (uint i = 0; i < unwindSize; i++)
{
Expand All @@ -2702,18 +2704,21 @@ private void allocUnwindInfo(byte* pHotCode, byte* pColdCode, uint startOffset,

if (target.Architecture == TargetArchitecture.ARM64 && target.OperatingSystem == TargetOS.Linux)
{
blobData = CompressARM64CFI(blobData);
blobData = CompressARM64CFI(blobData, out unwindData);
}

_frameInfos[_usedFrameInfos++] = new FrameInfo(flags, (int)startOffset, (int)endOffset, blobData);
_frameInfos[_usedFrameInfos++] = new FrameInfo(flags, (int)startOffset, (int)endOffset, blobData, unwindData);
}

// Get the CFI data in the same shape as clang/LLVM generated one. This improves the compatibility with libunwind and other unwind solutions
// - Combine in one single block for the whole prolog instead of one CFI block per assembler instruction
// - Store CFA definition first
// - Store all used registers in ascending order
private byte[] CompressARM64CFI(byte[] blobData)

private byte[] CompressARM64CFI(byte[] blobData, out byte[] unwindData)
{
unwindData = null;

if (blobData == null || blobData.Length == 0)
{
return blobData;
Expand Down Expand Up @@ -2792,10 +2797,16 @@ private byte[] CompressARM64CFI(byte[] blobData)
}
}

ArrayBuilder<byte> unwind = new ArrayBuilder<byte>();
uint compactCode;

using (MemoryStream cfiStream = new MemoryStream())
{
int storeOffset = 0;

int unwindRegister = 0;
int unwindOffset = 0;

using (BinaryWriter cfiWriter = new BinaryWriter(cfiStream))
{
if (cfaRegister != -1)
Expand All @@ -2805,6 +2816,9 @@ private byte[] CompressARM64CFI(byte[] blobData)
cfiWriter.Write(cfaRegister);
cfiWriter.Write(cfaOffset);
storeOffset = cfaOffset;

unwindRegister = cfaRegister;
unwindOffset = cfaOffset;
}
else
{
Expand All @@ -2814,6 +2828,9 @@ private byte[] CompressARM64CFI(byte[] blobData)
cfiWriter.Write((byte)CFI_OPCODE.CFI_ADJUST_CFA_OFFSET);
cfiWriter.Write((short)-1);
cfiWriter.Write(cfaOffset);

unwindRegister = 31;
unwindOffset = cfaOffset;
}

if (spOffset != 0)
Expand All @@ -2822,25 +2839,192 @@ private byte[] CompressARM64CFI(byte[] blobData)
cfiWriter.Write((byte)CFI_OPCODE.CFI_DEF_CFA);
cfiWriter.Write((short)31);
cfiWriter.Write(spOffset);

unwindRegister = 31;
unwindOffset = spOffset;
}
}

Debug.Assert(unwindRegister >= 0 && unwindRegister <= 31);
Debug.Assert(unwindOffset % 8 == 0);
Debug.Assert(unwindOffset >= 0);

compactCode = GenerateARM64CompactUnwindCode(unwindRegister, unwindOffset, registerOffset);

unwindOffset /= 8;

Debug.Assert(unwindOffset <= ushort.MaxValue);

unwind.Add((byte)unwindRegister);
unwind.Add((byte)(unwindOffset & 0xFF));
unwind.Add((byte)((unwindOffset >> 8) & 0xFF));

for (int i = registerOffset.Length - 1; i >= 0; i--)
{
if (registerOffset[i] != int.MinValue)
{
unwindRegister = i;
unwindOffset = registerOffset[i] + storeOffset;

cfiWriter.Write((byte)codeOffset);
cfiWriter.Write((byte)CFI_OPCODE.CFI_REL_OFFSET);
cfiWriter.Write((short)i);
cfiWriter.Write(registerOffset[i] + storeOffset);
cfiWriter.Write((short)unwindRegister);
cfiWriter.Write(unwindOffset);

Debug.Assert(unwindOffset % 8 == 0);
Debug.Assert(unwindOffset >= 0);

// X0 - X31
if (unwindRegister >= 0 && unwindRegister < 32)
{
}
// D8 - D16
else if (unwindRegister >= 72 && unwindRegister < 81)
{
unwindRegister -= 40;
}
else
{
continue;
}

unwindOffset /= 8;

Debug.Assert(unwindOffset <= byte.MaxValue);

unwind.Add((byte)unwindRegister);

unwind.Add((byte)(unwindOffset & 0xFF));
unwind.Add((byte)((unwindOffset >> 8) & 0xFF));
}
}
}

if (compactCode != 0)
{
unwindData = BitConverter.GetBytes((ushort)compactCode);
}
else
{
unwind.Add(0xFF);
unwindData = unwind.ToArray();
}

return cfiStream.ToArray();
}
}

uint GenerateARM64CompactUnwindCode (int cfaRegister, int cfaOffset, int[] registerOffsets)
{
if (cfaOffset >= 512*8)
{
return 0;
}

for (int i = 0; i < 18; i++)
{
if (registerOffsets[i] != int.MinValue)
{
return 0;
}
}

for (int i = 31; i < registerOffsets.Length; i++)
{
if (registerOffsets[i] != int.MinValue)
{
return 0;
}
}

uint code = 0;

code |= (uint)(cfaOffset / 8);

int regCount = 0;
int reg = 19;
int lastRegOffset = int.MinValue;

if (registerOffsets[reg] != int.MinValue)
{
int offset = registerOffsets[reg];
reg++;
regCount++;

for (; reg <= 28; reg++)
{
if (registerOffsets[reg] == int.MinValue)
break;

if (registerOffsets[reg] != offset + 8)
{
return 0;
}

regCount++;
offset = registerOffsets[reg];
}

lastRegOffset = offset;
}

for (; reg <= 28; reg++)
{
if (registerOffsets[reg] != int.MinValue)
{
return 0;
}
}

code |= (uint)(regCount << 9);

switch (cfaRegister)
{
case 29:
{
if (registerOffsets[29] != -cfaOffset || registerOffsets[30] != -cfaOffset + 8)
{
return 0;
}

if (regCount > 0 && lastRegOffset != -8)
{
return 0;
}
}
break;

case 31:
{
if (regCount > 0 && lastRegOffset + 8 != cfaOffset)
{
return 0;
}

if (registerOffsets[29] + 8 != registerOffsets[30])
{
return 0;
}

int frameOffset = registerOffsets[29] / 8;

if (frameOffset > 3)
{
return 0;
}

code |= 0x8000;
code |= (uint)(frameOffset << 13);
}
break;

default:
return 0;
}

return code;
}

private void* allocGCInfo(UIntPtr size)
{
_gcInfo = new byte[(int)size];
Expand Down
5 changes: 3 additions & 2 deletions src/Native/Runtime/unix/UnixContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "daccess.h"
#include "PalRedhawkCommon.h"
#include "regdisplay.h"
#include "ICodeManager.h"
#include "config.h"

#include <libunwind.h>
Expand Down Expand Up @@ -630,7 +631,7 @@ bool FindProcInfo(UIntNative controlPC, UIntNative* startAddress, UIntNative* ls
}

// Virtually unwind stack to the caller of the context specified by the REGDISPLAY
bool VirtualUnwind(REGDISPLAY* pRegisterSet)
bool VirtualUnwind(MethodInfo* pMethodInfo, REGDISPLAY* pRegisterSet)
{
return UnwindHelpers::StepFrame(pRegisterSet);
return UnwindHelpers::StepFrame(pMethodInfo, pRegisterSet);
}
Loading

0 comments on commit ba4c439

Please sign in to comment.