Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@ public static partial class RuntimeHelpers
RuntimeTypeHandle targetTypeHandle,
out int count);

[Intrinsic]
[MethodImpl(MethodImplOptions.NoInlining)]
internal static unsafe uint GetObjectHeader(object obj)
{
// We need to pin the object to access its header data
// otherwise it's a GC hole (exterior pointer).
// JIT is expected to lower this call as a single load with a
// negative offset (contained)
fixed (byte* pData = &GetRawData(obj))
{
// 64bit: [4b padding][4b header][8b pMT][data..
// ^
// 32bit: [4b header][4b pMT][data..
// ^
return *(uint*)(pData - sizeof(nint) - sizeof(int));
}
}

// GetObjectValue is intended to allow value classes to be manipulated as 'Object'
// but have aliasing behavior of a value class. The intent is that you would use
// this function just before an assignment to a variable of type 'Object'. If the
Expand Down Expand Up @@ -125,8 +143,35 @@ public static unsafe void PrepareMethod(RuntimeMethodHandle method, RuntimeTypeH
[MethodImpl(MethodImplOptions.InternalCall)]
public static extern void PrepareDelegate(Delegate d);



private const uint BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX = 0x08000000;
private const uint BIT_SBLK_IS_HASHCODE = 0x04000000;
private const uint BITS_IS_VALID_HASHCODE = BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | BIT_SBLK_IS_HASHCODE;
private const int HASHCODE_BITS = 26;
private const uint MASK_HASHCODE = (1u << HASHCODE_BITS) - 1u;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetHashCode(object? o)
Comment thread
EgorBo marked this conversation as resolved.
{
// WASM doesn't yet support negative offsets for loads required
// for GetObjectHeader to be optimized in RyuJIT.
if (!OperatingSystem.IsWasi() && !OperatingSystem.IsBrowser())
{
if (o is not null)
{
uint syncBlockValue = GetObjectHeader(o);
if ((syncBlockValue & BITS_IS_VALID_HASHCODE) == BITS_IS_VALID_HASHCODE)
{
return unchecked((int)(syncBlockValue & MASK_HASHCODE));
}
}
}
return InternalGetHashCode(o);
}

[MethodImpl(MethodImplOptions.InternalCall)]
public static extern int GetHashCode(object? o);
private static extern int InternalGetHashCode(object? o);

/// <summary>
/// If a hash code has been assigned to the object, it is returned. Otherwise zero is
Expand All @@ -136,8 +181,27 @@ public static unsafe void PrepareMethod(RuntimeMethodHandle method, RuntimeTypeH
/// The advantage of this over <see cref="GetHashCode" /> is that it avoids assigning a hash
/// code to the object if it does not already have one.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int TryGetHashCode(object o)
{
// WASM doesn't yet support negative offsets for loads required
// for GetObjectHeader to be optimized in RyuJIT.
if (!OperatingSystem.IsWasi() && !OperatingSystem.IsBrowser())
{
if (o is not null)
{
uint syncBlockValue = GetObjectHeader(o);
if ((syncBlockValue & BITS_IS_VALID_HASHCODE) == BITS_IS_VALID_HASHCODE)
{
return unchecked((int)(syncBlockValue & MASK_HASHCODE));
}
}
}
return InternalTryGetHashCode(o);
}

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int TryGetHashCode(object o);
private static extern int InternalTryGetHashCode(object? o);

public static new unsafe bool Equals(object? o1, object? o2)
{
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/classlibnative/bcltype/objectnative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ NOINLINE static INT32 GetHashCodeHelper(OBJECTREF objRef)
{
DWORD idx = 0;

FC_INNER_PROLOG(ObjectNative::GetHashCode);
FC_INNER_PROLOG(ObjectNative::InternalGetHashCode);

HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2, objRef);

Expand All @@ -37,7 +37,7 @@ NOINLINE static INT32 GetHashCodeHelper(OBJECTREF objRef)

// Note that we obtain a sync block index without actually building a sync block.
// That's because a lot of objects are hashed, without requiring support for
FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {
Comment thread
EgorBo marked this conversation as resolved.
FCIMPL1(INT32, ObjectNative::InternalGetHashCode, Object* obj) {

CONTRACTL
{
Expand Down Expand Up @@ -82,7 +82,7 @@ FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {
}
FCIMPLEND

FCIMPL1(INT32, ObjectNative::TryGetHashCode, Object* obj) {
FCIMPL1(INT32, ObjectNative::InternalTryGetHashCode, Object* obj) {

CONTRACTL
{
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/classlibnative/bcltype/objectnative.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ class ObjectNative
{
public:

static FCDECL1(INT32, GetHashCode, Object* vThisRef);
static FCDECL1(INT32, TryGetHashCode, Object* vThisRef);
static FCDECL1(INT32, InternalGetHashCode, Object* vThisRef);
static FCDECL1(INT32, InternalTryGetHashCode, Object* vThisRef);
static FCDECL2(FC_BOOL_RET, ContentEquals, Object *pThisRef, Object *pCompareRef);
static FCDECL1(Object*, GetClass, Object* pThis);
static FCDECL1(FC_BOOL_RET, IsLockHeld, Object* pThisUNSAFE);
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4688,6 +4688,8 @@ void CodeGen::genCodeForMulLong(GenTreeOp* mul)
//
void CodeGen::genLeaInstruction(GenTreeAddrMode* lea)
{
assert((lea->gtDebugFlags & GTF_DEBUG_LEA_EXTERIOR_PTR) == 0);

genConsumeOperands(lea);
emitter* emit = GetEmitter();
emitAttr size = emitTypeSize(lea);
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/codegenloongarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7216,6 +7216,8 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* blkOp)
//
void CodeGen::genLeaInstruction(GenTreeAddrMode* lea)
{
assert((lea->gtDebugFlags & GTF_DEBUG_LEA_EXTERIOR_PTR) == 0);

genConsumeOperands(lea);
emitter* emit = GetEmitter();
emitAttr size = emitTypeSize(lea);
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/codegenriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7266,6 +7266,8 @@ void CodeGen::genCodeForStoreBlk(GenTreeBlk* blkOp)
//
void CodeGen::genLeaInstruction(GenTreeAddrMode* lea)
{
assert((lea->gtDebugFlags & GTF_DEBUG_LEA_EXTERIOR_PTR) == 0);

genConsumeOperands(lea);
emitter* emit = GetEmitter();
emitAttr size = emitTypeSize(lea);
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6787,6 +6787,8 @@ void CodeGen::genJmpMethod(GenTree* jmp)
// produce code for a GT_LEA subnode
void CodeGen::genLeaInstruction(GenTreeAddrMode* lea)
{
assert((lea->gtDebugFlags & GTF_DEBUG_LEA_EXTERIOR_PTR) == 0);

emitAttr size = emitTypeSize(lea);
genConsumeOperands(lea);

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ enum GenTreeDebugFlags : unsigned int

GTF_DEBUG_VAR_CSE_REF = 0x00800000, // GT_LCL_VAR -- This is a CSE LCL_VAR node
GTF_DEBUG_CAST_DONT_FOLD = 0x00400000, // GT_CAST -- Try to prevent this cast from being folded
GTF_DEBUG_LEA_EXTERIOR_PTR = 0x00200000, // GT_LEA -- It has to be expanded with offset "contained"
};

inline constexpr GenTreeDebugFlags operator ~(GenTreeDebugFlags a)
Expand Down
14 changes: 14 additions & 0 deletions src/coreclr/jit/importercalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3122,6 +3122,9 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
// Not expanding this can lead to noticeable allocations in T0
case NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan:

// This is expanded into a single load instruction
case NI_System_Runtime_CompilerServices_RuntimeHelpers_GetObjectHeader:

// We need these to be able to fold "typeof(...) == typeof(...)"
case NI_System_Type_GetTypeFromHandle:
case NI_System_Type_op_Equality:
Expand Down Expand Up @@ -3312,6 +3315,13 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
break;
}

case NI_System_Runtime_CompilerServices_RuntimeHelpers_GetObjectHeader:
{
// It is expanded into a single load in lowering
isSpecial = true;
break;
}

case NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan:
{
retNode = impCreateSpanIntrinsic(sig);
Expand Down Expand Up @@ -9633,6 +9643,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
{
result = NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan;
}
else if (strcmp(methodName, "GetObjectHeader") == 0)
{
result = NI_System_Runtime_CompilerServices_RuntimeHelpers_GetObjectHeader;
}
else if (strcmp(methodName, "InitializeArray") == 0)
{
result = NI_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray;
Expand Down
65 changes: 65 additions & 0 deletions src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2484,6 +2484,61 @@ bool Lowering::LowerCallMemcmp(GenTreeCall* call, GenTree** next)
return false;
}

//------------------------------------------------------------------------
// LowerCallGetObjectHeader: Replace RuntimeHelpers.GetObjectHeader with a simple
// load with a negative offset (contained)
//
// Arguments:
// tree - GenTreeCall node to expand as a load
// next - [out] Next node to lower if this function returns true
//
// Return Value:
// false if no changes were made
//
bool Lowering::LowerCallGetObjectHeader(GenTreeCall* call, GenTree** next)
{
if (comp->info.compHasNextCallRetAddr)
{
JITDUMP("compHasNextCallRetAddr is true - bail out.\n")
return false;
}

GenTree* objNode = call->gtArgs.GetUserArgByIndex(0)->GetNode();

// Access object's header using LEA with a negative offset. This LEA has to be lowered into a single
// load instruction with an addressing mode to avoid introducing a GC hole.
const ssize_t offset = -4;
GenTree* lea = new (comp, GT_LEA) GenTreeAddrMode(TYP_I_IMPL, objNode, nullptr, 1, offset);
INDEBUG(lea->gtDebugFlags |= GTF_DEBUG_LEA_EXTERIOR_PTR);
GenTreeIndir* result = comp->gtNewIndir(TYP_INT, lea);
lea->SetContained();

LIR::Use use;
if (BlockRange().TryGetUse(call, &use))
{
use.ReplaceWith(result);
}
else
{
result->SetUnusedValue();
}

BlockRange().InsertAfter(objNode, lea, result);
BlockRange().Remove(call);

// Remove all non-user args (e.g. r2r cell)
for (CallArg& arg : call->gtArgs.Args())
{
if (!arg.IsUserArg())
{
arg.GetNode()->SetUnusedValue();
}
}

*next = result->gtNext;
return true;
}

// do lowering steps for a call
// this includes:
// - adding the placement nodes (either stack or register variety) for arguments
Expand Down Expand Up @@ -2538,6 +2593,16 @@ GenTree* Lowering::LowerCall(GenTree* node)
}
break;

// Target must guarantee that LEA(base, -12 or -8) is lowered as a single load with a contained imm offset
#if defined(TARGET_XARCH) || defined(TARGET_ARMARCH) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
case NI_System_Runtime_CompilerServices_RuntimeHelpers_GetObjectHeader:
if (LowerCallGetObjectHeader(call, &nextNode))
{
return nextNode;
}
break;
#endif

default:
break;
}
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/lower.h
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class Lowering final : public Phase
bool LowerCallMemmove(GenTreeCall* call, GenTree** next);
bool LowerCallMemcmp(GenTreeCall* call, GenTree** next);
bool LowerCallMemset(GenTreeCall* call, GenTree** next);
bool LowerCallGetObjectHeader(GenTreeCall* call, GenTree** next);
void LowerCFGCall(GenTreeCall* call);
void MoveCFGCallArgs(GenTreeCall* call);
void MoveCFGCallArg(GenTreeCall* call, GenTree* node);
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/namedintrinsiclist.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ enum NamedIntrinsic : unsigned short
NI_Internal_Runtime_MethodTable_Of,

NI_System_Runtime_CompilerServices_RuntimeHelpers_CreateSpan,
NI_System_Runtime_CompilerServices_RuntimeHelpers_GetObjectHeader,
NI_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray,
NI_System_Runtime_CompilerServices_RuntimeHelpers_IsKnownConstant,

Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -445,8 +445,8 @@ FCFuncStart(gRuntimeHelpers)
FCFuncElement("InitializeArray", ArrayNative::InitializeArray)
FCFuncElement("GetSpanDataFrom", ArrayNative::GetSpanDataFrom)
FCFuncElement("PrepareDelegate", ReflectionInvocation::PrepareDelegate)
FCFuncElement("GetHashCode", ObjectNative::GetHashCode)
FCFuncElement("TryGetHashCode", ObjectNative::TryGetHashCode)
FCFuncElement("InternalGetHashCode", ObjectNative::InternalGetHashCode)
FCFuncElement("InternalTryGetHashCode", ObjectNative::InternalTryGetHashCode)
FCFuncElement("ContentEquals", ObjectNative::ContentEquals)
FCFuncElement("EnsureSufficientExecutionStack", ReflectionInvocation::EnsureSufficientExecutionStack)
FCFuncElement("TryEnsureSufficientExecutionStack", ReflectionInvocation::TryEnsureSufficientExecutionStack)
Expand Down