public MethodDebugInformation GetDebugInfo(MethodIL methodIL) { if (!GenerateDebugInfo) { return(MethodDebugInformation.None); } // This method looks odd right now, but it's an extensibility point that lets us generate // fake debugging information for things that don't have physical symbols. return(methodIL.GetDebugInfo()); }
public override MethodDebugInformation GetDebugInfo(MethodIL methodIL) { MethodIL definitionIL = methodIL.GetMethodILDefinition(); if (definitionIL is EcmaMethodIL) { return(methodIL.GetDebugInfo()); } return(MethodDebugInformation.None); }
public MessageOrigin(MethodIL origin, int ilOffset) { string document = null; int? lineNumber = null; IEnumerable <ILSequencePoint> sequencePoints = origin.GetDebugInfo()?.GetSequencePoints(); if (sequencePoints != null) { foreach (var sequencePoint in sequencePoints) { if (sequencePoint.Offset <= ilOffset) { document = sequencePoint.Document; lineNumber = sequencePoint.LineNumber; } } } FileName = document; MemberDefinition = origin.OwningMethod; SourceLine = lineNumber; SourceColumn = null; }
public MethodIL GetMethodILWithInlinedSubstitutions(MethodIL method) { // This attempts to find all basic blocks that are unreachable after applying the substitutions. // // On a high level, we first find all the basic blocks and instruction boundaries in the IL stream. // This is tracked in a sidecar `flags` array that has flags for each byte of the IL stream. // // Once we have all the basic blocks and instruction boundaries, we do a marking phase to mark // the reachable blocks. We use substitutions to tell us what's unreachable. We consider conditional // branches "interesting" and whenever we see one, we seek backwards in the IL instruction stream // to find the instruction that feeds it. We make sure we don't cross the basic block boundary while // doing that. If the conditional instruction is fed by known values (either through the substitutions // or because it's an IL constant), we simulate the result of the comparison and only mark // the taken branch. We also mark any associated EH regions. // // The "seek backwards to find what feeds the comparison" only works for a couple known instructions // (load constant, call). It can't e.g. skip over arguments to the call. // // Last step is a sweep - we replace the tail of all unreachable blocks with "br $-2" // and nop out the rest. If the basic block is smaller than 2 bytes, we don't touch it. // We also eliminate any EH records that correspond to the stubbed out basic block. Debug.Assert(method.GetMethodILDefinition() == method); ILExceptionRegion[] ehRegions = method.GetExceptionRegions(); byte[] methodBytes = method.GetILBytes(); OpcodeFlags[] flags = new OpcodeFlags[methodBytes.Length]; // Offset 0 is the first basic block Stack <int> offsetsToVisit = new Stack <int>(); offsetsToVisit.Push(0); // Basic blocks also start around EH regions foreach (ILExceptionRegion ehRegion in ehRegions) { if (ehRegion.Kind == ILExceptionRegionKind.Filter) { offsetsToVisit.Push(ehRegion.FilterOffset); } offsetsToVisit.Push(ehRegion.HandlerOffset); } // Identify basic blocks and instruction boundaries while (offsetsToVisit.TryPop(out int offset)) { // If this was already visited, we're done if (flags[offset] != 0) { // Also mark as basic block start in case this was a target of a backwards branch. flags[offset] |= OpcodeFlags.BasicBlockStart; continue; } flags[offset] |= OpcodeFlags.BasicBlockStart; // Read until we reach the end of the basic block ILReader reader = new ILReader(methodBytes, offset); while (reader.HasNext) { offset = reader.Offset; flags[offset] |= OpcodeFlags.InstructionStart; ILOpcode opcode = reader.ReadILOpcode(); if (opcode >= ILOpcode.br_s && opcode <= ILOpcode.blt_un || opcode == ILOpcode.leave || opcode == ILOpcode.leave_s) { int destination = reader.ReadBranchDestination(opcode); offsetsToVisit.Push(destination); if (opcode != ILOpcode.leave && opcode != ILOpcode.leave_s && opcode != ILOpcode.br && opcode != ILOpcode.br_s) { // Branches not tested for above are conditional and the flow falls through. offsetsToVisit.Push(reader.Offset); } flags[offset] |= OpcodeFlags.EndBasicBlock; } else if (opcode == ILOpcode.ret || opcode == ILOpcode.endfilter || opcode == ILOpcode.endfinally || opcode == ILOpcode.throw_ || opcode == ILOpcode.rethrow || opcode == ILOpcode.jmp) { // Ends basic block. flags[offset] |= OpcodeFlags.EndBasicBlock; reader.Skip(opcode); } else if (opcode == ILOpcode.switch_) { uint count = reader.ReadILUInt32(); int jmpBase = reader.Offset + (int)(4 * count); for (uint i = 0; i < count; i++) { int destination = (int)reader.ReadILUInt32() + jmpBase; offsetsToVisit.Push(destination); } // We fall through to the next basic block. offsetsToVisit.Push(reader.Offset); flags[offset] |= OpcodeFlags.EndBasicBlock; } else { reader.Skip(opcode); } if ((flags[offset] & OpcodeFlags.EndBasicBlock) != 0) { if (reader.HasNext) { // If the bytes following this basic block are not reachable from anywhere, // the sweeping step would consider them to be part of the last instruction // of the current basic block because of how instruction boundaries are identified. // We wouldn't NOP them out if the current basic block is reachable. // // That's a problem for RyuJIT because RyuJIT looks at these bytes for... reasons. // // We can just do the same thing as RyuJIT and consider those a basic block. offsetsToVisit.Push(reader.Offset); } break; } } } // Mark all reachable basic blocks // // We also do another round of basic block marking to mark beginning of visible basic blocks // after dead branch elimination. This allows us to limit the number of potential small basic blocks // that are not interesting (because no code jumps to them anymore), but could prevent us from // finishing the process. Unreachable basic blocks smaller than 2 bytes abort the substitution // inlining process because we can't neutralize them (turn them into an infinite loop). offsetsToVisit.Push(0); while (offsetsToVisit.TryPop(out int offset)) { // Mark as a basic block visible after constant propagation. flags[offset] |= OpcodeFlags.VisibleBasicBlockStart; // If this was already marked, we're done. if ((flags[offset] & OpcodeFlags.Mark) != 0) { continue; } ILReader reader = new ILReader(methodBytes, offset); while (reader.HasNext) { offset = reader.Offset; flags[offset] |= OpcodeFlags.Mark; ILOpcode opcode = reader.ReadILOpcode(); // Mark any applicable EH blocks foreach (ILExceptionRegion ehRegion in ehRegions) { int delta = offset - ehRegion.TryOffset; if (delta >= 0 && delta < ehRegion.TryLength) { if (ehRegion.Kind == ILExceptionRegionKind.Filter) { offsetsToVisit.Push(ehRegion.FilterOffset); } offsetsToVisit.Push(ehRegion.HandlerOffset); // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. int handlerEnd = ehRegion.HandlerOffset + ehRegion.HandlerLength; if (handlerEnd < flags.Length) { flags[handlerEnd] |= OpcodeFlags.VisibleBasicBlockStart; } } } // All branches are relevant to basic block tracking if (opcode == ILOpcode.brfalse || opcode == ILOpcode.brfalse_s || opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s) { int destination = reader.ReadBranchDestination(opcode); if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int constant)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); offsetsToVisit.Push(reader.Offset); } else if ((constant == 0 && (opcode == ILOpcode.brfalse || opcode == ILOpcode.brfalse_s)) || (constant != 0 && (opcode == ILOpcode.brtrue || opcode == ILOpcode.brtrue_s))) { // Only the "branch taken" is live. // The fallthrough marks the beginning of a visible (but not live) basic block. offsetsToVisit.Push(destination); flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } else { // Only fallthrough is live. // The "brach taken" marks the beginning of a visible (but not live) basic block. flags[destination] |= OpcodeFlags.VisibleBasicBlockStart; offsetsToVisit.Push(reader.Offset); } } else if (opcode == ILOpcode.beq || opcode == ILOpcode.beq_s || opcode == ILOpcode.bne_un || opcode == ILOpcode.bne_un_s) { int destination = reader.ReadBranchDestination(opcode); if (!TryGetConstantArgument(method, methodBytes, flags, offset, 0, out int left) || !TryGetConstantArgument(method, methodBytes, flags, offset, 1, out int right)) { // Can't get the constant - both branches are live. offsetsToVisit.Push(destination); offsetsToVisit.Push(reader.Offset); } else if ((left == right && (opcode == ILOpcode.beq || opcode == ILOpcode.beq_s) || (left != right) && (opcode == ILOpcode.bne_un || opcode == ILOpcode.bne_un_s))) { // Only the "branch taken" is live. // The fallthrough marks the beginning of a visible (but not live) basic block. offsetsToVisit.Push(destination); flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } else { // Only fallthrough is live. // The "brach taken" marks the beginning of a visible (but not live) basic block. flags[destination] |= OpcodeFlags.VisibleBasicBlockStart; offsetsToVisit.Push(reader.Offset); } } else if (opcode >= ILOpcode.br_s && opcode <= ILOpcode.blt_un || opcode == ILOpcode.leave || opcode == ILOpcode.leave_s) { int destination = reader.ReadBranchDestination(opcode); offsetsToVisit.Push(destination); if (opcode != ILOpcode.leave && opcode != ILOpcode.leave_s && opcode != ILOpcode.br && opcode != ILOpcode.br_s) { // Branches not tested for above are conditional and the flow falls through. offsetsToVisit.Push(reader.Offset); } else { // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. if (reader.HasNext) { flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } } } else if (opcode == ILOpcode.switch_) { uint count = reader.ReadILUInt32(); int jmpBase = reader.Offset + (int)(4 * count); for (uint i = 0; i < count; i++) { int destination = (int)reader.ReadILUInt32() + jmpBase; offsetsToVisit.Push(destination); } offsetsToVisit.Push(reader.Offset); } else if (opcode == ILOpcode.ret || opcode == ILOpcode.endfilter || opcode == ILOpcode.endfinally || opcode == ILOpcode.throw_ || opcode == ILOpcode.rethrow || opcode == ILOpcode.jmp) { reader.Skip(opcode); // RyuJIT is going to look at this basic block even though it's unreachable. // Consider it visible so that we replace the tail with an endless loop. if (reader.HasNext) { flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart; } } else { reader.Skip(opcode); } if ((flags[offset] & OpcodeFlags.EndBasicBlock) != 0) { break; } } } // Now sweep unreachable basic blocks by replacing them with nops bool hasUnmarkedIntructions = false; foreach (var flag in flags) { if ((flag & OpcodeFlags.InstructionStart) != 0 && (flag & OpcodeFlags.Mark) == 0) { hasUnmarkedIntructions = true; } } if (!hasUnmarkedIntructions) { return(method); } byte[] newBody = (byte[])methodBytes.Clone(); int position = 0; while (position < newBody.Length) { Debug.Assert((flags[position] & OpcodeFlags.InstructionStart) != 0); Debug.Assert((flags[position] & OpcodeFlags.VisibleBasicBlockStart) != 0); bool erase = (flags[position] & OpcodeFlags.Mark) == 0; int basicBlockStart = position; do { if (erase) { newBody[position] = (byte)ILOpCode.Nop; } position++; } while (position < newBody.Length && (flags[position] & OpcodeFlags.VisibleBasicBlockStart) == 0); // If we had to nop out this basic block, we need to neutralize it by appending // an infinite loop ("br $-2"). // We append instead of prepend because RyuJIT's importer has trouble with junk unreachable bytes. if (erase) { if (position - basicBlockStart < 2) { // We cannot neutralize the basic block, so better leave the method alone. // The control would fall through to the next basic block. return(method); } newBody[position - 2] = (byte)ILOpCode.Br_s; newBody[position - 1] = unchecked ((byte)-2); } } // EH regions with unmarked handlers belong to unmarked basic blocks // Need to eliminate them because they're not usable. ArrayBuilder <ILExceptionRegion> newEHRegions = new ArrayBuilder <ILExceptionRegion>(); foreach (ILExceptionRegion ehRegion in ehRegions) { if ((flags[ehRegion.HandlerOffset] & OpcodeFlags.Mark) != 0) { newEHRegions.Add(ehRegion); } } // Existing debug information might not match new instruction boundaries (plus there's little point // in generating debug information for NOPs) - generate new debug information by filtering // out the sequence points associated with nopped out instructions. MethodDebugInformation debugInfo = method.GetDebugInfo(); IEnumerable <ILSequencePoint> oldSequencePoints = debugInfo?.GetSequencePoints(); if (oldSequencePoints != null) { ArrayBuilder <ILSequencePoint> sequencePoints = new ArrayBuilder <ILSequencePoint>(); foreach (var sequencePoint in oldSequencePoints) { if (sequencePoint.Offset < flags.Length && (flags[sequencePoint.Offset] & OpcodeFlags.Mark) != 0) { sequencePoints.Add(sequencePoint); } } debugInfo = new SubstitutedDebugInformation(debugInfo, sequencePoints.ToArray()); } return(new SubstitutedMethodIL(method, newBody, newEHRegions.ToArray(), debugInfo)); }
public virtual MethodDebugInformation GetDebugInfo(MethodIL methodIL) { return(methodIL.GetDebugInfo()); }