@@ -258,7 +258,7 @@ protected override void OnInsert (Instruction item, int index)
258258 item . next = current ;
259259 }
260260
261- UpdateLocalScopes ( null , null ) ;
261+ UpdateDebugInformation ( null , null ) ;
262262 }
263263
264264 protected override void OnSet ( Instruction item , int index )
@@ -271,7 +271,7 @@ protected override void OnSet (Instruction item, int index)
271271 current . previous = null ;
272272 current . next = null ;
273273
274- UpdateLocalScopes ( item , current ) ;
274+ UpdateDebugInformation ( item , current ) ;
275275 }
276276
277277 protected override void OnRemove ( Instruction item , int index )
@@ -285,7 +285,7 @@ protected override void OnRemove (Instruction item, int index)
285285 next . previous = item . previous ;
286286
287287 RemoveSequencePoint ( item ) ;
288- UpdateLocalScopes ( item , next ?? previous ) ;
288+ UpdateDebugInformation ( item , next ?? previous ) ;
289289
290290 item . previous = null ;
291291 item . next = null ;
@@ -306,126 +306,189 @@ void RemoveSequencePoint (Instruction instruction)
306306 }
307307 }
308308
309- void UpdateLocalScopes ( Instruction removedInstruction , Instruction existingInstruction )
309+ void UpdateDebugInformation ( Instruction removedInstruction , Instruction existingInstruction )
310310 {
311- var debug_info = method . debug_info ;
312- if ( debug_info == null )
313- return ;
314-
315- // Local scopes store start/end pair of "instruction offsets". Instruction offset can be either resolved, in which case it
311+ // Various bits of debug information store instruction offsets (as "pointers" to the IL)
312+ // Instruction offset can be either resolved, in which case it
316313 // has a reference to Instruction, or unresolved in which case it stores numerical offset (instruction offset in the body).
317- // Typically local scopes loaded from PE/PDB files will be resolved, but it's not a requirement .
314+ // Depending on where the InstructionOffset comes from ( loaded from PE/PDB or constructed) it can be in either state .
318315 // Each instruction has its own offset, which is populated on load, but never updated (this would be pretty expensive to do).
319316 // Instructions created during the editting will typically have offset 0 (so incorrect).
320- // Local scopes created during editing will also likely be resolved (so no numerical offsets).
321- // So while local scopes which are unresolved are relatively rare if they appear, manipulating them based
322- // on the offsets allone is pretty hard (since we can't rely on correct offsets of instructions).
323- // On the other hand resolved local scopes are easy to maintain, since they point to instructions and thus inserting
317+ // Manipulating unresolved InstructionOffsets is pretty hard (since we can't rely on correct offsets of instructions).
318+ // On the other hand resolved InstructionOffsets are easy to maintain, since they point to instructions and thus inserting
324319 // instructions is basically a no-op and removing instructions is as easy as changing the pointer.
325320 // For this reason the algorithm here is:
326321 // - First make sure that all instruction offsets are resolved - if not - resolve them
327- // - First time this will be relatively expensinve as it will walk the entire method body to convert offsets to instruction pointers
328- // Almost all local scopes are stored in the "right" order (sequentially per start offsets), so the code uses a simple one-item
329- // cache instruction<->offset to avoid walking instructions multiple times (that would only happen for scopes which are out of order).
330- // - Subsequent calls should be cheap as it will only walk all local scopes without doing anything
331- // - If there was an edit on local scope which makes some of them unresolved, the cost is proportional
322+ // - First time this will be relatively expensive as it will walk the entire method body to convert offsets to instruction pointers
323+ // Within the same debug info, IL offsets are typically stored in the "right" order (sequentially per start offsets),
324+ // so the code uses a simple one-item cache instruction<->offset to avoid walking instructions multiple times
325+ // (that would only happen for scopes which are out of order).
326+ // - Subsequent calls should be cheap as it will only walk all local scopes without doing anything (as it checks that they're resolved)
327+ // - If there was an edit which adds some unresolved, the cost is proportional (the code will only resolve those)
332328 // - Then update as necessary by manipulaitng instruction references alone
333329
334- InstructionOffsetCache cache = new InstructionOffsetCache ( ) {
335- Offset = 0 ,
336- Index = 0 ,
337- Instruction = items [ 0 ]
338- } ;
330+ InstructionOffsetResolver resolver = new InstructionOffsetResolver ( items , removedInstruction , existingInstruction ) ;
331+
332+ if ( method . debug_info != null )
333+ UpdateLocalScope ( method . debug_info . Scope , ref resolver ) ;
334+
335+ var custom_debug_infos = method . custom_infos ?? method . debug_info ? . custom_infos ;
336+ if ( custom_debug_infos != null ) {
337+ foreach ( var custom_debug_info in custom_debug_infos ) {
338+ switch ( custom_debug_info ) {
339+ case StateMachineScopeDebugInformation state_machine_scope :
340+ UpdateStateMachineScope ( state_machine_scope , ref resolver ) ;
341+ break ;
339342
340- UpdateLocalScope ( debug_info . Scope , removedInstruction , existingInstruction , ref cache ) ;
343+ case AsyncMethodBodyDebugInformation async_method_body :
344+ UpdateAsyncMethodBody ( async_method_body , ref resolver ) ;
345+ break ;
346+
347+ default :
348+ // No need to update the other debug info as they don't store instruction references
349+ break ;
350+ }
351+ }
352+ }
341353 }
342354
343- void UpdateLocalScope ( ScopeDebugInformation scope , Instruction removedInstruction , Instruction existingInstruction , ref InstructionOffsetCache cache )
355+ void UpdateLocalScope ( ScopeDebugInformation scope , ref InstructionOffsetResolver resolver )
344356 {
345357 if ( scope == null )
346358 return ;
347359
348- if ( ! scope . Start . IsResolved )
349- scope . Start = ResolveInstructionOffset ( scope . Start , ref cache ) ;
350-
351- if ( ! scope . Start . IsEndOfMethod && scope . Start . ResolvedInstruction == removedInstruction )
352- scope . Start = new InstructionOffset ( existingInstruction ) ;
360+ scope . Start = resolver . Resolve ( scope . Start ) ;
353361
354362 if ( scope . HasScopes ) {
355363 foreach ( var subScope in scope . Scopes )
356- UpdateLocalScope ( subScope , removedInstruction , existingInstruction , ref cache ) ;
364+ UpdateLocalScope ( subScope , ref resolver ) ;
357365 }
358366
359- if ( ! scope . End . IsResolved )
360- scope . End = ResolveInstructionOffset ( scope . End , ref cache ) ;
361-
362- if ( ! scope . End . IsEndOfMethod && scope . End . ResolvedInstruction == removedInstruction )
363- scope . End = new InstructionOffset ( existingInstruction ) ;
367+ scope . End = resolver . Resolve ( scope . End ) ;
364368 }
365369
366- struct InstructionOffsetCache {
367- public int Offset ;
368- public int Index ;
369- public Instruction Instruction ;
370+ void UpdateStateMachineScope ( StateMachineScopeDebugInformation debugInfo , ref InstructionOffsetResolver resolver )
371+ {
372+ resolver . Restart ( ) ;
373+ foreach ( var scope in debugInfo . Scopes ) {
374+ scope . Start = resolver . Resolve ( scope . Start ) ;
375+ scope . End = resolver . Resolve ( scope . End ) ;
376+ }
370377 }
371378
372- InstructionOffset ResolveInstructionOffset ( InstructionOffset inputOffset , ref InstructionOffsetCache cache )
379+ void UpdateAsyncMethodBody ( AsyncMethodBodyDebugInformation debugInfo , ref InstructionOffsetResolver resolver )
373380 {
374- if ( inputOffset . IsResolved )
375- return inputOffset ;
381+ if ( ! debugInfo . CatchHandler . IsResolved ) {
382+ resolver . Restart ( ) ;
383+ debugInfo . CatchHandler = resolver . Resolve ( debugInfo . CatchHandler ) ;
384+ }
376385
377- int offset = inputOffset . Offset ;
386+ resolver . Restart ( ) ;
387+ for ( int i = 0 ; i < debugInfo . Yields . Count ; i ++ ) {
388+ debugInfo . Yields [ i ] = resolver . Resolve ( debugInfo . Yields [ i ] ) ;
389+ }
378390
379- if ( cache . Offset == offset )
380- return new InstructionOffset ( cache . Instruction ) ;
391+ resolver . Restart ( ) ;
392+ for ( int i = 0 ; i < debugInfo . Resumes . Count ; i ++ ) {
393+ debugInfo . Resumes [ i ] = resolver . Resolve ( debugInfo . Resumes [ i ] ) ;
394+ }
395+ }
381396
382- if ( cache . Offset > offset ) {
383- // This should be rare - we're resolving offset pointing to a place before the current cache position
384- // resolve by walking the instructions from start and don't cache the result.
385- int size = 0 ;
386- for ( int i = 0 ; i < items . Length ; i ++ ) {
387- // The array can be larger than the actual size, in which case its padded with nulls at the end
388- // so when we reach null, treat it as an end of the IL.
389- if ( items [ i ] == null )
390- return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
397+ struct InstructionOffsetResolver {
398+ readonly Instruction [ ] items ;
399+ readonly Instruction removed_instruction ;
400+ readonly Instruction existing_instruction ;
391401
392- if ( size == offset )
393- return new InstructionOffset ( items [ i ] ) ;
402+ int cache_offset ;
403+ int cache_index ;
404+ Instruction cache_instruction ;
394405
395- if ( size > offset )
396- return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
406+ public int LastOffset { get => cache_offset ; }
397407
398- size += items [ i ] . GetSize ( ) ;
399- }
408+ public InstructionOffsetResolver ( Instruction [ ] instructions , Instruction removedInstruction , Instruction existingInstruction )
409+ {
410+ items = instructions ;
411+ removed_instruction = removedInstruction ;
412+ existing_instruction = existingInstruction ;
413+ cache_offset = 0 ;
414+ cache_index = 0 ;
415+ cache_instruction = items [ 0 ] ;
416+ }
400417
401- // Offset is larger than the size of the body - so it points after the end
402- return new InstructionOffset ( ) ;
403- } else {
404- // The offset points after the current cache position - so continue counting and update the cache
405- int size = cache . Offset ;
406- for ( int i = cache . Index ; i < items . Length ; i ++ ) {
407- cache . Index = i ;
408- cache . Offset = size ;
418+ public void Restart ( )
419+ {
420+ cache_offset = 0 ;
421+ cache_index = 0 ;
422+ cache_instruction = items [ 0 ] ;
423+ }
409424
410- var item = items [ i ] ;
425+ public InstructionOffset Resolve ( InstructionOffset inputOffset )
426+ {
427+ var result = ResolveInstructionOffset ( inputOffset ) ;
428+ if ( ! result . IsEndOfMethod && result . ResolvedInstruction == removed_instruction )
429+ result = new InstructionOffset ( existing_instruction ) ;
411430
412- // Allow for trailing null values in the case of
413- // instructions.Size < instructions.Capacity
414- if ( item == null )
415- return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
431+ return result ;
432+ }
416433
417- cache . Instruction = item ;
434+ InstructionOffset ResolveInstructionOffset ( InstructionOffset inputOffset )
435+ {
436+ if ( inputOffset . IsResolved )
437+ return inputOffset ;
418438
419- if ( cache . Offset == offset )
420- return new InstructionOffset ( cache . Instruction ) ;
439+ int offset = inputOffset . Offset ;
421440
422- if ( cache . Offset > offset )
423- return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
441+ if ( cache_offset == offset )
442+ return new InstructionOffset ( cache_instruction ) ;
424443
425- size += item . GetSize ( ) ;
426- }
444+ if ( cache_offset > offset ) {
445+ // This should be rare - we're resolving offset pointing to a place before the current cache position
446+ // resolve by walking the instructions from start and don't cache the result.
447+ int size = 0 ;
448+ for ( int i = 0 ; i < items . Length ; i ++ ) {
449+ // The array can be larger than the actual size, in which case its padded with nulls at the end
450+ // so when we reach null, treat it as an end of the IL.
451+ if ( items [ i ] == null )
452+ return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
453+
454+ if ( size == offset )
455+ return new InstructionOffset ( items [ i ] ) ;
456+
457+ if ( size > offset )
458+ return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
459+
460+ size += items [ i ] . GetSize ( ) ;
461+ }
462+
463+ // Offset is larger than the size of the body - so it points after the end
464+ return new InstructionOffset ( ) ;
465+ } else {
466+ // The offset points after the current cache position - so continue counting and update the cache
467+ int size = cache_offset ;
468+ for ( int i = cache_index ; i < items . Length ; i ++ ) {
469+ cache_index = i ;
470+ cache_offset = size ;
427471
428- return new InstructionOffset ( ) ;
472+ var item = items [ i ] ;
473+
474+ // Allow for trailing null values in the case of
475+ // instructions.Size < instructions.Capacity
476+ if ( item == null )
477+ return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
478+
479+ cache_instruction = item ;
480+
481+ if ( cache_offset == offset )
482+ return new InstructionOffset ( cache_instruction ) ;
483+
484+ if ( cache_offset > offset )
485+ return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
486+
487+ size += item . GetSize ( ) ;
488+ }
489+
490+ return new InstructionOffset ( ) ;
491+ }
429492 }
430493 }
431494 }
0 commit comments