| File: Compiler\DependencyAnalysis\ReadyToRun\DebugInfoTableNode.cs | Web Access |
| Project: src\runtime\src\coreclr\tools\aot\ILCompiler.ReadyToRun\ILCompiler.ReadyToRun.csproj (ILCompiler.ReadyToRun) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using Internal.JitInterface; using Internal.NativeFormat; using Internal.Text; using Internal.TypeSystem; namespace ILCompiler.DependencyAnalysis.ReadyToRun { /// <summary> /// DebugInfoTableNode emits the data structures required to support managed code debugging /// for ready to run code. See https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/cordebuginfo.h /// for the types this information is based on. We store two tables of information in an /// image: /// /// Location Information /// /// In order to provide source line debugging, generated ready-to-run code is mapped /// back to the original IL instruction. This maps to OffsetMapping in cordebuginfo.h. /// /// A row in the table looks like this: /// /// Native offset | IL Offset | Flags /// /// Native offset starts at 0 for the beginning of a method. The rows are stored in /// ascending order of offset and use a delta encoding. That is, each successive row /// stores the difference between the previous native offset and the new one to save /// encoding space. /// /// Flags stores values useful to the debugger. For example, if the offset is a /// call site, or the stack is empty at that point. These are used for edit and /// continue. It maps to SourceTypes in cordebuginfo.h. /// /// Variable Information /// /// The debugger needs to know, for a given native code range, where the memory /// backing each variable is stored. That can be in a register, on the stack, or maybe /// even split between a combination of registers and the stack. This maps to /// NativeVarInfo in cordebuginfo.h. /// /// A row in the table looks like this: /// /// Start Offset | End Offset | Variable Number | Location /// /// Start Offset and End Offset are offsets within the generated native code and are /// used to capture the range of instructions during which the variable is stored /// in the same location. /// /// Variable Number is the index of each variable in the original IL code. /// /// Location is a variant data structure specifying what memory is backing the variable /// for a given native code range. This is a specific register, or a stack offset. This /// maps to VarLoc in cordebuginfo.h. /// /// </summary> public class DebugInfoTableNode : HeaderTableNode { public override int ClassCode => 1000735112; public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) { sb.Append(nameMangler.CompilationUnitPrefix); sb.Append("__ReadyToRunDebugInfoTable"u8); } public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { // This node does not trigger generation of other nodes. if (relocsOnly) return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this }); NativeWriter writer = new NativeWriter(); Section section = writer.NewSection(); VertexArray vertexArray = new VertexArray(section); section.Place(vertexArray); Dictionary<byte[], BlobVertex> blobCache = new Dictionary<byte[], BlobVertex>(ByteArrayComparer.Instance); foreach (MethodWithGCInfo method in factory.EnumerateCompiledMethods()) { MemoryStream methodDebugBlob = new MemoryStream(); byte[] bounds = method.DebugLocInfos; byte[] vars = method.DebugVarInfos; NibbleWriter nibbleWriter = new NibbleWriter(); uint boundsLength = (uint)(bounds?.Length ?? 0); bool isFatHeader = boundsLength == DebugInfoFat; if (isFatHeader) { nibbleWriter.WriteUInt(DebugInfoFat); nibbleWriter.WriteUInt(boundsLength); nibbleWriter.WriteUInt((uint)(vars?.Length ?? 0)); nibbleWriter.WriteUInt(0); // cbUninstrumentedBounds nibbleWriter.WriteUInt(0); // cbPatchpointInfo nibbleWriter.WriteUInt(0); // cbRichDebugInfo nibbleWriter.WriteUInt(0); // cbAsyncInfo } else { nibbleWriter.WriteUInt(boundsLength); nibbleWriter.WriteUInt((uint)(vars?.Length ?? 0)); } byte[] header = nibbleWriter.ToArray(); methodDebugBlob.Write(header, 0, header.Length); if (bounds?.Length > 0) { methodDebugBlob.Write(bounds, 0, bounds.Length); } if (vars?.Length > 0) { methodDebugBlob.Write(vars, 0, vars.Length); } byte[] debugBlobArrayKey = methodDebugBlob.ToArray(); if (!blobCache.TryGetValue(debugBlobArrayKey, out BlobVertex debugBlob)) { debugBlob = new BlobVertex(methodDebugBlob.ToArray()); blobCache.Add(debugBlobArrayKey, debugBlob); } vertexArray.Set(factory.RuntimeFunctionsTable.GetIndex(method), new DebugInfoVertex(debugBlob)); } vertexArray.ExpandLayout(); MemoryStream writerContent = new MemoryStream(); writer.Save(writerContent); return new ObjectData( data: writerContent.ToArray(), relocs: null, alignment: 8, definedSymbols: new ISymbolDefinitionNode[] { this }); } public static byte[] CreateBoundsBlobForMethod(OffsetMapping[] offsetMapping) { if (offsetMapping == null || offsetMapping.Length == 0) return null; uint previousNativeOffset = 0; uint maxNativeDelta = 0; uint maxILValue = 0; foreach (var locInfo in offsetMapping) { maxNativeDelta = Math.Max(maxNativeDelta, locInfo.nativeOffset - previousNativeOffset); maxILValue = Math.Max(maxILValue, locInfo.ilOffset + 3); previousNativeOffset = locInfo.nativeOffset; } int bitWidthForNativeDelta = Math.Max(1, 32 - System.Numerics.BitOperations.LeadingZeroCount(maxNativeDelta)); int bitWidthForILOffset = Math.Max(1, 32 - System.Numerics.BitOperations.LeadingZeroCount(maxILValue)); int bitWidthReportForNativeDelta = bitWidthForNativeDelta - 1; int bitWidthReportForILOffset = bitWidthForILOffset - 1; NibbleWriter writer2 = new NibbleWriter(); writer2.WriteUInt((uint)offsetMapping.Length); // We need the total count writer2.WriteUInt((uint)bitWidthReportForNativeDelta); // Number of bits needed for native deltas writer2.WriteUInt((uint)bitWidthReportForILOffset); // How many bits needed for IL offsets const int BitsForSourceType = 3; int bitWidth = bitWidthForNativeDelta + bitWidthForILOffset + BitsForSourceType; int totalBits = bitWidth * offsetMapping.Length; int bytesNeededForArray = (totalBits + 7) / 8; byte bitsInProgress = 0; byte bitsInProgressCount = 0; List<byte> boundsBlob = new List<byte>(writer2.ToArray()); for (uint i = 0; i < offsetMapping.Length; i++) { var bound = offsetMapping[i]; uint prevNativeOffset = 0; if (i > 0) { prevNativeOffset = offsetMapping[i - 1].nativeOffset; } uint nativeOffsetDelta = bound.nativeOffset - prevNativeOffset; uint sourceBits = 0; if ((bound.source & SourceTypes.CALL_INSTRUCTION) != 0) sourceBits |= 1; if ((bound.source & SourceTypes.STACK_EMPTY) != 0) sourceBits |= 2; if ((bound.source & SourceTypes.ASYNC) != 0) sourceBits |= 4; if ((bound.source & ~(SourceTypes.CALL_INSTRUCTION | SourceTypes.STACK_EMPTY | SourceTypes.ASYNC)) != 0) throw new InternalCompilerErrorException("Unknown source type " + (uint)bound.source); if ((sourceBits & ~((1u << BitsForSourceType) - 1)) != 0) throw new InternalCompilerErrorException("Unencodable source type " + sourceBits + " (for " + (uint)bound.source + ")"); ulong mappingDataEncoded = (ulong)sourceBits | ((ulong)nativeOffsetDelta << BitsForSourceType) | ((ulong)((int)bound.ilOffset - (int)MappingTypes.EPILOG) << (BitsForSourceType + bitWidthForNativeDelta)); for (byte bitsToWrite = (byte)bitWidth; bitsToWrite > 0;) { // Figure out next bits to write if we need to combine with a previous byte. if (bitsInProgressCount > 0) { byte bitsToAddFromNewEncoding = (byte)(8 - bitsInProgressCount); byte bitsToAddOnToInProgress = (byte)((mappingDataEncoded & ((((uint)1) << bitsToAddFromNewEncoding) - 1)) << bitsInProgressCount); boundsBlob.Add((byte)(bitsToAddOnToInProgress | bitsInProgress)); mappingDataEncoded >>= bitsToAddFromNewEncoding; bitsToWrite -= bitsToAddFromNewEncoding; bitsInProgressCount = 0; } else if (bitsToWrite >= 8) { boundsBlob.Add((byte)mappingDataEncoded); mappingDataEncoded >>= 8; bitsToWrite -= 8; } else { bitsInProgress = (byte)mappingDataEncoded; bitsInProgressCount = bitsToWrite; bitsToWrite = 0; } } } if (bitsInProgressCount > 0) { Debug.Assert(bitsInProgressCount < 8); boundsBlob.Add(bitsInProgress); } return boundsBlob.ToArray(); } public static byte[] CreateVarBlobForMethod(NativeVarInfo[] varInfos, TargetDetails target) { if (varInfos == null || varInfos.Length == 0) return null; bool isX86 = target.Architecture == TargetArchitecture.X86; NibbleWriter writer = new NibbleWriter(); writer.WriteUInt((uint)varInfos.Length); foreach (var nativeVarInfo in varInfos) { writer.WriteUInt((uint)(nativeVarInfo.varNumber - (int)ILNum.MAX_ILNUM)); writer.WriteUInt(nativeVarInfo.startOffset); if (nativeVarInfo.varNumber == unchecked((uint)ILNum.CALL_RETURN_ILNUM)) { writer.WriteUInt(nativeVarInfo.callReturnValueILOffset); } else { writer.WriteUInt(nativeVarInfo.endOffset - nativeVarInfo.startOffset); } VarLocType varLocType = nativeVarInfo.varLoc.LocationType; writer.WriteUInt((uint)varLocType); switch (varLocType) { case VarLocType.VLT_REG: case VarLocType.VLT_REG_FP: case VarLocType.VLT_REG_BYREF: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); break; case VarLocType.VLT_STK: case VarLocType.VLT_STK_BYREF: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); WriteEncodedStackOffset(writer, nativeVarInfo.varLoc.C, assume4ByteAligned : isX86); break; case VarLocType.VLT_REG_REG: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); writer.WriteUInt((uint)nativeVarInfo.varLoc.C); break; case VarLocType.VLT_REG_STK: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); writer.WriteUInt((uint)nativeVarInfo.varLoc.C); WriteEncodedStackOffset(writer, nativeVarInfo.varLoc.D, assume4ByteAligned : isX86); break; case VarLocType.VLT_STK_REG: WriteEncodedStackOffset(writer, nativeVarInfo.varLoc.B, assume4ByteAligned : isX86); writer.WriteUInt((uint)nativeVarInfo.varLoc.C); writer.WriteUInt((uint)nativeVarInfo.varLoc.D); break; case VarLocType.VLT_STK2: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); WriteEncodedStackOffset(writer, nativeVarInfo.varLoc.C, assume4ByteAligned : isX86); break; case VarLocType.VLT_FPSTK: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); break; case VarLocType.VLT_FIXED_VA: writer.WriteUInt((uint)nativeVarInfo.varLoc.B); break; default: throw new BadImageFormatException("Unexpected var loc type"); } static void WriteEncodedStackOffset(NibbleWriter _writer, int offset, bool assume4ByteAligned) { if (assume4ByteAligned) { Debug.Assert(offset % 4 == 0); _writer.WriteInt(offset / 4); } else { _writer.WriteInt(offset); } } } return writer.ToArray(); } private const int DebugInfoFat = 0; } }