Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot set breakpoint in Object Initializer #27184

Open
Ciwan1859 opened this issue May 26, 2018 · 23 comments
Open

Cannot set breakpoint in Object Initializer #27184

Ciwan1859 opened this issue May 26, 2018 · 23 comments

Comments

@Ciwan1859
Copy link

Version Used:

VS 15.7.2

Steps to Reproduce:

I've captured what is happening in a screen-cast.

https://www.screencast.com/t/aFvHcdnkYx

Expected Behavior:

I should be able to hit that single if condition, and not the whole block.

Actual Behavior:

The whole block gets covered by debug break-point.

@jcouv
Copy link
Member

jcouv commented May 29, 2018

It's not currently possible to set breakpoints in expressions. Relates to #22016

@gafter
Copy link
Member

gafter commented Apr 6, 2020

@Ciwan1859 the original issue's link is dead. Please include a complete description of the issue.

@gafter
Copy link
Member

gafter commented Apr 22, 2020

See #42711 for the related switch expression work for reference when implementing this.

@gafter gafter modified the milestones: Compiler.Net5, Backlog Apr 23, 2020
@gafter
Copy link
Member

gafter commented Apr 23, 2020

I'm not sure I understand the request, and I'm not confident this would be useful as I imagine it is being requested.

If the idea is being able to set breakpoints on the individual initializers of an object initializer, why would you want that? It is straight-line code. To the extent that it is not straight-line code (e.g. ?: is used), that debugging difficulty is not specific to object initializers rather it is specific to the conditional operators.

If the idea is to improve stack traces when an exception is thrown in the initializer, I'm not sure the code degradation required to support that is worth it (we have to spill the stack at every sequence point).

I'm moving this to the back burner so we can investigate the options and possible benefits.

@JwanKhalaf
Copy link

@gafter that is fine, thank you. I've lost the original gif that I had. I'm really sorry about the dead-link :(

@jnm2
Copy link
Contributor

jnm2 commented Apr 25, 2020

@gafter

I'm not sure I understand the request, and I'm not confident this would be useful as I imagine it is being requested.

For me it's about one thing: being able to step in to calls both on the RHS and LHS of the assignments. That's very hard to do if you can't step over to the right line before stepping in.

@jcouv
Copy link
Member

jcouv commented Apr 25, 2020

@jnm2 Do you know that the debugger allows to "step into a specific method"? At a given breakpoint, you can invoke that command, it will display all the methods that could be invoked from there (different parts of the expression) and you can choose which one to step into.
Seems like it would mostly solve your scenario.

According to my custom bindings, the command is Debug.StepIntoSpecific.

@jnm2
Copy link
Contributor

jnm2 commented Apr 25, 2020

@jcouv Yes. The 'Step into' submenu doesn't always appear when I expect it to, but I do use it regularly. Next time I run into an object initializer I'll look for this.

@jnm2
Copy link
Contributor

jnm2 commented Apr 26, 2020

@gafter @jcouv Here's another scenario I've just remembered by running into it unexpectedly: If an exception is thrown, there is no way to see which line of the object creation expression was responsible. I have to search each line and try to see which ones could potentially have thrown the exception.

@jnm2
Copy link
Contributor

jnm2 commented Apr 26, 2020

I think this exception scenario gets to the root of most of what I've heard from folks that keeps them from using multiline expressions in general, including chaining of && and || and pattern matching.

@gafter
Copy link
Member

gafter commented Apr 27, 2020

The quality of stack traces is not necessarily the same as where you can set breakpoints, which is not necessarily the same as where the debugger stops as you're single-stepping. Today we combine the three at statement boundaries, but that need not necessarily be the case in the future.

The difficulty of stack trace quality occurs for any complex expression with multiple parts. It is not specific to object initializers. For example, the same issue occurs with chained calls written in a fluent style. Helping with the quality of stack traces needn't be done by making the debugger single-step through each call.

Since there is no description of the original issue here, it is hard to tell what is being requested.

@tmat
Copy link
Member

tmat commented Apr 27, 2020

+1 for what Neal says here:

The difficulty of stack trace quality occurs for any complex expression with multiple parts. It is not specific to object initializers. For example, the same issue occurs with chained calls written in a fluent style. Helping with the quality of stack traces needn't be done by making the debugger single-step through each call.

@jnm2 In what scenarios is it a problem to not see from the call stack exactly what method call caused the exception?

@Suchiman
Copy link
Contributor

Suchiman commented Apr 27, 2020

@tmat a simple example would just be:

new T
{
    A = a.B.C.D,
    B = e.F.G.D,
    C = h.H,
    L = c.D.E
}; // NullReferenceException on this line isn't helpful... at all

EDIT: And when you're working with large class structures generated by XSD.exe and you fall into the trap of taking VS suggestion to "simplify" that code (by going from statements to object initializer), then you're in debugging hell once stacktraces hit production. Just to clarify, this can easily exceed 20 lines.

Being able to step through each assignment would be nice to have but i'd accept only getting better stacktraces as a compromise.

@tmat
Copy link
Member

tmat commented Apr 27, 2020

We would not change how we generate sequence points in production (release) builds as that would regress performance. Besides the stack traces in release builds will be off anyways since most of your properties in your example above would be inlined.

@tmat
Copy link
Member

tmat commented Apr 27, 2020

@Suchiman

Being able to step through each assignment would be nice to have but i'd accept only getting better stacktraces as a compromise.

As @gafter pointed out those are two different things. Why would it be nice to step on each assignment (in debug builds)? They all happen unconditionally. So what new information would you get from one step to another?

@Suchiman
Copy link
Contributor

Suchiman commented Apr 27, 2020

@tmat The following also happens unconditionally yet i can step over each of them. It would be nice to have symmetry but i also just step through the code until it blows up, e.g. until it hits the line that causes the NullReferenceException.

A();
B();
C();

Bonus points if i could inspect the class that is being constructed by hovering the A = part.

EDIT: Also i prefer the step over functionality over the step into specific, which, to be honest, i know exists, just never think of it when stepping through the code... Maybe i can get used to it with the keyboard shortcut (didn't know there was one, for me it was always in the right click context menu which just interrupted my debugging workflow so i usually went step into, step out, until i got where i want to be)

@Suchiman
Copy link
Contributor

@tmat

We would not change how we generate sequence points in production (release) builds as that would regress performance.

I cannot confirm this

using System;
using System.Runtime.CompilerServices;

namespace ConsoleApp80
{
    class Program
    {
        static void Main(string[] args)
        {
            var a = new A { B = new B() };
            try
            {
                Test(a);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            try
            {
                Test2(a);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        static Test Test(A a)
        {
            return new Test()
            {
                A = a,
                B = a.B,
                C = a.B.C,
                Value = a.B.C.Value,
            };
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static Test Test2(A a)
        {
            Test test = new Test();
            test.A = a;
            test.B = a.B;
            test.C = a.B.C;
            test.Value = a.B.C.Value;
            return test;
        }
    }

    class A
    {
        public B B { get; set; }
    }

    class B
    {
        public C C { get; set; }
    }

    class C
    {
        public string Value { get; set; }
    }

    class Test
    {
        public A A { get; set; }
        public B B { get; set; }
        public C C { get; set; }
        public string Value { get; set; }
    }
}
Results in identical assembly being generated:

Object Initializer

; Assembly listing for method Program:Test(A):Test
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 arg0         [V00,T01] (  4,  4   )     ref  ->  rsi         class-hnd
;  V01 OutArgs      [V01    ] (  1,  1   )  lclBlk (32) [rsp+0x00]   "OutgoingArgSpace"
;  V02 tmp1         [V02,T00] (  6, 12   )     ref  ->  rdi         class-hnd exact "NewObj constructor temp"
;  V03 tmp2         [V03,T03] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;  V04 tmp3         [V04,T04] (  2,  4   )     ref  ->  rsi         class-hnd "Inlining Arg"
;  V05 tmp4         [V05,T05] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;* V06 tmp5         [V06,T09] (  0,  0   )     ref  ->  zero-ref    class-hnd "Inlining Arg"
;  V07 tmp6         [V07,T06] (  2,  4   )     ref  ->  rsi         class-hnd "Inlining Arg"
;  V08 tmp7         [V08,T07] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;  V09 cse0         [V09,T02] (  3,  3   )     ref  ->  rsi         "CSE - aggressive"
;  V10 cse1         [V10,T08] (  3,  3   )     ref  ->  rsi         "CSE - aggressive"
;
; Lcl frame size = 40

G_M206_IG01:
       push     rdi
       push     rsi
       sub      rsp, 40
       mov      rsi, rcx
                                                ;; bbWeight=1    PerfScore 2.50
G_M206_IG02:
       mov      rcx, 0xD1FFAB1E
       call     CORINFO_HELP_NEWSFAST
       mov      rdi, rax
       lea      rcx, bword ptr [rdi+8]
       mov      rdx, rsi
       call     CORINFO_HELP_ASSIGN_REF
       mov      rsi, gword ptr [rsi+8]
       mov      rdx, rsi
       lea      rcx, bword ptr [rdi+16]
       call     CORINFO_HELP_ASSIGN_REF
       mov      rsi, gword ptr [rsi+8]
       mov      rdx, rsi
       lea      rcx, bword ptr [rdi+24]
       call     CORINFO_HELP_ASSIGN_REF
       mov      rdx, gword ptr [rsi+8]
       lea      rcx, bword ptr [rdi+32]
       call     CORINFO_HELP_ASSIGN_REF
       mov      rax, rdi
                                                ;; bbWeight=1    PerfScore 14.50
G_M206_IG03:
       add      rsp, 40
       pop      rsi
       pop      rdi
       ret
                                                ;; bbWeight=1    PerfScore 2.25

; Total bytes of code 94, prolog size 6, PerfScore 28.65, (MethodHash=d51eff31) for method Program:Test(A):Test
; ============================================================

Assignments

; Assembly listing for method Program:Test2(A):Test
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 arg0         [V00,T01] (  4,  4   )     ref  ->  rsi         class-hnd
;  V01 OutArgs      [V01    ] (  1,  1   )  lclBlk (32) [rsp+0x00]   "OutgoingArgSpace"
;  V02 tmp1         [V02,T00] (  6, 12   )     ref  ->  rdi         class-hnd exact "NewObj constructor temp"
;  V03 tmp2         [V03,T03] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;  V04 tmp3         [V04,T04] (  2,  4   )     ref  ->  rsi         class-hnd "Inlining Arg"
;  V05 tmp4         [V05,T05] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;* V06 tmp5         [V06,T09] (  0,  0   )     ref  ->  zero-ref    class-hnd "Inlining Arg"
;  V07 tmp6         [V07,T06] (  2,  4   )     ref  ->  rsi         class-hnd "Inlining Arg"
;  V08 tmp7         [V08,T07] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;  V09 cse0         [V09,T02] (  3,  3   )     ref  ->  rsi         "CSE - aggressive"
;  V10 cse1         [V10,T08] (  3,  3   )     ref  ->  rsi         "CSE - aggressive"
;
; Lcl frame size = 40

G_M42780_IG01:
       push     rdi
       push     rsi
       sub      rsp, 40
       mov      rsi, rcx
                                                ;; bbWeight=1    PerfScore 2.50
G_M42780_IG02:
       mov      rcx, 0xD1FFAB1E
       call     CORINFO_HELP_NEWSFAST
       mov      rdi, rax
       lea      rcx, bword ptr [rdi+8]
       mov      rdx, rsi
       call     CORINFO_HELP_ASSIGN_REF
       mov      rsi, gword ptr [rsi+8]
       mov      rdx, rsi
       lea      rcx, bword ptr [rdi+16]
       call     CORINFO_HELP_ASSIGN_REF
       mov      rsi, gword ptr [rsi+8]
       mov      rdx, rsi
       lea      rcx, bword ptr [rdi+24]
       call     CORINFO_HELP_ASSIGN_REF
       mov      rdx, gword ptr [rsi+8]
       lea      rcx, bword ptr [rdi+32]
       call     CORINFO_HELP_ASSIGN_REF
       mov      rax, rdi
                                                ;; bbWeight=1    PerfScore 14.50
G_M42780_IG03:
       add      rsp, 40
       pop      rsi
       pop      rdi
       ret
                                                ;; bbWeight=1    PerfScore 2.25

; Total bytes of code 94, prolog size 6, PerfScore 28.65, (MethodHash=cd2b58e3) for method Program:Test2(A):Test
; ============================================================

Unfortunately though, it seems that the improved JIT optimizations in .NET Core lead to the line numbers being scrambled for the assignments too. When you suppress JIT optimizations with
set COMPlus_JitMinopts=1 then you still get identical assembly for both methods but correct line numbers for the assignments.

Object Initializer

; Assembly listing for method Program:Test(A):Test
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; MinOpts code
; rbp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 arg0         [V00    ] (  1,  1   )     ref  ->  [rbp+0x10]   class-hnd
;  V01 OutArgs      [V01    ] (  1,  1   )  lclBlk (32) [rsp+0x00]   "OutgoingArgSpace"
;  V02 tmp1         [V02    ] (  1,  1   )     ref  ->  [rbp-0x08]   must-init class-hnd exact "NewObj constructor temp"
;  V03 tmp2         [V03    ] (  1,  1   )     ref  ->  [rbp-0x10]   must-init "argument with side effect"
;  V04 tmp3         [V04    ] (  1,  1   )     ref  ->  [rbp-0x18]   must-init "argument with side effect"
;  V05 tmp4         [V05    ] (  1,  1   )     ref  ->  [rbp-0x20]   must-init "argument with side effect"
;
; Lcl frame size = 64

G_M206_IG01:
       push     rbp
       sub      rsp, 64
       lea      rbp, [rsp+40H]
       vxorps   xmm4, xmm4
       vmovdqa  xmmword ptr [rbp-20H], xmm4
       vmovdqa  xmmword ptr [rbp-10H], xmm4
       mov      gword ptr [rbp+10H], rcx
                                                ;; bbWeight=1    PerfScore 5.08
G_M206_IG02:
       mov      rcx, 0xD1FFAB1E
       call     CORINFO_HELP_NEWSFAST
       mov      gword ptr [rbp-08H], rax
       mov      rcx, gword ptr [rbp-08H]
       call     Test:.ctor():this
       mov      rcx, gword ptr [rbp-08H]
       mov      rdx, gword ptr [rbp+10H]
       cmp      dword ptr [rcx], ecx
       call     Test:set_A(A):this
       mov      rcx, gword ptr [rbp+10H]
       cmp      dword ptr [rcx], ecx
       call     A:get_B():B:this
       mov      gword ptr [rbp-10H], rax
       mov      rdx, gword ptr [rbp-10H]
       mov      rcx, gword ptr [rbp-08H]
       cmp      dword ptr [rcx], ecx
       call     Test:set_B(B):this
       mov      rcx, gword ptr [rbp+10H]
       cmp      dword ptr [rcx], ecx
       call     A:get_B():B:this
       mov      rcx, rax
       cmp      dword ptr [rcx], ecx
       call     B:get_C():C:this
       mov      gword ptr [rbp-18H], rax
       mov      rdx, gword ptr [rbp-18H]
       mov      rcx, gword ptr [rbp-08H]
       cmp      dword ptr [rcx], ecx
       call     Test:set_C(C):this
       mov      rcx, gword ptr [rbp+10H]
       cmp      dword ptr [rcx], ecx
       call     A:get_B():B:this
       mov      rcx, rax
       cmp      dword ptr [rcx], ecx
       call     B:get_C():C:this
       mov      rcx, rax
       cmp      dword ptr [rcx], ecx
       call     C:get_Value():String:this
       mov      gword ptr [rbp-20H], rax
       mov      rdx, gword ptr [rbp-20H]
       mov      rcx, gword ptr [rbp-08H]
       cmp      dword ptr [rcx], ecx
       call     Test:set_Value(String):this
       mov      rax, gword ptr [rbp-08H]
                                                ;; bbWeight=1    PerfScore 50.00
G_M206_IG03:
       lea      rsp, [rbp]
       pop      rbp
       ret
                                                ;; bbWeight=1    PerfScore 2.00

; Total bytes of code 201, prolog size 24, PerfScore 77.48, (MethodHash=d51eff31) for method Program:Test(A):Test
; ============================================================

Assignments

; Assembly listing for method Program:Test2(A):Test
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; MinOpts code
; rbp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 arg0         [V00    ] (  1,  1   )     ref  ->  [rbp+0x10]   class-hnd
;  V01 OutArgs      [V01    ] (  1,  1   )  lclBlk (32) [rsp+0x00]   "OutgoingArgSpace"
;  V02 tmp1         [V02    ] (  1,  1   )     ref  ->  [rbp-0x08]   must-init class-hnd exact "NewObj constructor temp"
;  V03 tmp2         [V03    ] (  1,  1   )     ref  ->  [rbp-0x10]   must-init "argument with side effect"
;  V04 tmp3         [V04    ] (  1,  1   )     ref  ->  [rbp-0x18]   must-init "argument with side effect"
;  V05 tmp4         [V05    ] (  1,  1   )     ref  ->  [rbp-0x20]   must-init "argument with side effect"
;
; Lcl frame size = 64

G_M42780_IG01:
       push     rbp
       sub      rsp, 64
       lea      rbp, [rsp+40H]
       vxorps   xmm4, xmm4
       vmovdqa  xmmword ptr [rbp-20H], xmm4
       vmovdqa  xmmword ptr [rbp-10H], xmm4
       mov      gword ptr [rbp+10H], rcx
                                                ;; bbWeight=1    PerfScore 5.08
G_M42780_IG02:
       mov      rcx, 0xD1FFAB1E
       call     CORINFO_HELP_NEWSFAST
       mov      gword ptr [rbp-08H], rax
       mov      rcx, gword ptr [rbp-08H]
       call     Test:.ctor():this
       mov      rcx, gword ptr [rbp-08H]
       mov      rdx, gword ptr [rbp+10H]
       cmp      dword ptr [rcx], ecx
       call     Test:set_A(A):this
       mov      rcx, gword ptr [rbp+10H]
       cmp      dword ptr [rcx], ecx
       call     A:get_B():B:this
       mov      gword ptr [rbp-10H], rax
       mov      rdx, gword ptr [rbp-10H]
       mov      rcx, gword ptr [rbp-08H]
       cmp      dword ptr [rcx], ecx
       call     Test:set_B(B):this
       mov      rcx, gword ptr [rbp+10H]
       cmp      dword ptr [rcx], ecx
       call     A:get_B():B:this
       mov      rcx, rax
       cmp      dword ptr [rcx], ecx
       call     B:get_C():C:this
       mov      gword ptr [rbp-18H], rax
       mov      rdx, gword ptr [rbp-18H]
       mov      rcx, gword ptr [rbp-08H]
       cmp      dword ptr [rcx], ecx
       call     Test:set_C(C):this
       mov      rcx, gword ptr [rbp+10H]
       cmp      dword ptr [rcx], ecx
       call     A:get_B():B:this
       mov      rcx, rax
       cmp      dword ptr [rcx], ecx
       call     B:get_C():C:this
       mov      rcx, rax
       cmp      dword ptr [rcx], ecx
       call     C:get_Value():String:this
       mov      gword ptr [rbp-20H], rax
       mov      rdx, gword ptr [rbp-20H]
       mov      rcx, gword ptr [rbp-08H]
       cmp      dword ptr [rcx], ecx
       call     Test:set_Value(String):this
       mov      rax, gword ptr [rbp-08H]
                                                ;; bbWeight=1    PerfScore 50.00
G_M42780_IG03:
       lea      rsp, [rbp]
       pop      rbp
       ret
                                                ;; bbWeight=1    PerfScore 2.00

; Total bytes of code 201, prolog size 24, PerfScore 77.48, (MethodHash=cd2b58e3) for method Program:Test2(A):Test
; ============================================================

@tmat
Copy link
Member

tmat commented Apr 27, 2020

@Suchiman I can definitely confirm this. You are checking the simple case. There are many cases that would be affected. We would need to spill the stack.
Consider following expression:

F(G(), new C { X = Z(), Y = Z() }, H());

@tmat
Copy link
Member

tmat commented Apr 27, 2020

The following also happens unconditionally yet i can step over each of them. It would be nice to have symmetry

These are statements and don't produce values consumed by other expressions. I don't think we want a "symmetry". I don't want to be stepping onto every method call in expressions like A() + B() * C().

@Suchiman
Copy link
Contributor

Suchiman commented Apr 27, 2020

@tmat
The example i manufactured with your sample (see below) actually generates worse code for the object initializer but i get your point.

Also i kinda see the argument against symmetry but the distinction seems rather arbitrarily based on a technical limitation than a logical one. One could argue for everything that could also be a valid ExpressionStatement to be steppable and to reduce stepping noise, limit that to expressions that are wrapped, e.g. so you don't single step multiple times on the same line. Alternatively there's always run to cursor if there are too many steps inbetween. E.g. #21781 is another ticket that proposes more granular stepping which i'm a huge fan of (at least skim the gif's in that one 😉)

using System;
using System.Runtime.CompilerServices;

namespace ConsoleApp80
{
    class Program
    {
        static void Main(string[] args)
        {
            var a = new A { B = new B() };
            try
            {
                Test(a);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            try
            {
                Test2(a);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        static string Test(A a)
        {
            return F(G(), new Test { B = Z(a), C = X(a) }, H());
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static string Test2(A a)
        {
            string v = G();
            Test test = new Test();
            test.B = Z(a);
            test.C = X(a);
            return F(v, test, H());
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static string H()
        {
            return "World";
        }

        private static string G()
        {
            return "Hello";
        }

        private static C X(A a)
        {
            return a.B.C;
        }

        private static B Z(A a)
        {
            return a.B;
        }

        private static string F(string v, Test a, string p)
        {
            return $"{v} {a.B} {p}";
        }
    }

    class A
    {
        public B B { get; set; }
    }

    class B
    {
        public C C { get; set; }
    }

    class C
    {
        public string Value { get; set; }
    }

    class Test
    {
        public A A { get; set; }
        public B B { get; set; }
        public C C { get; set; }
        public string Value { get; set; }
    }
}

Object Initializer

; Assembly listing for method Program:Test(A):String
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 arg0         [V00,T02] (  3,  3   )     ref  ->  rsi         class-hnd
;  V01 OutArgs      [V01    ] (  1,  1   )  lclBlk (32) [rsp+0x00]   "OutgoingArgSpace"
;  V02 tmp1         [V02,T01] (  4,  8   )     ref  ->  rdi         class-hnd exact "NewObj constructor temp"
;  V03 tmp2         [V03,T03] (  2,  4   )     ref  ->  rbx         class-hnd exact "impAppendStmt"
;  V04 tmp3         [V04,T04] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;  V05 tmp4         [V05,T05] (  2,  4   )     ref  ->  rsi         class-hnd "Inlining Arg"
;  V06 tmp5         [V06,T06] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;  V07 tmp6         [V07,T07] (  2,  4   )     ref  ->  rsi         class-hnd "Inlining Arg"
;  V08 tmp7         [V08,T08] (  2,  4   )     ref  ->  rdi         class-hnd "Inlining Arg"
;* V09 tmp8         [V09    ] (  0,  0   )  struct (32) zero-ref    "NewObj constructor temp"
;  V10 tmp9         [V10,T11] (  2,  2   )     ref  ->  rbx         V09._arg0(offs=0x00) P-INDEP "field V09._arg0 (fldOffset=0x0)"
;  V11 tmp10        [V11,T12] (  2,  2   )     ref  ->  rdi         V09._arg1(offs=0x08) P-INDEP "field V09._arg1 (fldOffset=0x8)"
;  V12 tmp11        [V12,T13] (  2,  2   )     ref  ->  rsi         V09._arg2(offs=0x10) P-INDEP "field V09._arg2 (fldOffset=0x10)"
;  V13 tmp12        [V13,T14] (  2,  2   )     ref  ->   r8         V09._args(offs=0x18) P-INDEP "field V09._args (fldOffset=0x18)"
;  V14 tmp13        [V14    ] (  2,  4   )  struct (32) [rsp+0x20]   do-not-enreg[XSB] must-init addr-exposed "by-value struct argument"
;  V15 tmp14        [V15,T00] (  5, 10   )   byref  ->  rcx         stack-byref "BlockOp address local"
;  V16 tmp15        [V16,T09] (  2,  4   )     ref  ->  rdx         "argument with side effect"
;  V17 cse0         [V17,T10] (  3,  3   )     ref  ->  rsi         "CSE - aggressive"
;
; Lcl frame size = 64

G_M653_IG01:
       push     rdi
       push     rsi
       push     rbx
       sub      rsp, 64
       vxorps   xmm4, xmm4
       vmovdqa  xmmword ptr [rsp+20H], xmm4
       vmovdqa  xmmword ptr [rsp+30H], xmm4
       mov      rsi, rcx
                                                ;; bbWeight=1    PerfScore 5.83
G_M653_IG02:
       mov      rcx, 0xD1FFAB1E
       call     CORINFO_HELP_NEWSFAST
       mov      rdi, rax
       mov      rdx, 0xD1FFAB1E
       mov      rbx, gword ptr [rdx]
       mov      rsi, gword ptr [rsi+8]
       mov      rdx, rsi
       lea      rcx, bword ptr [rdi+16]
       call     CORINFO_HELP_ASSIGN_REF
       mov      rdx, gword ptr [rsi+8]
       lea      rcx, bword ptr [rdi+24]
       call     CORINFO_HELP_ASSIGN_REF
       call     Program:H():String
       mov      rsi, rax
       mov      rdi, gword ptr [rdi+16]
       mov      rcx, 0xD1FFAB1E
       mov      edx, 377
       call     CORINFO_HELP_GETSHARED_NONGCSTATIC_BASE
       mov      r8, 0xD1FFAB1E
       mov      r8, gword ptr [r8]
       mov      rdx, 0xD1FFAB1E
       mov      rdx, gword ptr [rdx]
       lea      rcx, bword ptr [rsp+20H]
       mov      gword ptr [rcx], rbx
       mov      gword ptr [rcx+8], rdi
       mov      gword ptr [rcx+16], rsi
       mov      gword ptr [rcx+24], r8
       lea      r8, bword ptr [rsp+20H]
       xor      rcx, rcx
       call     String:FormatHelper(IFormatProvider,String,ParamsArray):String
       nop
                                                ;; bbWeight=1    PerfScore 26.75
G_M653_IG03:
       add      rsp, 64
       pop      rbx
       pop      rsi
       pop      rdi
       ret
                                                ;; bbWeight=1    PerfScore 2.75

; Total bytes of code 185, prolog size 23, PerfScore 54.13, (MethodHash=a712fd72) for method Program:Test(A):String
; ============================================================

Assignments

; Assembly listing for method Program:Test2(A):String
; Emitting BLENDED_CODE for X64 CPU with AVX - Windows
; optimized code
; rsp based frame
; partially interruptible
; Final local variable assignments
;
;  V00 arg0         [V00,T02] (  3,  3   )     ref  ->  rsi         class-hnd
;* V01 loc0         [V01    ] (  0,  0   )     ref  ->  zero-ref    class-hnd exact
;  V02 OutArgs      [V02    ] (  1,  1   )  lclBlk (32) [rsp+0x00]   "OutgoingArgSpace"
;  V03 tmp1         [V03,T01] (  4,  8   )     ref  ->  rdi         class-hnd exact "NewObj constructor temp"
;  V04 tmp2         [V04,T03] (  2,  4   )     ref  ->  rbx         class-hnd exact "impAppendStmt"
;  V05 tmp3         [V05,T04] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;  V06 tmp4         [V06,T05] (  2,  4   )     ref  ->  rsi         class-hnd "Inlining Arg"
;  V07 tmp5         [V07,T06] (  2,  4   )     ref  ->  rdx         class-hnd "Inlining Arg"
;  V08 tmp6         [V08,T07] (  2,  4   )     ref  ->  rax         class-hnd "Inlining Arg"
;  V09 tmp7         [V09,T08] (  2,  4   )     ref  ->   r8         class-hnd "Inlining Arg"
;* V10 tmp8         [V10    ] (  0,  0   )  struct (32) zero-ref    "NewObj constructor temp"
;  V11 tmp9         [V11,T11] (  2,  2   )     ref  ->  rbx         V10._arg0(offs=0x00) P-INDEP "field V10._arg0 (fldOffset=0x0)"
;  V12 tmp10        [V12,T12] (  2,  2   )     ref  ->   r8         V10._arg1(offs=0x08) P-INDEP "field V10._arg1 (fldOffset=0x8)"
;  V13 tmp11        [V13,T13] (  2,  2   )     ref  ->  rax         V10._arg2(offs=0x10) P-INDEP "field V10._arg2 (fldOffset=0x10)"
;  V14 tmp12        [V14,T14] (  2,  2   )     ref  ->  rdx         V10._args(offs=0x18) P-INDEP "field V10._args (fldOffset=0x18)"
;  V15 tmp13        [V15    ] (  2,  4   )  struct (32) [rsp+0x20]   do-not-enreg[XSB] must-init addr-exposed "by-value struct argument"
;  V16 tmp14        [V16,T00] (  5, 10   )   byref  ->   r9         stack-byref "BlockOp address local"
;  V17 tmp15        [V17,T09] (  2,  4   )     ref  ->  rcx         "argument with side effect"
;  V18 cse0         [V18,T10] (  3,  3   )     ref  ->  rsi         "CSE - aggressive"
;
; Lcl frame size = 64

G_M13791_IG01:
       push     rdi
       push     rsi
       push     rbx
       sub      rsp, 64
       vxorps   xmm4, xmm4
       vmovdqa  xmmword ptr [rsp+20H], xmm4
       vmovdqa  xmmword ptr [rsp+30H], xmm4
       mov      rsi, rcx
                                                ;; bbWeight=1    PerfScore 5.83
G_M13791_IG02:
       mov      rcx, 0xD1FFAB1E
       call     CORINFO_HELP_NEWSFAST
       mov      rdi, rax
       mov      rdx, 0xD1FFAB1E
       mov      rbx, gword ptr [rdx]
       mov      rsi, gword ptr [rsi+8]
       mov      rdx, rsi
       lea      rcx, bword ptr [rdi+16]
       call     CORINFO_HELP_ASSIGN_REF
       mov      rdx, gword ptr [rsi+8]
       lea      rcx, bword ptr [rdi+24]
       call     CORINFO_HELP_ASSIGN_REF
       call     Program:H():String
       mov      r8, gword ptr [rdi+16]
       mov      rdx, 0xD1FFAB1E
       mov      rdx, gword ptr [rdx]
       mov      rcx, 0xD1FFAB1E
       mov      rcx, gword ptr [rcx]
       lea      r9, bword ptr [rsp+20H]
       mov      gword ptr [r9], rbx
       mov      gword ptr [r9+8], r8
       mov      gword ptr [r9+16], rax
       mov      gword ptr [r9+24], rdx
       lea      r8, bword ptr [rsp+20H]
       mov      rdx, rcx
       xor      rcx, rcx
       call     String:FormatHelper(IFormatProvider,String,ParamsArray):String
       nop
                                                ;; bbWeight=1    PerfScore 25.25
G_M13791_IG03:
       add      rsp, 64
       pop      rbx
       pop      rsi
       pop      rdi
       ret
                                                ;; bbWeight=1    PerfScore 2.75

; Total bytes of code 165, prolog size 23, PerfScore 50.63, (MethodHash=ccf4ca20) for method Program:Test2(A):String
; ============================================================

@tmat
Copy link
Member

tmat commented Apr 27, 2020

Also i kinda see the argument against symmetry but the distinction seems rather arbitrarily based on a technical limitation than a logical one.

It's not arbitrary, but it is indeed based on technical limitations. It's a balance between multiple factors. We can and we do add sequence points into select expressions. We just did it for switch expression. But there are trade-offs. We need to consider all the contributing factors. Adding breakpoints on each method call would be very expensive, both from perf perspective and the effort to make features like Edit and Continue work. The benefit from such work is questionable. Maybe someone would appreciate stepping onto every single thing, but many would not.

@TahirAhmadov
Copy link

TahirAhmadov commented Aug 15, 2022

I opened a dupe because I couldn't find this issue. Here are the reasons why I recently found that I need this a lot:

  • I started using collection initializers;
  • Ditto for "existing property object modifications";
  • This means, sometimes I can have upwards of 100+ LOC of init blocks;
  • This is mainly for UI initialization;
  • At some point in the future, we're getting compound assignments, and those may need to be debugged, as they are more complicated than simple property assignments;

Currently, the shortcomings are:

  • I can't see where exactly the exception is thrown;
  • I can't quickly "skip forward" to a particular line, using a breakpoint or a "run to this line" feature;
    • Pressing F11 to "step-into" and "step-out" many times is not workable;
  • If I need to set a breakpoint on an assignment which contains a large init-block, the breakpoint spans the entire statement, which sometimes leads to a couple of pages of code to be a single breakpoint.

Regarding the concern that this would reduce performance significantly, I'm assuming this would only be in debug mode, right? Is it a valid concern, to worry about the performance of code built in debug mode? Also, can it be made an option, to "turn on" this detailed metadata, for those teams/projects where it's needed, not to negatively impact others?

@TahirAhmadov
Copy link

TahirAhmadov commented Aug 22, 2022

I'm not sure if it's related, but if you have an init block like this:

var x = new Foo
{
  Prop = await GetValue(),
  OtherProp = 123,
};

And you try to modify that init block to change what's assigned to OtherProp, it doesn't work because it treats the block as one statement, and modifying a statement containing await isn't supported. I was curious if splitting this statement into individual statements (similar to what is being discussed here) would allow this edit&continue scenario, as well?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

10 participants