@@ -258,7 +258,7 @@ protected override void OnInsert (Instruction item, int index)
258
258
item . next = current ;
259
259
}
260
260
261
- UpdateLocalScopes ( null , null ) ;
261
+ UpdateDebugInformation ( null , null ) ;
262
262
}
263
263
264
264
protected override void OnSet ( Instruction item , int index )
@@ -271,7 +271,7 @@ protected override void OnSet (Instruction item, int index)
271
271
current . previous = null ;
272
272
current . next = null ;
273
273
274
- UpdateLocalScopes ( item , current ) ;
274
+ UpdateDebugInformation ( item , current ) ;
275
275
}
276
276
277
277
protected override void OnRemove ( Instruction item , int index )
@@ -285,7 +285,7 @@ protected override void OnRemove (Instruction item, int index)
285
285
next . previous = item . previous ;
286
286
287
287
RemoveSequencePoint ( item ) ;
288
- UpdateLocalScopes ( item , next ?? previous ) ;
288
+ UpdateDebugInformation ( item , next ?? previous ) ;
289
289
290
290
item . previous = null ;
291
291
item . next = null ;
@@ -306,126 +306,189 @@ void RemoveSequencePoint (Instruction instruction)
306
306
}
307
307
}
308
308
309
- void UpdateLocalScopes ( Instruction removedInstruction , Instruction existingInstruction )
309
+ void UpdateDebugInformation ( Instruction removedInstruction , Instruction existingInstruction )
310
310
{
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
316
313
// 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 .
318
315
// Each instruction has its own offset, which is populated on load, but never updated (this would be pretty expensive to do).
319
316
// 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
324
319
// instructions is basically a no-op and removing instructions is as easy as changing the pointer.
325
320
// For this reason the algorithm here is:
326
321
// - 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)
332
328
// - Then update as necessary by manipulaitng instruction references alone
333
329
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 ;
339
342
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
+ }
341
353
}
342
354
343
- void UpdateLocalScope ( ScopeDebugInformation scope , Instruction removedInstruction , Instruction existingInstruction , ref InstructionOffsetCache cache )
355
+ void UpdateLocalScope ( ScopeDebugInformation scope , ref InstructionOffsetResolver resolver )
344
356
{
345
357
if ( scope == null )
346
358
return ;
347
359
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 ) ;
353
361
354
362
if ( scope . HasScopes ) {
355
363
foreach ( var subScope in scope . Scopes )
356
- UpdateLocalScope ( subScope , removedInstruction , existingInstruction , ref cache ) ;
364
+ UpdateLocalScope ( subScope , ref resolver ) ;
357
365
}
358
366
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 ) ;
364
368
}
365
369
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
+ }
370
377
}
371
378
372
- InstructionOffset ResolveInstructionOffset ( InstructionOffset inputOffset , ref InstructionOffsetCache cache )
379
+ void UpdateAsyncMethodBody ( AsyncMethodBodyDebugInformation debugInfo , ref InstructionOffsetResolver resolver )
373
380
{
374
- if ( inputOffset . IsResolved )
375
- return inputOffset ;
381
+ if ( ! debugInfo . CatchHandler . IsResolved ) {
382
+ resolver . Restart ( ) ;
383
+ debugInfo . CatchHandler = resolver . Resolve ( debugInfo . CatchHandler ) ;
384
+ }
376
385
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
+ }
378
390
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
+ }
381
396
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 ;
391
401
392
- if ( size == offset )
393
- return new InstructionOffset ( items [ i ] ) ;
402
+ int cache_offset ;
403
+ int cache_index ;
404
+ Instruction cache_instruction ;
394
405
395
- if ( size > offset )
396
- return new InstructionOffset ( i == 0 ? items [ 0 ] : items [ i - 1 ] ) ;
406
+ public int LastOffset { get => cache_offset ; }
397
407
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
+ }
400
417
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
+ }
409
424
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 ) ;
411
430
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
+ }
416
433
417
- cache . Instruction = item ;
434
+ InstructionOffset ResolveInstructionOffset ( InstructionOffset inputOffset )
435
+ {
436
+ if ( inputOffset . IsResolved )
437
+ return inputOffset ;
418
438
419
- if ( cache . Offset == offset )
420
- return new InstructionOffset ( cache . Instruction ) ;
439
+ int offset = inputOffset . Offset ;
421
440
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 ) ;
424
443
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 ;
427
471
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
+ }
429
492
}
430
493
}
431
494
}
0 commit comments