Skip to content

RyuJIT losing top bits of handles #122013

@MichalStrehovsky

Description

@MichalStrehovsky

In #121697 I'm seeing a failure in the interfacestatics test.

The problem is that in this part of the program:

ldsfld class [System.Runtime]System.Type class IFoo<object>::O
ldtoken object[]
call class [System.Runtime]System.Type class [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
ceq
brtrue IFoo_object_O_Okay
ldc.i4.1
ret

we decide that the IFoo_object_O_Okay jump is never taken and the entire Main should compile to return 1.

The reason why we didn't see this failure before #121697 is that with TrimMode=partial, compiler is forced to assume the IFoo<object>::O field could be a target of reflection and therefore we can't optimize with the otherwise constant value of the field. With TrimMode full the optimizations start kicking in, and misfire.

Here's the JitDump:

dumpy.txt

The problematic part is:

Folding operator with constant nodes into a constant:
               [000004] J---G--N---                         *  EQ        int   
               [000000] H----+-----                         +--*  CNS_INT(h) ref     'ILCompiler.DependencyAnalysis.FrozenRuntimeTypeNode'
               [000069] H----+-----                         \--*  CNS_INT(h) ref     'ILCompiler.DependencyAnalysis.FrozenRuntimeTypeNode'
Bashed to int constant:
               [000004] -----------                         *  CNS_INT   int    0

and the reason why it got folded this way is that RyuJIT lost the top bits of the CNS_INT handle in one side of the comparison.

As to why we have top bits set in the handle:

#if DEBUG
private static readonly IntPtr s_handleHighBitSet = (sizeof(IntPtr) == 4) ? new IntPtr(0x40000000) : new IntPtr(0x4000000000000000);
#endif
private IntPtr ObjectToHandle(object obj)
{
// MethodILScopes need to go through ObjectToHandle(MethodILScope methodIL).
Debug.Assert(obj is not MethodILScope);
return ObjectToHandleUnchecked(obj);
}
private IntPtr ObjectToHandleUnchecked(object obj)
{
// SuperPMI relies on the handle returned from this function being stable for the lifetime of the crossgen2 process
// If handle deletion is implemented, please update SuperPMI
IntPtr handle;
if (!_objectToHandle.TryGetValue(obj, out handle))
{
handle = (IntPtr)(handleMultiplier * _handleToObject.Count + handleBase);
#if DEBUG
handle = new IntPtr((long)s_handleHighBitSet | (long)handle);
#endif
_handleToObject.Add(obj);
_objectToHandle.Add(obj, handle);
}
return handle;
}

Notice we set top bits, but do not enforce the top bits are present when the handle is used, so handles with stripped top bits are as good as any.

Is the bug that this should look at the pointer size of the target, not at the pointer size of the host?

private static readonly IntPtr s_handleHighBitSet = (sizeof(IntPtr) == 4) ? new IntPtr(0x40000000) : new IntPtr(0x4000000000000000);

Or is RyuJIT expected to preserve the top bits like this and the bug is in RyuJIT?

Cc @davidwrighton

Metadata

Metadata

Assignees

Labels

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

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions