Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Convert fast path of ValueType.GetHashCode to managed#97590

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
jkotas merged 19 commits intodotnet:mainfromhuoyaoyuan:cancomparebits
Feb 21, 2024

Conversation

@huoyaoyuan
Copy link
Member

Also converts HELPER_METHOD_FRAME for slow path to QCall.

@ghostghost added area-VM-coreclr community-contributionIndicates that the PR has been added by a community member labelsJan 27, 2024

// Subset of src\vm\methodtable.h
[StructLayout(LayoutKind.Explicit)]
internal unsafestruct MethodTableAuxiliaryData
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

I'm not sure if this is the desired direction to expose this, but it's relatively simple.

Comment on lines 107 to 109
// We don't want to expose the method table pointer in the hash code
// Let's use the typeID instead.
uinttypeID=RuntimeHelpers.GetTypeID(pMT);
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Is this something we still care about? I assumeGetType().GetHashCode() would be slower.

Copy link
Member

Choose a reason for hiding this comment

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

I think running the methodtable pointer throughHashCode would be good enough. If you do that, the multiplication on the next won't be necessary.

hashCode^=RegularGetValueTypeHashCode(pMT,ObjectHandleOnStack.Create(refobj));
}

GC.KeepAlive(this);
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Should be unnecessary

Copy link
Member

Choose a reason for hiding this comment

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

Right

AaronRobinsonMSFT reacted with thumbs up emoji
}

[LibraryImport(RuntimeHelpers.QCall,EntryPoint="ValueType_RegularGetValueTypeHashCode")]
[SuppressGCTransition]
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

The unmanaged slow path manipulates the object reference in COOP mode

Copy link
Member

Choose a reason for hiding this comment

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

It can also throw and call back into managed code. It is not ok to use SuppressGCTransition with that.

SuppressGCTransition can be only used for leaf methods.

Copy link
Member

Choose a reason for hiding this comment

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

If you would like to make it more managed, you can change the helper to return the strategy to use to compute the hashcode:

  • Call object.GetHashCode for object reference at given offset
  • Call double.GetHashCode for field at given offset
  • Call float.GetHashCode for field at given offset
  • Hash N bytes from given offset (this can also cover the case whereCanCompareBitsOrUseFastGetHashCode was not computed)

AaronRobinsonMSFT reacted with thumbs up emoji
Copy link
MemberAuthor

@huoyaoyuanhuoyaoyuanJan 27, 2024
edited
Loading

Choose a reason for hiding this comment

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

Got some chanllenge when handling the recursive case.
How to pass a reference pointing to the middle of an object to QCall? There's a leaf methodGetFieldTypeHandleThrowing marked as throwing, so it looks unsuitable for FCall since we are moving away from HELPER_METHOD_FRAME.
Or, will it really throw when loading a field handle of already boxed type? Is there any alternate?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Well, P/Invoke generator can pin theref byte. Is it reliable/performant for object fields? Can GCPROTECT be omitted then?

Copy link
Member

Choose a reason for hiding this comment

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

I think it would work better if you return field offset from the QCall and compute the byref on the managed side. Nothing to GC protect, etc.

AaronRobinsonMSFT reacted with thumbs up emoji
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

For the recursive value type case, it requires a reference pointing to the field, which is not a top level object. Passing object handle+offset does work, but looks a bit confusing if more levels are involved.

Copy link
Member

Choose a reason for hiding this comment

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

Compound offset like this is not uncommon. It is frequently used e.g. by JITed code.

My preference is to have less manually managed code. Less GC mode switches in VM and less manually managed code that runs in a cooperative mode is goodness.

@huoyaoyuan
Copy link
MemberAuthor

huoyaoyuan commentedJan 28, 2024
edited
Loading

There is also an inconsistency in old code: if the first field is a struct type that overridesGetHashCode, then the overridden method won't be called by the fallback method. In contrast, the overriddenEquals will get called by fallbackEquals. Consider following code:

structStruct2{publicStruct1field;}structStruct1{publicstring?o;publicoverrideintGetHashCode()=>StringComparer.OrdinalIgnoreCase.GetHashCode(o??"");publicoverrideboolEquals(object?obj)=>objisStruct1other&&string.Equals(o,other.o,StringComparison.OrdinalIgnoreCase);}Struct1a1=new(){o="ABC"},b1=new(){o="abc"};Struct2a2=new(){field=a1},b2=new(){field=b1};Console.WriteLine(a1.Equals(b1));// trueConsole.WriteLine(a1.GetHashCode()==b1.GetHashCode());// trueConsole.WriteLine(a2.Equals(b2));// trueConsole.WriteLine(a2.GetHashCode()==b2.GetHashCode());// false

This can be fixed in follow-up.

CorElementType fieldType = field->GetFieldType();
if (fieldType == ELEMENT_TYPE_R8)
{
*fieldOffset = field->GetOffsetUnsafe();
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
*fieldOffset = field->GetOffsetUnsafe();
*fieldOffset+= field->GetOffsetUnsafe();

I think all these need to be+= to make the recursive case work.

Copy link
Member

Choose a reason for hiding this comment

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

I may be a good idea to add a test for the recursive case to src\libraries\system.Runtime\tests\System.Runtime.Tests\System\ValueTypeTests.cs that would catch this mistake.

AaronRobinsonMSFT reacted with thumbs up emoji
Comment on lines 311 to 312
obj1.o=null;
obj1.value.value=2;

Choose a reason for hiding this comment

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

Suggested change
obj1.o=null;
obj1.value.value=2;
obj2.o=null;
obj2.value.value=2;

I assume?

}

[Fact]
publicstaticvoidStructContainsPointerNestedCompareTest()

Choose a reason for hiding this comment

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

Because these two values are not the same the fact they aren't equal makes sense. We should also have a test where they are equal to validate the other direction.

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Well all the other tests are only testing the equal case. Returning different hash code is just an implementation detail. The test should be reversed and only test equal case instead.

@jkotas
Copy link
Member

The new test is failing on native AOT:

[FAIL] System.Tests.ValueTypeTests.StructContainsPointerNestedCompareTestAssert.NotEqual() Failure: Values are equalExpected: Not -413602087Actual:       -413602087   at System.Tests.ValueTypeTests.StructContainsPointerNestedCompareTest() + 0xdc

@jkotas
Copy link
Member

The new test is failing on native AOT:

If it looks like a native AOT bug, you can file an issue on it and disable the test against it.

@AaronRobinsonMSFTAaronRobinsonMSFT added the needs-author-actionAn issue or pull request that requires more info or actions from the author. labelFeb 6, 2024
@huoyaoyuan
Copy link
MemberAuthor

huoyaoyuan commentedFeb 20, 2024
edited
Loading

If it looks like a native AOT bug, you can file an issue on it and disable the test against it.

I don't think it's actually a bug. Different values may have equal hash code, it's not violating the contract.
The coreclr behavior which reads next field when meetingnull also makes the implementation more complicated.I'd like to keep the behavior as-is and limit the test to run on coreclr only.

@huoyaoyuan
Copy link
MemberAuthor

Removing the behavior that skipsnull field can simplify the implementation and remove the need to pass the object itself to native code. Should we change the behavior instead?

@jkotas
Copy link
Member

Should we change the behavior instead?

I do not think we should be changing the behavior. It can result into arbitrarily large perf regression of hashtable lookups.

@huoyaoyuan
Copy link
MemberAuthor

Is there any other change required? I don't think it's necessary to test such implementation detail.

Copy link
Member

@jkotasjkotas left a comment

Choose a reason for hiding this comment

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

Thank you!

@jkotasjkotas merged commit8cc7586 intodotnet:mainFeb 21, 2024
@jkotasjkotas removed the needs-author-actionAn issue or pull request that requires more info or actions from the author. labelFeb 21, 2024
@huoyaoyuanhuoyaoyuan deleted the cancomparebits branchFebruary 21, 2024 04:45
davidwrighton added a commit to davidwrighton/runtime that referenced this pull requestMar 6, 2024
@github-actionsgithub-actionsbot locked and limited conversation to collaboratorsMar 22, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.

Reviewers

@AaronRobinsonMSFTAaronRobinsonMSFTAaronRobinsonMSFT left review comments

@jkotasjkotasjkotas approved these changes

Assignees

No one assigned

Labels

area-VM-coreclrcommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

3 participants

@huoyaoyuan@jkotas@AaronRobinsonMSFT

[8]ページ先頭

©2009-2025 Movatter.jp