Skip to content

Conversation

@EgorBo
Copy link
Member

@EgorBo EgorBo commented Mar 3, 2025

The goal is to support things like

BitConverter.GetBytes(val).AsSpan(0, 3).CopyTo(dst);

where we want to save n-bytes of a primitive into a span, today we can either use either ^ (allocates) or come up with something ugly/unsafe.

But looks like there more work to do in JIT to enable that. Currently, JIT gives up on saving the temp array into Span.

@ghost ghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Mar 3, 2025
@EgorBo
Copy link
Member Author

EgorBo commented Mar 3, 2025

@AndyAyersMS so after GetBytes is inlined, we get:

static void Write3Bytes(int val, Span<byte> dst)
{
    byte[] bytes = new byte[4];
    Unsafe.As<byte, int>(ref bytes[0]) = val;
    bytes.AsSpan(0, 3).CopyTo(dst);
}

JIT IR before 'Allocate Objects' phase:

------------ BB01 [0000] [000..015) -> BB03(1) (always), preds={} succs={BB03}

***** BB01 [0000]
STMT00000 ( 0x000[E-] ... 0x006 )
               [000003] DACXG------                         *  STORE_LCL_VAR ref    V05 tmp1
               [000002] --CXG------                         \--*  CALL help ref    CORINFO_HELP_NEWARR_1_VC
               [000001] H---------- arg0                       +--*  CNS_INT(h) long   0x7ffa32c34ae0 class ubyte[]
               [000000] ----------- arg1                       \--*  CNS_INT   long   4

***** BB01 [0000]
STMT00001 ( ??? ... ??? )
               [000005] DA---------                         *  STORE_LCL_VAR ref    V02 loc0
               [000004] -----------                         \--*  LCL_VAR   ref    V05 tmp1

***** BB01 [0000]
STMT00002 ( 0x007[E-] ... 0x014 )
               [000010] nA-XG------                         *  STOREIND  int
               [000008] ---XG------                         +--*  INDEX_ADDR byref ubyte[]
               [000006] -----------                         |  +--*  LCL_VAR   ref    V02 loc0
               [000007] -----------                         |  \--*  CNS_INT   int    0
               [000009] -----------                         \--*  LCL_VAR   int    V00 arg0

***** BB01 [0000]
STMT00007 ( INL01 @ 0x000[E-] ... ??? ) <- INLRT @ 0x015[E-]
               [000025] DA---------                         *  STORE_LCL_VAR struct<System.Span`1, 16> V06 tmp2
               [000024] -----------                         \--*  CNS_INT   int    0

------------ BB03 [0002] [015..016) -> BB08(1),BB04(0) (cond), preds={BB01} succs={BB04,BB08}

***** BB03 [0002]
STMT00009 ( INL02 @ 0x000[E-] ... ??? ) <- INL01 @ ??? <- INLRT @ 0x015[E-]
               [000034] -----------                         *  JTRUE     void
               [000033] -----------                         \--*  NE        int
               [000011] -----------                            +--*  LCL_VAR   ref    V02 loc0
               [000032] -----------                            \--*  CNS_INT   ref    null

------------ BB04 [0003] [015..016) -> BB05(1) (always), preds={BB03} succs={BB05}

------------ BB05 [0004] [015..016) -> BB06(1) (always), preds={BB04} succs={BB06}

------------ BB06 [0005] [015..016) -> BB07(1) (always), preds={BB05} succs={BB07}

***** BB06 [0005]
STMT00015 ( INL02 @ 0x009[E-] ... ??? ) <- INL01 @ ??? <- INLRT @ 0x015[E-]
               [000073] --C-G------                         *  CALL      void   System.ThrowHelper:ThrowArgumentOutOfRangeException()

------------ BB07 [0006] [015..016) -> BB02(1) (always), preds={BB06} succs={BB02}

***** BB07 [0006]
STMT00016 ( INL02 @ 0x00E[E-] ... ??? ) <- INL01 @ ??? <- INLRT @ 0x015[E-]
               [000076] DA---------                         *  STORE_LCL_VAR struct<System.Span`1, 16> V06 tmp2
               [000075] -----------                         \--*  CNS_INT   int    0

------------ BB08 [0007] [015..016) -> BB11(1) (always), preds={BB03} succs={BB11}

------------ BB11 [0010] [015..016) -> BB13(1),BB12(0) (cond), preds={BB08} succs={BB12,BB13}

***** BB11 [0010]
STMT00010 ( INL02 @ 0x043[E-] ... ??? ) <- INL01 @ ??? <- INLRT @ 0x015[E-]
               [000050] ---X-------                         *  JTRUE     void
               [000049] N--X-----U-                         \--*  LE        int
               [000045] -----------                            +--*  CNS_INT   long   3
               [000048] ---X-----U-                            \--*  CAST      long <- ulong <- uint
               [000047] ---X-------                               \--*  ARR_LENGTH int
               [000046] -----------                                  \--*  LCL_VAR   ref    V02 loc0

------------ BB12 [0011] [015..016) -> BB13(1) (always), preds={BB11} succs={BB13}

***** BB12 [0011]
STMT00014 ( INL02 @ 0x04E[E-] ... ??? ) <- INL01 @ ??? <- INLRT @ 0x015[E-]
               [000066] --C-G------                         *  CALL      void   System.ThrowHelper:ThrowArgumentOutOfRangeException()

------------ BB13 [0012] [015..016) -> BB02(1) (always), preds={BB11,BB12} succs={BB02}

***** BB13 [0012]
STMT00011 ( INL02 @ 0x053[E-] ... ??? ) <- INL01 @ ??? <- INLRT @ 0x015[E-]
               [000054] ---X-------                         *  NULLCHECK byte
               [000052] -----------                         \--*  LCL_VAR   ref    V02 loc0

***** BB13 [0012]
STMT00012 ( INL02 @ ??? ... ??? ) <- INL01 @ ??? <- INLRT @ 0x015[E-]
               [000061] sA-X-------                         *  STOREIND  byref
               [000060] -----------                         +--*  FIELD_ADDR byref  System.Span`1[ubyte]:_reference
               [000051] -----------                         |  \--*  LCL_ADDR  byref  V06 tmp2         [+0]
               [000059] ---XG------                         \--*  ADD       byref
               [000056] ---XG------                            +--*  INDEX_ADDR byref ubyte[]
               [000053] -----------                            |  +--*  LCL_VAR   ref    V02 loc0
               [000055] -----------                            |  \--*  CNS_INT   long   0
               [000058] -----------                            \--*  CNS_INT   long   0

***** BB13 [0012]
STMT00013 ( INL02 @ 0x066[E-] ... ??? ) <- INL01 @ ??? <- INLRT @ 0x015[E-]
               [000065] sA---------                         *  STOREIND  int
               [000064] -----------                         +--*  FIELD_ADDR byref  System.Span`1[ubyte]:_length
               [000062] -----------                         |  \--*  LCL_ADDR  byref  V06 tmp2         [+0]
               [000063] -----------                         \--*  CNS_INT   int    3

------------ BB02 [0013] [015..026) -> BB15(1) (always), preds={BB07,BB13} succs={BB15}

***** BB02 [0013]
STMT00004 ( 0x015[E-] ... ??? )
               [000030] DA---------                         *  STORE_LCL_VAR struct<System.Span`1, 16> V03 loc1
               [000028] -----------                         \--*  LCL_VAR   struct<System.Span`1, 16> V06 tmp2

***** BB02 [0013]
STMT00020 ( 0x01E[E-] ... ??? )
               [000098] DA---------                         *  STORE_LCL_VAR struct<System.Span`1, 16> V07 tmp3
               [000019] -----------                         \--*  LCL_VAR   struct<System.Span`1, 16> V01 arg1

------------ BB15 [0014] [01E..01F) -> BB17(0),BB16(1) (cond), preds={BB02} succs={BB16,BB17}

***** BB15 [0014]
STMT00017 ( INL03 @ 0x000[E-] ... ??? ) <- INLRT @ 0x01E[E-]
               [000085] ----G------                         *  JTRUE     void
               [000084] N---G----U-                         \--*  GT        int
               [000080] n----------                            +--*  IND       int
               [000079] -----------                            |  \--*  FIELD_ADDR byref  System.Span`1[ubyte]:_length
               [000078] -----------                            |     \--*  LCL_ADDR  byref  V03 loc1         [+0]
               [000083] n---G------                            \--*  IND       int
               [000082] -----------                               \--*  FIELD_ADDR byref  System.Span`1[ubyte]:_length
               [000081] -----------                                  \--*  LCL_ADDR  byref  V07 tmp3         [+0]

------------ BB16 [0015] [01E..???) -> BB19(1) (always), preds={BB15} succs={BB19}

***** BB16 [0015]
STMT00022 ( INL03 @ 0x00F[E-] ... ??? ) <- INLRT @ 0x01E[E-]
               [000109] DA---------                         *  STORE_LCL_VAR byref  V08 tmp4
               [000089] n----------                         \--*  IND       byref
               [000088] -----------                            \--*  FIELD_ADDR byref  System.Span`1[ubyte]:_reference
               [000087] -----------                               \--*  LCL_ADDR  byref  V07 tmp3         [+0]

***** BB16 [0015]
STMT00023 ( INL03 @ 0x00F[E-] ... ??? ) <- INLRT @ 0x01E[E-]
               [000110] DA---------                         *  STORE_LCL_VAR byref  V09 tmp5
               [000092] n----------                         \--*  IND       byref
               [000091] -----------                            \--*  FIELD_ADDR byref  System.Span`1[ubyte]:_reference
               [000090] -----------                               \--*  LCL_ADDR  byref  V03 loc1         [+0]

***** BB16 [0015]
STMT00024 ( INL03 @ 0x00F[E-] ... ??? ) <- INLRT @ 0x01E[E-]
               [000111] DA---------                         *  STORE_LCL_VAR long   V10 tmp6
               [000096] ---------U-                         \--*  CAST      long <- ulong <- uint
               [000095] n----------                            \--*  IND       int
               [000094] -----------                               \--*  FIELD_ADDR byref  System.Span`1[ubyte]:_length
               [000093] -----------                                  \--*  LCL_ADDR  byref  V03 loc1         [+0]

------------ BB19 [0018] [01E..01F) -> BB20(1) (always), preds={BB16} succs={BB20}

------------ BB20 [0019] [01E..01F) -> BB18(1) (always), preds={BB19} succs={BB18}

***** BB20 [0019]
STMT00021 ( INL04 @ 0x007[E-] ... ??? ) <- INL03 @ 0x00F[E-] <- INLRT @ 0x01E[E-]
               [000108] --C-G------                         *  CALL      void   System.SpanHelpers:Memmove(byref,byref,ulong)
               [000103] ----------- arg0                    +--*  LCL_VAR   byref  V08 tmp4
               [000104] ----------- arg1                    +--*  LCL_VAR   byref  V09 tmp5
               [000105] ----------- arg2                    \--*  LCL_VAR   long   V10 tmp6

------------ BB18 [0021] [01F..01F) -> BB14(1) (always), preds={BB20} succs={BB14}

------------ BB17 [0016] [01E..01F) -> BB14(1) (always), preds={BB15} succs={BB14}

***** BB17 [0016]
STMT00018 ( INL03 @ 0x029[E-] ... ??? ) <- INLRT @ 0x01E[E-]
               [000086] --C-G------                         *  CALL      void   System.ThrowHelper:ThrowArgumentException_DestinationTooShort()

------------ BB14 [0017] [026..027) (return), preds={BB17,BB18} succs={}

***** BB14 [0017]
STMT00006 ( 0x026[E-] ... ??? )
               [000021] -----------                         *  RETURN    void

-------------------------------------------------------------------------------------------------------------------



*************** Starting PHASE Allocate Objects
enabled, analyzing...
... V05 ... checking [000005]
... V02 ... checking [000008]
... V02 ... checking [000010]
V00 first escapes via [000009]
... V02 ... checking [000033]
... V02 ... checking [000047]
... V02 ... checking [000054]
V06 first escapes via [000051]
... V02 ... checking [000056]
... V02 ... checking [000059]
... V02 ... checking [000061]
V02 first escapes via [000053]
V01 first escapes via [000019]
V03 first escapes via [000078]
V07 first escapes via [000081]
... V08 ... checking [000108]
Named Intrinsic System.SpanHelpers.Memmove: Recognized
... V09 ... checking [000108]
Named Intrinsic System.SpanHelpers.Memmove: Recognized
... V10 ... checking [000108]
Named Intrinsic System.SpanHelpers.Memmove: Recognized

Computing escape closure

V02 causes V05 to escape
Allocating V05 on the heap: [escapes]

*************** Finishing PHASE Allocate Objects

Looks like saving array object into Span._reference is preventing us from optimizing it

@EgorBo EgorBo marked this pull request as ready for review March 3, 2025 21:04
Copilot AI review requested due to automatic review settings March 3, 2025 21:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 1 out of 1 changed files in this pull request and generated no comments.

@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

@EgorBo
Copy link
Member Author

EgorBo commented Mar 3, 2025

@MihuBot

@AndyAyersMS
Copy link
Member

As I mentioned in chat, I can split off the part of #112543 that deals with local span captures from the part that tries to reason about what happens when spans get passed to calls.

@EgorBo
Copy link
Member Author

EgorBo commented Mar 3, 2025

As I mentioned in chat, I can split off the part of #112543 that deals with local span captures from the part that tries to reason about what happens when spans get passed to calls.

@AndyAyersMS Nice! Should we land this PR then? It seems to find some diffs alone too, but it definitely need your work to properly handle more cases

@AndyAyersMS
Copy link
Member

As I mentioned in chat, I can split off the part of #112543 that deals with local span captures from the part that tries to reason about what happens when spans get passed to calls.

@AndyAyersMS Nice! Should we land this PR then? It seems to find some diffs alone too, but it definitely need your work to properly handle more cases

Yeah go ahead, I will try and get the other thing going but it may be a day or two.

}
else if (asCall->IsSpecialIntrinsic())
{
// Some known special intrinsics don't escape. At this moment, only the ones accepting byrefs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

byrefs for non-GC types....

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add that to the comment as part of a different PR to avoid re-running CI 🙂

@EgorBo EgorBo merged commit 75e7800 into dotnet:main Mar 4, 2025
112 checks passed
@EgorBo EgorBo deleted the inline-bitconverter branch March 4, 2025 13:22
@github-actions github-actions bot locked and limited conversation to collaborators Apr 4, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants