Skip to content

Fix box/unbox Nullable<T> types #3199

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions src/CLR/CorLib/corlib_native.h
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,14 @@ struct Library_corlib_native_System_MulticastDelegate
//--//
};

struct Library_corlib_native_System_Nullable_1
{
static const int FIELD__hasValue = 1;
static const int FIELD__value = 2;

//--//
};

struct Library_corlib_native_System_Number
{
NANOCLR_NATIVE_DECLARE(FormatNative___STATIC__STRING__OBJECT__BOOLEAN__STRING__STRING__STRING__STRING__SZARRAY_I4);
Expand Down
96 changes: 94 additions & 2 deletions src/CLR/Core/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
//
#include "Core.h"

typedef Library_corlib_native_System_Nullable_1 Sys_Nullable;

////////////////////////////////////////////////////////////////////////////////////////////////////

#if defined(NANOCLR_TRACE_EXCEPTIONS) && defined(VIRTUAL_DEVICE)
Expand Down Expand Up @@ -2802,13 +2804,101 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg)

UPDATESTACK(stack, evalPos);

// check if value is a nullable type
bool hasValue = false;
CLR_RT_HeapBlock *nullableValue = nullptr;
bool valueIsNullableType = false;
bool tokenIsNullableType = false;
CLR_RT_TypeDef_Instance destinationType;

valueIsNullableType =
CLR_RT_ExecutionEngine::IsInstanceOf(evalPos[0], g_CLR_RT_WellKnownTypes.Nullable);
tokenIsNullableType =
CLR_RT_ExecutionEngine::IsInstanceOf(typeInst, g_CLR_RT_WellKnownTypes.Nullable);

// resolve the T to box to / unbox from
if (tokenIsNullableType && destinationType.ResolveNullableType(arg, assm, &stack->m_call) == false)
{
NANOCLR_SET_AND_LEAVE(CLR_E_WRONG_TYPE);
}

if (valueIsNullableType)
{
// check HasValue property
nullableValue = evalPos[0].Dereference();
hasValue = nullableValue[Library_corlib_native_System_Nullable_1::FIELD__hasValue]
.NumericByRefConst()
.u1;
}

if (op == CEE_BOX)
{
NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst));
if (tokenIsNullableType)
{
if (!hasValue)
{
// box a null reference
evalPos[0].SetObjectReference(nullptr);
}
else
{
// reach the value to box
CLR_RT_HeapBlock &value =
nullableValue[Library_corlib_native_System_Nullable_1::FIELD__value];

// box the value
NANOCLR_CHECK_HRESULT(value.PerformBoxing(destinationType));

// assign the boxed result back to the evaluation stack
evalPos[0].Assign(value);
}
}
else
{
NANOCLR_CHECK_HRESULT(evalPos[0].PerformBoxing(typeInst));
}
}
else
{
NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(typeInst));
if (tokenIsNullableType)
{
// create a Nullable<T>...
CLR_RT_HeapBlock nullableObject;
CLR_RT_TypeSpec_Index destinationTypeSpec;
destinationTypeSpec.data = arg;

NANOCLR_CHECK_HRESULT(g_CLR_RT_ExecutionEngine.NewGenericInstanceObject(
nullableObject,
typeInst,
destinationTypeSpec));

CLR_RT_ProtectFromGC gc(nullableObject);

if (evalPos[0].Dereference() == nullptr)
{
// assign a null reference (already carried out by NewObjectFromIndex)
// set HasValue to false
nullableObject.Dereference()[Sys_Nullable::FIELD__hasValue].SetBoolean(false);
}
else
{
// unbox the T value...
NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(destinationType));

// assign the copied unboxed value
nullableObject.Dereference()[Sys_Nullable::FIELD__value].Assign(evalPos[0]);

// set HasValue to true
nullableObject.Dereference()[Sys_Nullable::FIELD__hasValue].SetBoolean(true);
}

// assign the Nullable<T> object to the evaluation stack
evalPos[0].SetObjectReference(nullableObject.Dereference());
}
else
{
NANOCLR_CHECK_HRESULT(evalPos[0].PerformUnboxing(typeInst));
}
}
break;
}
Expand All @@ -2825,6 +2915,8 @@ HRESULT CLR_RT_Thread::Execute_IL(CLR_RT_StackFrame &stackArg)
// ldobj.) When applied to a reference type, the unbox.any instruction has the same effect as
// castclass typeTok.

// TODO: still not handling Nullable<T> types here

CLR_RT_TypeDef_Instance typeInst{};
if (typeInst.ResolveToken(arg, assm, &stack->m_call) == false)
{
Expand Down
146 changes: 119 additions & 27 deletions src/CLR/Core/TypeSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -976,34 +976,23 @@ bool CLR_RT_TypeDef_Instance::ResolveToken(
parser.Initialize_TypeSpec(assm, ts);

CLR_RT_SignatureParser::Element elem;

// Skip any leading SZARRAY or BYREF
do
if (FAILED(parser.Advance(elem)))
{
if (FAILED(parser.Advance(elem)))
{
return false;
}
} while (elem.DataType == DATATYPE_SZARRAY || elem.DataType == DATATYPE_BYREF);
return false;
}

// If this is a closed‐generic instantiation header, peel off the wrapper
if (elem.DataType == DATATYPE_GENERICINST)
if (elem.DataType == DATATYPE_CLASS || elem.DataType == DATATYPE_VALUETYPE)
{
// consume the CLASS/VALUETYPE marker
if (FAILED(parser.Advance(elem)))
{
return false;
}
// consume the generic‐definition token itself
if (FAILED(parser.Advance(elem)))
{
return false;
}
// consume the count of generic arguments
if (FAILED(parser.Advance(elem)))
{
return false;
}
// If it's a class or value type, resolve the type
data = elem.Class.data;
assembly = g_CLR_RT_TypeSystem.m_assemblies[elem.Class.Assembly() - 1];
target = assembly->GetTypeDef(elem.Class.Type());

#if defined(NANOCLR_INSTANCE_NAMES)
name = assembly->GetString(target->name);
#endif

return true;
}

// walk forward until a VAR (type‐generic) or MVAR (method‐generic) is hit
Expand Down Expand Up @@ -1045,8 +1034,6 @@ bool CLR_RT_TypeDef_Instance::ResolveToken(
}
else
{
// elem.DataType == DATATYPE_MVAR

// Use the *caller's* bound genericType (Stack<Int32>, etc.)
if (caller == nullptr || caller->genericType == nullptr)
{
Expand All @@ -1060,6 +1047,7 @@ bool CLR_RT_TypeDef_Instance::ResolveToken(
data = gp.classTypeDef.data;
assembly = g_CLR_RT_TypeSystem.m_assemblies[gp.classTypeDef.Assembly() - 1];
target = assembly->GetTypeDef(gp.classTypeDef.Type());

return true;
}
}
Expand All @@ -1081,6 +1069,109 @@ bool CLR_RT_TypeDef_Instance::ResolveToken(
return false;
}

bool CLR_RT_TypeDef_Instance::ResolveNullableType(
CLR_UINT32 tk,
CLR_RT_Assembly *assm,
const CLR_RT_MethodDef_Instance *caller)
{
NATIVE_PROFILE_CLR_CORE();

if (assm)
{
CLR_UINT32 index = CLR_DataFromTk(tk);

if (CLR_TypeFromTk(tk) != TBL_TypeSpec)
{
// not a TypeSpec, so return false
ClearInstance();
return false;
}

// Grab the raw signature for the IL token (e.g. !T[], List`1<T>, etc.)
const CLR_RECORD_TYPESPEC *ts = assm->GetTypeSpec(index);
CLR_RT_SignatureParser parser;
parser.Initialize_TypeSpec(assm, ts);

CLR_RT_SignatureParser::Element elem;
if (FAILED(parser.Advance(elem)))
{
return false;
}

if (elem.DataType != DATATYPE_VALUETYPE)
{
// If it's not a value type, we can't resolve it as a nullable type
ClearInstance();
return false;
}

// move to the next element in the signature
if (FAILED(parser.Advance(elem)))
{
return false;
}

// If it's a type‐generic slot (!T), resolve against the caller's closed generic
if (elem.DataType == DATATYPE_VAR)
{
int pos = elem.GenericParamPosition;

// Use the *caller's* bound genericType (Stack<Int32>, etc.)
if (caller == nullptr || caller->genericType == nullptr)
{
return false;
}

auto &tsi = *caller->genericType;
CLR_UINT32 closedTsRow = tsi.TypeSpec();

CLR_RT_TypeDef_Index realTypeDef;
NanoCLRDataType realDataType;

// Only call this once to map (e.g. !T→Int32)
caller->assembly->FindGenericParamAtTypeSpec(closedTsRow, (CLR_UINT32)pos, realTypeDef, realDataType);

// populate this instance
data = realTypeDef.data;
assembly = g_CLR_RT_TypeSystem.m_assemblies[realTypeDef.Assembly() - 1];
target = assembly->GetTypeDef(realTypeDef.Type());

return true;
}
else if (elem.DataType == DATATYPE_MVAR)
{
// Use the *caller's* bound genericType (Stack<Int32>, etc.)
if (caller == nullptr || caller->genericType == nullptr)
{
return false;
}

CLR_RT_GenericParam_Index gpIdx;
caller->assembly->FindGenericParamAtMethodDef(*caller, elem.GenericParamPosition, gpIdx);
auto &gp = caller->assembly->crossReferenceGenericParam[gpIdx.GenericParam()];

data = gp.classTypeDef.data;
assembly = g_CLR_RT_TypeSystem.m_assemblies[gp.classTypeDef.Assembly() - 1];
target = assembly->GetTypeDef(gp.classTypeDef.Type());

return true;
}
else
{
// If it's a class or value type, resolve the type
data = elem.Class.data;
assembly = g_CLR_RT_TypeSystem.m_assemblies[elem.Class.Assembly() - 1];
target = assembly->GetTypeDef(elem.Class.Type());

return true;
}
}

ClearInstance();

return false;
}

//--//

bool CLR_RT_TypeDef_Instance::SwitchToParent()
Expand Down Expand Up @@ -4293,6 +4384,7 @@ static const TypeIndexLookup c_TypeIndexLookup[] = {
TIL("System", "Object", Object),
TIL("System", "ValueType", ValueType),
TIL("System", "Enum", Enum),
TIL("System", "Nullable`1", Nullable),

TIL("System", "AppDomainUnloadedException", AppDomainUnloadedException),
TIL("System", "ArgumentNullException", ArgumentNullException),
Expand Down
2 changes: 2 additions & 0 deletions src/CLR/Include/nanoCLR_Runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,7 @@ struct CLR_RT_WellKnownTypes
CLR_RT_TypeDef_Index Object;
CLR_RT_TypeDef_Index ValueType;
CLR_RT_TypeDef_Index Enum;
CLR_RT_TypeDef_Index Nullable;

CLR_RT_TypeDef_Index AppDomainUnloadedException;
CLR_RT_TypeDef_Index ArgumentNullException;
Expand Down Expand Up @@ -2114,6 +2115,7 @@ struct CLR_RT_TypeDef_Instance : public CLR_RT_TypeDef_Index
void ClearInstance();

bool ResolveToken(CLR_UINT32 tk, CLR_RT_Assembly *assm, const CLR_RT_MethodDef_Instance *caller = nullptr);
bool ResolveNullableType(CLR_UINT32 tk, CLR_RT_Assembly *assm, const CLR_RT_MethodDef_Instance *caller = nullptr);

//--//

Expand Down