@@ -397,30 +397,36 @@ private bool SkipGeneratedBranchesForExceptionHandlers(MethodDefinition methodDe
397
397
398
398
private bool SkipAwaitUsingBranches ( List < Instruction > instructions , Instruction instruction )
399
399
{
400
+ /*
401
+ There are three kinds of branches we'll want to skip in the compiler-
402
+ generated state machine that arises from an "await using" statement.
403
+ */
404
+
405
+ int currentIndex = instructions . BinarySearch ( instruction , new InstructionByOffsetComparer ( ) ) ;
406
+
400
407
/*
401
408
Suppose we have an "await using" statement like this one:
402
409
403
410
await using (var ms = new MemoryStream(Encoding.ASCII.GetBytes("abc")))
404
411
{
405
412
}
406
413
407
- The asynchronously disposable object is stored in a compiler-generated
408
- field with the name <xxx>5__#, where "xxx" is the name of the variable
409
- and "#" is an increasing index number for compiler-generated variables
410
- (so, in this case, it would be <ms>5__1). Where it increases from 1 is
411
- when "await using" is nested.
412
-
413
- There are three kinds of branches that the compiler generates that we
414
- should skip.
415
- */
414
+ In the Debug configuration, the asynchronously disposable object is stored
415
+ in a compiler-generated "hoisted local" field (written as ">5__"). The
416
+ rest of the name includes the name of the local variable and a sort of
417
+ sequence number (so, for example, in the example above, the full name
418
+ would be "<ms>5__1").
416
419
417
- int currentIndex = instructions . BinarySearch ( instruction , new InstructionByOffsetComparer ( ) ) ;
420
+ In the Release configuration, the object is instead stored in a local
421
+ variable in the generated MoveNext() method.
418
422
419
- /*
420
423
The first kind of branch we want to skip is a check that the variable
421
424
holding the asychronously disposable object is null. In the presence
422
425
of nested "await using" statements, all of these variables are checked
423
- in various places, but the IL for each one follows the same pattern.
426
+ in various places, but the IL for each one follows one of two patterns
427
+ (one for the Debug and one for the Release configuration).
428
+
429
+ For the Debug configuration, we have:
424
430
425
431
if (<ms>5__1 == null) [<--- we want to skip this]
426
432
{
@@ -430,19 +436,41 @@ holding the asychronously disposable object is null. In the presence
430
436
IL_0048: ldarg.0
431
437
IL_0049: ldfld class [System.Private.CoreLib]System.IO.MemoryStream AwaitUsing/'<AsyncAwait>d__0'::'<ms>5__1'
432
438
IL_004e: brfalse.s IL_00b9 [<--- first kind of branch to skip]
439
+
440
+ For the Release configuration, we have this instead (appearing just after
441
+ a generated try/catch, so we'll check for the preceding leave.s instruction,
442
+ since the pattern is otherwise so simple and short.
443
+
444
+ if (generatedLocalVariable == null) [<--- we want to skip this]
445
+ {
446
+ goto IL_0075;
447
+ }
448
+
449
+ IL_0037: leave.s IL_0039
450
+ IL_0039: ldloc.1
451
+ IL_003a: brfalse.s IL_0098
433
452
*/
434
453
if ( currentIndex >= 2 &&
435
454
instructions [ currentIndex - 2 ] . OpCode == OpCodes . Ldarg_0 &&
436
455
instructions [ currentIndex - 1 ] . OpCode == OpCodes . Ldfld &&
437
456
instructions [ currentIndex - 1 ] . Operand is FieldReference disposableRef &&
438
- disposableRef . Name . StartsWith ( "<" ) && disposableRef . Name . Contains ( ">5__" ) )
457
+ disposableRef . Name . StartsWith ( "<" ) && disposableRef . Name . Contains ( ">5__" ) &&
458
+ instructions [ currentIndex ] . OpCode == OpCodes . Brfalse_S )
459
+ {
460
+ return true ;
461
+ }
462
+ else if ( currentIndex >= 2 &&
463
+ instructions [ currentIndex - 2 ] . OpCode == OpCodes . Leave_S &&
464
+ instructions [ currentIndex - 1 ] . OpCode == OpCodes . Ldloc_1 &&
465
+ instructions [ currentIndex ] . OpCode == OpCodes . Brfalse_S )
439
466
{
440
467
return true ;
441
468
}
442
469
/*
443
470
The second and third kinds of branches we want to skip both appear in a
444
471
compiler-generated block that checks if an exception has been thrown by a
445
- task and needs to be re-thrown.
472
+ task and needs to be re-thrown. This pattern, too, differs between
473
+ Debug and Release configurations. In a Debug configuration, we have:
446
474
447
475
obj2 = <>s__2;
448
476
if (obj2 != null) [<--- second kind of branch to skip]
@@ -465,23 +493,84 @@ task and needs to be re-thrown.
465
493
IL_00c9: stloc.s 5
466
494
IL_00cb: ldloc.s 5
467
495
IL_00cd: brtrue.s IL_00d1 [<--- third branch to skip]
496
+
497
+ Meanwhile, in a Release configuration, we have the same pattern, but
498
+ the naming convention used for the compiler-generated field is
499
+ different (<>7__ instead of <>s__, i.e., GeneratedNameKind.ReusableHoistedLocalField
500
+ instead of GeneratedNameKind.HoistedSynthesizedLocalField), with
501
+ different local variables in use.
502
+
503
+ IL_0098: ldarg.0
504
+ IL_0099: ldfld object AwaitUsing/'<AsyncAwait>d__0'::'<>7__wrap1'
505
+ IL_009e: stloc.2
506
+ IL_009f: ldloc.2
507
+ IL_00a0: brfalse.s IL_00b7
508
+ IL_00a2: ldloc.2
509
+ IL_00a3: isinst [System.Private.CoreLib]System.Exception
510
+ IL_00a8: stloc.s 5
511
+ IL_00aa: ldloc.s 5
512
+ IL_00ac: brtrue.s IL_00af
513
+
514
+ And one more note: On SharpLab, I see that the stloc.s/ldloc.s pairs
515
+ can also be optimized down to dup instructions, so we'll check for
516
+ that pattern, as well.
468
517
*/
469
518
else if ( currentIndex >= 3 &&
470
519
instructions [ currentIndex - 3 ] . OpCode == OpCodes . Ldfld &&
471
- instructions [ currentIndex - 3 ] . Operand is FieldReference exceptionRef && exceptionRef . Name . Contains ( "<>s__" ) &&
520
+ instructions [ currentIndex - 3 ] . Operand is FieldReference debugExceptionRef && debugExceptionRef . Name . Contains ( "<>s__" ) &&
472
521
instructions [ currentIndex - 2 ] . OpCode == OpCodes . Stloc_1 &&
473
- instructions [ currentIndex - 1 ] . OpCode == OpCodes . Ldloc_1 )
522
+ instructions [ currentIndex - 1 ] . OpCode == OpCodes . Ldloc_1 &&
523
+ instructions [ currentIndex ] . OpCode == OpCodes . Brfalse_S )
474
524
{
475
525
return true ;
476
526
}
477
527
else if ( currentIndex >= 4 &&
478
528
instructions [ currentIndex - 4 ] . OpCode == OpCodes . Ldloc_1 &&
479
529
instructions [ currentIndex - 3 ] . OpCode == OpCodes . Isinst &&
480
- instructions [ currentIndex - 3 ] . Operand is TypeReference exceptionType && exceptionType . FullName == "System.Exception" &&
530
+ instructions [ currentIndex - 3 ] . Operand is TypeReference debugExceptionType && debugExceptionType . FullName == "System.Exception" &&
481
531
instructions [ currentIndex - 2 ] . OpCode == OpCodes . Stloc_S &&
482
- instructions [ currentIndex - 2 ] . Operand is VariableReference variableStore && variableStore . Index == 5 &&
532
+ instructions [ currentIndex - 2 ] . Operand is VariableReference debugVariableStore && debugVariableStore . Index == 5 &&
483
533
instructions [ currentIndex - 1 ] . OpCode == OpCodes . Ldloc_S &&
484
- instructions [ currentIndex - 1 ] . Operand is VariableReference variableLoad && variableLoad . Index == 5 )
534
+ instructions [ currentIndex - 1 ] . Operand is VariableReference debugVariableLoad && debugVariableLoad . Index == 5 &&
535
+ instructions [ currentIndex ] . OpCode == OpCodes . Brtrue_S )
536
+ {
537
+ return true ;
538
+ }
539
+ else if ( currentIndex >= 3 &&
540
+ instructions [ currentIndex - 3 ] . OpCode == OpCodes . Ldfld &&
541
+ instructions [ currentIndex - 3 ] . Operand is FieldReference releaseExceptionRef && releaseExceptionRef . Name . Contains ( "<>7__" ) &&
542
+ instructions [ currentIndex - 2 ] . OpCode == OpCodes . Stloc_2 &&
543
+ instructions [ currentIndex - 1 ] . OpCode == OpCodes . Ldloc_2 &&
544
+ instructions [ currentIndex ] . OpCode == OpCodes . Brfalse_S )
545
+ {
546
+ return true ;
547
+ }
548
+ else if ( currentIndex >= 2 &&
549
+ instructions [ currentIndex - 2 ] . OpCode == OpCodes . Ldfld &&
550
+ instructions [ currentIndex - 2 ] . Operand is FieldReference releaseWithDupExceptionRef && releaseWithDupExceptionRef . Name . Contains ( "<>7__" ) &&
551
+ instructions [ currentIndex - 1 ] . OpCode == OpCodes . Dup &&
552
+ instructions [ currentIndex ] . OpCode == OpCodes . Brfalse_S )
553
+ {
554
+ return true ;
555
+ }
556
+ else if ( currentIndex >= 4 &&
557
+ instructions [ currentIndex - 4 ] . OpCode == OpCodes . Ldloc_2 &&
558
+ instructions [ currentIndex - 3 ] . OpCode == OpCodes . Isinst &&
559
+ instructions [ currentIndex - 3 ] . Operand is TypeReference releaseExceptionType && releaseExceptionType . FullName == "System.Exception" &&
560
+ instructions [ currentIndex - 2 ] . OpCode == OpCodes . Stloc_S &&
561
+ instructions [ currentIndex - 2 ] . Operand is VariableReference releaseVariableStore && releaseVariableStore . Index == 5 &&
562
+ instructions [ currentIndex - 1 ] . OpCode == OpCodes . Ldloc_S &&
563
+ instructions [ currentIndex - 1 ] . Operand is VariableReference releaseVariableLoad && releaseVariableLoad . Index == 5 &&
564
+ instructions [ currentIndex ] . OpCode == OpCodes . Brtrue_S )
565
+ {
566
+ return true ;
567
+ }
568
+ else if ( currentIndex >= 3 &&
569
+ instructions [ currentIndex - 3 ] . OpCode == OpCodes . Ldloc_2 &&
570
+ instructions [ currentIndex - 2 ] . OpCode == OpCodes . Isinst &&
571
+ instructions [ currentIndex - 2 ] . Operand is TypeReference releaseWithDupExceptionType && releaseWithDupExceptionType . FullName == "System.Exception" &&
572
+ instructions [ currentIndex - 1 ] . OpCode == OpCodes . Dup &&
573
+ instructions [ currentIndex ] . OpCode == OpCodes . Brtrue_S )
485
574
{
486
575
return true ;
487
576
}
0 commit comments