Skip to content
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

[NativeAOT] Objective-C Marshal: object tracker support #78280

Merged
merged 15 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from 14 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
14 changes: 14 additions & 0 deletions src/coreclr/nativeaot/Bootstrap/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ extern "C" void OnFirstChanceException();
extern "C" void OnUnhandledException();
extern "C" void IDynamicCastableIsInterfaceImplemented();
extern "C" void IDynamicCastableGetInterfaceImplementation();
#ifdef FEATURE_OBJCMARSHAL
extern "C" void ObjectiveCMarshalTryGetTaggedMemory();
extern "C" void ObjectiveCMarshalGetIsTrackedReferenceCallback();
extern "C" void ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback();
#endif

typedef void(*pfn)();

Expand All @@ -126,6 +131,15 @@ static const pfn c_classlibFunctions[] = {
&OnUnhandledException,
&IDynamicCastableIsInterfaceImplemented,
&IDynamicCastableGetInterfaceImplementation,
#ifdef FEATURE_OBJCMARSHAL
&ObjectiveCMarshalTryGetTaggedMemory,
&ObjectiveCMarshalGetIsTrackedReferenceCallback,
&ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback,
#else
nullptr,
nullptr,
nullptr,
#endif
};

#ifndef _countof
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/nativeaot/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ if(CLR_CMAKE_HOST_UNIX)

if(CLR_CMAKE_TARGET_OSX)
add_definitions(-D_XOPEN_SOURCE)
add_definitions(-DFEATURE_OBJCMARSHAL)
endif(CLR_CMAKE_TARGET_OSX)

if(CLR_CMAKE_TARGET_ARCH_AMD64 OR CLR_CMAKE_TARGET_ARCH_I386)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,14 @@ internal bool IsHFA
}
}

internal bool IsTrackedReferenceWithFinalizer
{
get
{
return (ExtendedFlags & (ushort)EETypeFlagsEx.IsTrackedReferenceWithFinalizerFlag) != 0;
}
}

internal uint ValueTypeFieldPadding
{
get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ internal enum ClassLibFunctionId
OnUnhandledException = 7,
IDynamicCastableIsInterfaceImplemented = 8,
IDynamicCastableGetInterfaceImplementation = 9,
ObjectiveCMarshalTryGetTaggedMemory = 10,
ObjectiveCMarshalGetIsTrackedReferenceCallback = 11,
ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback = 12,
}

internal static class InternalCalls
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/nativeaot/Runtime/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ else()
set(ASM_SUFFIX S)
endif()

if (CLR_CMAKE_TARGET_OSX)
list(APPEND COMMON_RUNTIME_SOURCES
interoplibinterface_objc.cpp
)
endif (CLR_CMAKE_TARGET_OSX)

if (CLR_CMAKE_TARGET_ARCH_AMD64 AND CLR_CMAKE_TARGET_WIN32)
list(APPEND COMMON_RUNTIME_SOURCES
${GC_DIR}/vxsort/isa_detection.cpp
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/nativeaot/Runtime/Crst.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum CrstType
CrstInterfaceDispatchGlobalLists,
CrstStressLog,
CrstRestrictedCallouts,
CrstObjectiveCMarshalCallouts,
CrstGcStressControl,
CrstSuspendEE,
CrstCastCache,
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/nativeaot/Runtime/GCHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "varint.h"
#include "regdisplay.h"
#include "StackFrameIterator.h"
#include "interoplibinterface.h"

#include "thread.h"
#include "RWLock.h"
Expand Down Expand Up @@ -132,6 +133,13 @@ COOP_PINVOKE_HELPER(void, RhUnregisterGcCallout, (GcRestrictedCalloutKind eKind,
RestrictedCallouts::UnregisterGcCallout(eKind, pCallout);
}

#ifdef FEATURE_OBJCMARSHAL
COOP_PINVOKE_HELPER(FC_BOOL_RET, RhRegisterObjectiveCMarshalBeginEndCallback, (void * pCallback))
{
FC_RETURN_BOOL(ObjCMarshalNative::RegisterBeginEndCallback(pCallback));
}
#endif

COOP_PINVOKE_HELPER(int32_t, RhGetLohCompactionMode, ())
{
return GCHeapUtilities::GetGCHeap()->GetLOHCompactionMode();
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/nativeaot/Runtime/ICodeManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ enum class ClasslibFunctionId
OnUnhandledException = 7,
IDynamicCastableIsInterfaceImplemented = 8,
IDynamicCastableGetInterfaceImplementation = 9,
ObjectiveCMarshalTryGetTaggedMemory = 10,
ObjectiveCMarshalGetIsTrackedReferenceCallback = 11,
ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback = 12,
};

enum class AssociatedDataFlags : unsigned char
Expand Down
28 changes: 27 additions & 1 deletion src/coreclr/nativeaot/Runtime/gcrhenv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "daccess.h"

#include "GCMemoryHelpers.h"
#include "interoplibinterface.h"

#include "holder.h"
#include "volatile.h"
Expand Down Expand Up @@ -682,13 +683,25 @@ void GCToEEInterface::GcStartWork(int condemned, int /*max_gen*/)

void GCToEEInterface::BeforeGcScanRoots(int condemned, bool is_bgc, bool is_concurrent)
{
#ifdef FEATURE_OBJCMARSHAL
if (!is_concurrent)
{
ObjCMarshalNative::BeforeRefCountedHandleCallbacks();
}
#endif
}

// EE can perform post stack scanning action, while the user threads are still suspended
void GCToEEInterface::AfterGcScanRoots(int condemned, int /*max_gen*/, ScanContext* /*sc*/)
void GCToEEInterface::AfterGcScanRoots(int condemned, int /*max_gen*/, ScanContext* sc)
{
// Invoke any registered callouts for the end of the mark phase.
RestrictedCallouts::InvokeGcCallouts(GCRC_AfterMarkPhase, condemned);
AustinWise marked this conversation as resolved.
Show resolved Hide resolved
#ifdef FEATURE_OBJCMARSHAL
if (!sc->concurrent)
{
ObjCMarshalNative::AfterRefCountedHandleCallbacks();
}
#endif
}

void GCToEEInterface::GcDone(int condemned)
Expand All @@ -699,6 +712,11 @@ void GCToEEInterface::GcDone(int condemned)

bool GCToEEInterface::RefCountedHandleCallbacks(Object * pObject)
{
#ifdef FEATURE_OBJCMARSHAL
bool isReferenced = false;
if (ObjCMarshalNative::IsTrackedReference(pObject, &isReferenced))
return isReferenced;
#endif // FEATURE_OBJCMARSHAL
return RestrictedCallouts::InvokeRefCountedHandleCallbacks(pObject);
AustinWise marked this conversation as resolved.
Show resolved Hide resolved
}

Expand Down Expand Up @@ -1153,6 +1171,14 @@ void GCToEEInterface::HandleFatalError(unsigned int exitCode)

bool GCToEEInterface::EagerFinalized(Object* obj)
{
#ifdef FEATURE_OBJCMARSHAL
if (obj->GetGCSafeMethodTable()->IsTrackedReferenceWithFinalizer())
{
ObjCMarshalNative::OnEnteredFinalizerQueue(obj);
return false;
}
#endif

if (!obj->GetGCSafeMethodTable()->HasEagerFinalizer())
return false;

Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/nativeaot/Runtime/inc/MethodTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ class MethodTable
{
HasEagerFinalizerFlag = 0x0001,
HasCriticalFinalizerFlag = 0x0002,
IsTrackedReferenceWithFinalizerFlag = 0x0004,
};

public:
Expand Down Expand Up @@ -272,6 +273,11 @@ class MethodTable
return (m_uFlags & HasCriticalFinalizerFlag) && !HasComponentSize();
}

bool IsTrackedReferenceWithFinalizer()
{
return (m_uFlags & IsTrackedReferenceWithFinalizerFlag) && !HasComponentSize();
}

bool HasComponentSize()
{
static_assert(HasComponentSizeFlag == (MethodTable::Flags)(1 << 31), "we assume that HasComponentSizeFlag matches the sign bit");
Expand Down
24 changes: 24 additions & 0 deletions src/coreclr/nativeaot/Runtime/interoplibinterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#ifdef FEATURE_OBJCMARSHAL

class ObjCMarshalNative
{
public:
using TryGetCallback = void*(REDHAWK_CALLCONV *)(void);
using TryGetTaggedMemoryCallback = CLR_BOOL(REDHAWK_CALLCONV *)(_In_ Object *, _Out_ void **);
using BeginEndCallback = void(REDHAWK_CALLCONV *)(void);
using IsReferencedCallback = int(REDHAWK_CALLCONV *)(_In_ void*);
using EnteredFinalizationCallback = void(REDHAWK_CALLCONV *)(_In_ void*);

public: // Instance inspection
static bool IsTrackedReference(_In_ Object * pObject, _Out_ bool* isReferenced);
public: // GC interaction
static bool RegisterBeginEndCallback(void * callback);
static void BeforeRefCountedHandleCallbacks();
static void AfterRefCountedHandleCallbacks();
static void OnEnteredFinalizerQueue(_In_ Object * object);
};

#endif // FEATURE_OBJCMARSHAL
178 changes: 178 additions & 0 deletions src/coreclr/nativeaot/Runtime/interoplibinterface_objc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include "common.h"
#include "CommonTypes.h"
#include "CommonMacros.h"
#include "daccess.h"
#include "PalRedhawkCommon.h"
#include "PalRedhawk.h"
#include "rhassert.h"
#include "slist.h"
#include "holder.h"
#include "gcrhinterface.h"
#include "shash.h"
#include "RWLock.h"
#include "rhbinder.h"
#include "Crst.h"
#include "RuntimeInstance.h"
#include "TypeManager.h"
#include "MethodTable.h"
#include "ObjectLayout.h"
#include "event.h"
#include "varint.h"
#include "regdisplay.h"
#include "StackFrameIterator.h"
#include "thread.h"
#include "threadstore.h"
#include "threadstore.inl"

#include "interoplibinterface.h"

#include "MethodTable.inl"

#ifdef FEATURE_OBJCMARSHAL

namespace
{
// TODO: Support registering multiple begin/end callbacks.
// NativeAOT support the concept of multiple managed "worlds",
// each with their own object heirarchy. Each of these worlds
// could potentially have its own set of callbacks.
// However this Objective-C Marshal API was not designed with such a
// possibility in mind.
ObjCMarshalNative::BeginEndCallback s_pBeginEndCallback = nullptr;

struct DisableTriggerGcWhileCallingManagedCode
{
Thread * m_pThread;
bool m_fGcStressWasSuppressed;

DisableTriggerGcWhileCallingManagedCode()
{
// It is illegal for any of the callouts to trigger a GC.
m_pThread = ThreadStore::GetCurrentThread();
m_pThread->SetDoNotTriggerGc();

// Due to the above we have better suppress GC stress.
m_fGcStressWasSuppressed = m_pThread->IsSuppressGcStressSet();
if (!m_fGcStressWasSuppressed)
m_pThread->SetSuppressGcStress();
}

~DisableTriggerGcWhileCallingManagedCode()
{
// Revert GC stress mode if we changed it.
if (!m_fGcStressWasSuppressed)
m_pThread->ClearSuppressGcStress();

m_pThread->ClearDoNotTriggerGc();
}
};

bool TryGetTaggedMemory(_In_ Object * obj, _Out_ void ** tagged)
{
void* fn = obj->get_EEType()
AustinWise marked this conversation as resolved.
Show resolved Hide resolved
->GetTypeManagerPtr()
->AsTypeManager()
->GetClasslibFunction(ClasslibFunctionId::ObjectiveCMarshalTryGetTaggedMemory);

ASSERT(fn != nullptr);

auto pTryGetTaggedMemoryCallback = reinterpret_cast<ObjCMarshalNative::TryGetTaggedMemoryCallback>(fn);

DisableTriggerGcWhileCallingManagedCode disabler{};

bool result = pTryGetTaggedMemoryCallback(obj, tagged);

return result;
}

// Calls a managed classlib function to potentially get an unmanaged callback.
// Not for use with ObjectiveCMarshalTryGetTaggedMemory.
void * GetCallbackViaClasslibCallback(_In_ Object * object, _In_ ClasslibFunctionId id)
{
void* fn = object->get_EEType()
->GetTypeManagerPtr()
->AsTypeManager()
->GetClasslibFunction(id);

ASSERT(fn != nullptr);

DisableTriggerGcWhileCallingManagedCode disabler{};

fn = ((ObjCMarshalNative::TryGetCallback)fn)();

ASSERT(fn != nullptr);

return fn;
}
}

bool ObjCMarshalNative::RegisterBeginEndCallback(void * callback)
{
ASSERT(callback != nullptr);

// We must be in Cooperative mode since we are setting callbacks that
// will be used during a GC and we want to ensure a GC isn't occurring
// while they are being set.
ASSERT(ThreadStore::GetCurrentThread()->IsCurrentThreadInCooperativeMode());

return PalInterlockedCompareExchangePointer(&s_pBeginEndCallback, callback, nullptr) == nullptr;
}

bool ObjCMarshalNative::IsTrackedReference(_In_ Object * object, _Out_ bool* isReferenced)
{
*isReferenced = false;

if (!object->GetGCSafeMethodTable()->IsTrackedReferenceWithFinalizer())
return false;

auto pIsReferencedCallbackCallback = (ObjCMarshalNative::IsReferencedCallback)GetCallbackViaClasslibCallback(
object, ClasslibFunctionId::ObjectiveCMarshalGetIsTrackedReferenceCallback);

void* taggedMemory;
if (!TryGetTaggedMemory(object, &taggedMemory))
{
// It should not be possible to create a ref-counted handle
// without setting up the tagged memory first.
ASSERT(false);
return false;
}

int result = pIsReferencedCallbackCallback(taggedMemory);

*isReferenced = (result != 0);
return true;
}

void ObjCMarshalNative::BeforeRefCountedHandleCallbacks()
{
if (s_pBeginEndCallback != nullptr)
s_pBeginEndCallback();
}

void ObjCMarshalNative::AfterRefCountedHandleCallbacks()
{
if (s_pBeginEndCallback != nullptr)
s_pBeginEndCallback();
}

void ObjCMarshalNative::OnEnteredFinalizerQueue(_In_ Object * object)
{
auto pOnEnteredFinalizerQueueCallback = (ObjCMarshalNative::EnteredFinalizationCallback)GetCallbackViaClasslibCallback(
object, ClasslibFunctionId::ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback);

void* taggedMemory;
if (!TryGetTaggedMemory(object, &taggedMemory))
{
// Its possible to create an object that supports reference tracking
// without ever creating a ref-counted handle for it. In this case,
// there will be no tagged memory.
return;
}

pOnEnteredFinalizerQueueCallback(taggedMemory);
}

#endif // FEATURE_OBJCMARSHAL
Loading