-
Notifications
You must be signed in to change notification settings - Fork 2.6k
JIT: Optimize "constant_string".Length #26000
Conversation
Jit-diff (
|
may be patch roslyn and fsharp? |
src/jit/morph.cpp
Outdated
GenTree* strCon = op1->AsOp()->gtGetOp1(); | ||
if (strCon->OperIs(GT_CNS_INT) && strCon->IsIconHandle(GTF_ICON_STR_HDL)) | ||
{ | ||
CORINFO_String* asString = *reinterpret_cast<CORINFO_String**>(strCon->AsIntCon()->IconValue()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I remember looking into this in the past and thinking that the JIT-VM interface would need to be extended to obtain this information. But I don't remember why, perhaps it wasn't clear if it's appropriate to use CORINFO_String
like this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not ok to use CORINFO_String
like this. The string is not pinned and the GC can move it while the JIT is looking at the Length field. It would result into GC hole / intermittent silent bad codegen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I suspected that, I hope I won't have to modify vm API for it, looking at the importation now to find out if I can save string length to GenTreeStrCon (new field)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think you would be able to do this without modifying JIT/EE interface
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkotas ah I see, I guess it should be something similar to isValidStringRef
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkotas I tried to copy-paste isValidStringRef
method and introduced getStringLength
should work now I hope...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a new function is added to the JIT-VM interface would it make sense to also return the string characters somehow? Perhaps in the future we could add some other optimizations around constant strings such as generating special code for a.Equals("ABCD")
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember looking into this in the past and thinking that the JIT-VM interface would need to be extended to obtain this information. But I don't remember why,
"Jit doesn't recognise ldstr as non-null"? https://github.com/dotnet/coreclr/issues/22511#issuecomment-462440743
That seems to be unexpectedly complicated, the VM keeps the JIT in the dark about the string length. The JIT interface will probably need to be extended.
src/jit/morph.cpp
Outdated
|
||
case GT_ARR_LENGTH: | ||
// Morph "constant_string".Length to CNS_INT(15) | ||
if (op1->OperIs(GT_IND) && fgGlobalMorph) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this really have to wait until morph? Is there any way to do this during importation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is enough to catch all cases. Does this work if the string is a static readonly
field? Does it work if you have conditional code like (c ? "abc" : "def").Length
? The later, while probably not common, would likely require updating VN to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mikedn if I do it in the importation phase will it handle e.g. stringBuilder.Append(".");
? (Append will be inlined and it checks for Length < 2 inside afair for fast insert -- so with the current change it's improved).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inliner will directly substitute constants like this, so checking in the importer should get most cases.
Where do code size regressions come from? Seems odd for this to introduce any regressions, unless we have cases where the characters of the constant string are accessed via the indexer and this breaks range check elimination. Though I don't see how, if anyhting it should be the other way around. |
What do you mean by that? |
|
In general managed compilers do not make optimizations for various reasons. The fact that there is more than one managed compiler is one of them. Another reason is that some optimizations may require access to more code than the managed compiler has access to (e.g. what happens if the constant string is returned by a method in another assembly). IL optimizations may also affect various IL tools out there (e.g. some IL tool may care about knowing when the length of a particular string is obtained). |
I would move the transformation into Also you'll need a new jit GUID. |
Right, perhaps with some care that would also take care of the unnecessary null check from #22511 |
An example of the regression:
Is not handled yet but the
So will it handle this case: sharplab.io ? Current implementation handles it.
Um..
Could you please show me where it's located? Also, I was wrong about |
It does not sound like a good idea to me. It would just make the code bigger and slower. |
The JIT GUID is found in corinfo.h. Once you change it, running jit-diffs will become more complicated as the base/diff jits will no longer be plugin compatible. So maybe change that as a last step. You should invoke the suitably updated |
I wonder if we can lose bounds check eliminations from this, eg a loop like string s = "a string";
for (int i = 0; i < s.Length; i++)
{
... s[i]
} Can you double-check that the internally generated string length access also gets optimized? |
@EgorBo, why is that a regression? Both should be 4 bytes as there are |
@tannergooding Maybe because of constant transmissions? just like @AndyAyersMS 's example. It will make sense some of regressions causes from inline small codes into large code. and most of codes will be improved from other optimization side effects. |
@dotnet/jit-contrib |
Thank you for your contribution. As announced in dotnet/coreclr#27549 this repository will be moving to dotnet/runtime on November 13. If you would like to continue working on this PR after this date, the easiest way to move the change to dotnet/runtime is:
|
Thank you for your contribution. As announced in #27549 the dotnet/runtime repository will be used going forward for changes to this code base. Closing this PR as no more changes will be accepted into master for this repository. If you’d like to continue working on this change please move it to dotnet/runtime. |
Replace
"Hello".Length
with just5
. See https://github.com/dotnet/coreclr/issues/3633Before:
After:
Morph:
Also, according to jit-diff (see below) it improves things like: