Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
<CppLinker>$(_NdkAbi)-linux-android$(_NDKApiLevel)-clang$(_NdkWrapperScriptExt)</CppLinker>
<ObjCopyName>llvm-objcopy</ObjCopyName>

<!-- We must ensure this is `false`, as it would interfere with statically linking libc++ -->
<!-- NativeAOT Android links without the C++ standard library.
Minimal C++ runtime shims are provided by cxx-shims.cc in the runtime. -->
<LinkStandardCPlusPlusLibrary>false</LinkStandardCPlusPlusLibrary>
</PropertyGroup>

Expand Down Expand Up @@ -197,13 +198,9 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.

<_NdkLibs Include="@(RuntimePackAsset->WithMetadataValue('Filename', 'libnaot-android.$(Configuration.ToLower())-static-$(Configuration.ToLower())'))" />

<!-- Include libc++ -->
<_NdkLibs Include="$(_NdkSysrootDir)libc++_static.a" />
<_NdkLibs Include="$(_NdkSysrootDir)libc++abi.a" />

<LinkerArg Include="&quot;%(_NdkLibs.Identity)&quot;" />

<!-- This library conflicts with static libc++ -->
<!-- libstdc++compat.a brings in libc++ symbols; NativeAOT Android links without the C++ stdlib. -->
<NativeLibrary Remove="$(IlcSdkPath)libstdc++compat.a" />
<LinkerArg Remove="$(IlcSdkPath)libstdc++compat.a" />

Expand Down Expand Up @@ -362,12 +359,10 @@ This file contains the NativeAOT-specific MSBuild logic for .NET for Android.
<_NativeAotSystemLibraries Include="c" />
</ItemGroup>

<!-- When using workload linker, resolve libc++/libnaot from runtime pack dir.
<!-- When using workload linker, resolve libnaot from the runtime pack dir.
When using NDK, @(_NdkLibs) already has the right NDK sysroot paths. -->
<ItemGroup Condition=" '$(_AndroidUseWorkloadNativeLinker)' == 'true' ">
<_NativeAotLinkLibraries Include="@(RuntimePackAsset->WithMetadataValue('Filename', 'libnaot-android.$(Configuration.ToLower())-static-$(Configuration.ToLower())'))" />
<_NativeAotLinkLibraries Include="$(_NativeAotRuntimePackNativeDir)libc++_static.a" />
<_NativeAotLinkLibraries Include="$(_NativeAotRuntimePackNativeDir)libc++abi.a" />
</ItemGroup>
<ItemGroup Condition=" '$(_AndroidUseWorkloadNativeLinker)' != 'true' ">
<_NativeAotLinkLibraries Include="@(_NdkLibs)" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,6 @@ public NativeRuntimeComponents (ITaskItem[]? monoComponents)
new AndroidArchive ("libxa-shared-bits-release.a"),
new AndroidArchive ("libxamarin-startup-release.a"),

// C++ standard library
new CplusPlusArchive ("libc++_static.a"),
new CplusPlusArchive ("libc++abi.a"),

// LLVM clang built-ins archives
new ClangBuiltinsArchive ("aarch64"),
new ClangBuiltinsArchive ("arm"),
Expand Down
82 changes: 61 additions & 21 deletions src/native/clr/host/bridge-processing.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#include <cstdio>
#include <cstdlib>

#include <host/bridge-processing.hh>
#include <host/host.hh>
#include <host/runtime-util.hh>
Expand All @@ -17,9 +20,10 @@ void BridgeProcessingShared::initialize_on_runtime_init (JNIEnv *env, jclass run
abort_unless (GCUserPeer_class != nullptr && GCUserPeer_ctor != nullptr, "Failed to load mono.android.GCUserPeer!");
}

BridgeProcessingShared::BridgeProcessingShared (MarkCrossReferencesArgs *args) noexcept
BridgeProcessingShared::BridgeProcessingShared (MarkCrossReferencesArgs *args, const BridgeProcessingCallbacks *host_callbacks) noexcept
: env{ OSBridge::ensure_jnienv () },
cross_refs{ args }
cross_refs{ args },
callbacks{ host_callbacks != nullptr ? *host_callbacks : BridgeProcessingCallbacks {} }
{
if (args == nullptr) [[unlikely]] {
Helpers::abort_application (LOG_GC, "Cross references argument is a NULL pointer"sv);
Expand All @@ -32,6 +36,12 @@ BridgeProcessingShared::BridgeProcessingShared (MarkCrossReferencesArgs *args) n
if (args->CrossReferenceCount > 0 && args->CrossReferences == nullptr) [[unlikely]] {
Helpers::abort_application (LOG_GC, "CrossReferences member of the cross references arguments structure is NULL"sv);
}

}

BridgeProcessingShared::~BridgeProcessingShared () noexcept
{
release_temporary_peers ();
}

void BridgeProcessingShared::process () noexcept
Expand Down Expand Up @@ -59,9 +69,7 @@ void BridgeProcessingShared::prepare_for_java_collection () noexcept
}

// With cross references processed, the temporary peer list can be released
for (const auto& [scc, temporary_peer] : temporary_peers) {
env->DeleteLocalRef (temporary_peer);
}
release_temporary_peers ();

// Switch global to weak references
for (size_t i = 0; i < cross_refs->ComponentCount; i++) {
Expand All @@ -79,7 +87,13 @@ void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index,
{
// Count == 0 case: Some SCCs might have no IGCUserPeers associated with them, so we must create one
if (scc.Count == 0) {
temporary_peers [scc_index] = env->NewObject (GCUserPeer_class, GCUserPeer_ctor);
abort_unless (temporary_peers.find (scc_index) == temporary_peers.end (), "Temporary peer must not already exist");

jobject temporary_peer = env->NewObject (GCUserPeer_class, GCUserPeer_ctor);
abort_unless (temporary_peer != nullptr, "Failed to create GC bridge temporary peer");

auto [peer_entry, inserted] = temporary_peers.emplace (scc_index, temporary_peer);
abort_unless (inserted, "Temporary peer must not already exist");
return;
}

Expand All @@ -98,15 +112,27 @@ CrossReferenceTarget BridgeProcessingShared::select_cross_reference_target (size
const StronglyConnectedComponent &scc = cross_refs->Components [scc_index];

if (scc.Count == 0) {
const auto temporary_peer = temporary_peers.find (scc_index);
abort_unless (temporary_peer != temporary_peers.end(), "Temporary peer must be found in the map");
return { .is_temporary_peer = true, .temporary_peer = temporary_peer->second };
auto peer_entry = temporary_peers.find (scc_index);
abort_unless (peer_entry != temporary_peers.end (), "Temporary peer must be found in the map");
abort_unless (peer_entry->second != nullptr, "Temporary peer must not be null");
return { .is_temporary_peer = true, .temporary_peer = peer_entry->second };
}

abort_unless (scc.Contexts [0] != nullptr, "SCC must have at least one context");
return { .is_temporary_peer = false, .context = scc.Contexts [0] };
}

void BridgeProcessingShared::release_temporary_peers () noexcept
{
for (const auto &entry : temporary_peers) {
jobject temporary_peer = entry.second;
if (temporary_peer != nullptr) {
env->DeleteLocalRef (temporary_peer);
}
}
temporary_peers.clear ();
}

// caller must ensure that scc.Count > 1
void BridgeProcessingShared::add_circular_references (const StronglyConnectedComponent &scc) noexcept
{
Expand Down Expand Up @@ -219,6 +245,24 @@ void BridgeProcessingShared::clear_references (jobject handle) noexcept
env->CallVoidMethod (handle, clear_method_id);
}

bool BridgeProcessingShared::maybe_call_gc_user_peerable_add_managed_reference (JNIEnv *jni_env, jobject from, jobject to) noexcept
{
if (callbacks.maybe_call_gc_user_peerable_add_managed_reference == nullptr) {
return false;
}

return callbacks.maybe_call_gc_user_peerable_add_managed_reference (callbacks.context, jni_env, from, to);
}

bool BridgeProcessingShared::maybe_call_gc_user_peerable_clear_managed_references (JNIEnv *jni_env, jobject handle) noexcept
{
if (callbacks.maybe_call_gc_user_peerable_clear_managed_references == nullptr) {
return false;
}

return callbacks.maybe_call_gc_user_peerable_clear_managed_references (callbacks.context, jni_env, handle);
}

void BridgeProcessingShared::take_global_ref (HandleContext &context) noexcept
{
abort_unless (context.control_block != nullptr, "Control block must not be null");
Expand Down Expand Up @@ -317,31 +361,31 @@ void CrossReferenceTarget::mark_refs_added_if_needed () noexcept
[[gnu::always_inline]]
void BridgeProcessingShared::log_missing_add_references_method ([[maybe_unused]] jclass java_class) noexcept
{
log_error (LOG_DEFAULT, "Failed to find monodroidAddReferences method");
(log_error) (LOG_DEFAULT, "Failed to find monodroidAddReferences method");
#if DEBUG
abort_if_invalid_pointer_argument (java_class, "java_class");
if (!Logger::gc_spew_enabled ()) [[likely]] {
return;
}

char *class_name = Host::get_java_class_name_for_TypeManager (java_class);
log_error (LOG_GC, "Missing monodroidAddReferences method for object of class {}", optional_string (class_name));
(log_error) (LOG_GC, "Missing monodroidAddReferences method for object of class %s", optional_string (class_name));
free (class_name);
#endif
}

[[gnu::always_inline]]
void BridgeProcessingShared::log_missing_clear_references_method ([[maybe_unused]] jclass java_class) noexcept
{
log_error (LOG_DEFAULT, "Failed to find monodroidClearReferences method");
(log_error) (LOG_DEFAULT, "Failed to find monodroidClearReferences method");
#if DEBUG
abort_if_invalid_pointer_argument (java_class, "java_class");
if (!Logger::gc_spew_enabled ()) [[likely]] {
return;
}

char *class_name = Host::get_java_class_name_for_TypeManager (java_class);
log_error (LOG_GC, "Missing monodroidClearReferences method for object of class {}", optional_string (class_name));
(log_error) (LOG_GC, "Missing monodroidClearReferences method for object of class %s", optional_string (class_name));
free (class_name);
#endif
}
Expand All @@ -364,10 +408,7 @@ void BridgeProcessingShared::log_weak_to_gref (jobject weak, jobject handle) noe
return;
}

OSBridge::_monodroid_gref_log (
std::format ("take_global_ref wref={:#x} -> handle={:#x}\n"sv,
reinterpret_cast<intptr_t> (weak),
reinterpret_cast<intptr_t> (handle)).data ());
OSBridge::_monodroid_gref_log ("take_global_ref wref=%p -> handle=%p\n", reinterpret_cast<void*>(weak), reinterpret_cast<void*>(handle));
}

[[gnu::always_inline]]
Expand All @@ -377,8 +418,7 @@ void BridgeProcessingShared::log_weak_ref_collected (jobject weak) noexcept
return;
}

OSBridge::_monodroid_gref_log (
std::format ("handle {:#x}/W; was collected by a Java GC"sv, reinterpret_cast<intptr_t> (weak)).data ());
OSBridge::_monodroid_gref_log ("handle %p/W; was collected by a Java GC", reinterpret_cast<void*>(weak));
}

[[gnu::always_inline]]
Expand All @@ -388,7 +428,7 @@ void BridgeProcessingShared::log_take_weak_global_ref (jobject handle) noexcept
return;
}

OSBridge::_monodroid_gref_log (std::format ("take_weak_global_ref handle={:#x}\n"sv, reinterpret_cast<intptr_t> (handle)).data ());
OSBridge::_monodroid_gref_log ("take_weak_global_ref handle=%p\n", reinterpret_cast<void*>(handle));
}

[[gnu::always_inline]]
Expand Down Expand Up @@ -449,5 +489,5 @@ void BridgeProcessingShared::log_gc_summary () noexcept
}
}

log_info (LOG_GC, "GC cleanup summary: {} objects tested - resurrecting {}.", total, alive);
log_info_fmt (LOG_GC, "GC cleanup summary: %zu objects tested - resurrecting %zu.", total, alive);
}
10 changes: 5 additions & 5 deletions src/native/clr/host/fastdev-assemblies.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ auto FastDevAssemblies::open_assembly (std::string_view const& name, int64_t &si
{
size = 0;

std::string const& override_dir_path = AndroidSystem::get_primary_override_dir ();
const char *override_dir_path = AndroidSystem::get_primary_override_dir ();
if (!Util::dir_exists (override_dir_path)) [[unlikely]] {
log_debug (LOG_ASSEMBLY, "Override directory '{}' does not exist"sv, override_dir_path);
log_debug (LOG_ASSEMBLY, "Override directory '{}' does not exist"sv, optional_string (override_dir_path));
return nullptr;
}

Expand All @@ -29,9 +29,9 @@ auto FastDevAssemblies::open_assembly (std::string_view const& name, int64_t &si
if (override_dir_fd < 0) [[unlikely]] {
std::lock_guard dir_lock { override_dir_lock };
if (override_dir_fd < 0) [[likely]] {
override_dir = opendir (override_dir_path.c_str ());
override_dir = opendir (override_dir_path);
if (override_dir == nullptr) [[unlikely]] {
log_warn (LOG_ASSEMBLY, "Failed to open override dir '{}'. {}"sv, override_dir_path, strerror (errno));
log_warn (LOG_ASSEMBLY, "Failed to open override dir '{}'. {}"sv, optional_string (override_dir_path), strerror (errno));
return nullptr;
}
override_dir_fd = dirfd (override_dir);
Expand All @@ -42,7 +42,7 @@ auto FastDevAssemblies::open_assembly (std::string_view const& name, int64_t &si
LOG_ASSEMBLY,
"Attempting to load FastDev assembly '{}' from override directory '{}'"sv,
name,
override_dir_path
optional_string (override_dir_path)
);

if (!Util::file_exists (override_dir_fd, name)) {
Expand Down
36 changes: 26 additions & 10 deletions src/native/clr/host/gc-bridge.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#include <cerrno>
#include <cstdio>
#include <cstdlib>

#include <host/gc-bridge.hh>
#include <host/bridge-processing.hh>
#include <host/os-bridge.hh>
Expand Down Expand Up @@ -45,7 +49,7 @@ void GCBridge::trigger_java_gc (JNIEnv *env) noexcept

env->ExceptionDescribe ();
env->ExceptionClear ();
log_error (LOG_DEFAULT, "Java GC failed");
(log_error) (LOG_DEFAULT, "Java GC failed");
}

void GCBridge::mark_cross_references (MarkCrossReferencesArgs *args) noexcept
Expand All @@ -55,8 +59,9 @@ void GCBridge::mark_cross_references (MarkCrossReferencesArgs *args) noexcept
abort_unless (args->CrossReferences != nullptr || args->CrossReferenceCount == 0, "CrossReferences must not be null if CrossReferenceCount is greater than 0");
log_mark_cross_references_args_if_enabled (args);

shared_args.store (args);
shared_args_semaphore.release ();
__atomic_store_n (&shared_args, args, __ATOMIC_RELEASE);
int ret = sem_post (&shared_args_semaphore);
abort_unless (ret == 0, "Failed to release GC bridge semaphore");
}

void GCBridge::bridge_processing () noexcept
Expand All @@ -66,8 +71,13 @@ void GCBridge::bridge_processing () noexcept

while (true) {
// wait until mark cross references args are set by the GC callback
shared_args_semaphore.acquire ();
MarkCrossReferencesArgs *args = shared_args.load ();
int ret;
do {
ret = sem_wait (&shared_args_semaphore);
} while (ret == -1 && errno == EINTR);
abort_unless (ret == 0, "Failed to acquire GC bridge semaphore");

MarkCrossReferencesArgs *args = __atomic_load_n (&shared_args, __ATOMIC_ACQUIRE);

bridge_processing_started_callback (args);

Expand All @@ -78,20 +88,26 @@ void GCBridge::bridge_processing () noexcept
}
}

auto GCBridge::bridge_processing_thread_entry ([[maybe_unused]] void *arg) noexcept -> void*
{
bridge_processing ();
return nullptr;
}

[[gnu::always_inline]]
void GCBridge::log_mark_cross_references_args_if_enabled (MarkCrossReferencesArgs *args) noexcept
{
if (!Logger::gc_spew_enabled ()) [[likely]] {
return;
}

log_info (LOG_GC, "cross references callback invoked with {} sccs and {} xrefs.", args->ComponentCount, args->CrossReferenceCount);
log_info_fmt (LOG_GC, "cross references callback invoked with %zu sccs and %zu xrefs.", args->ComponentCount, args->CrossReferenceCount);

JNIEnv *env = OSBridge::ensure_jnienv ();

for (size_t i = 0; i < args->ComponentCount; ++i) {
const StronglyConnectedComponent &scc = args->Components [i];
log_info (LOG_GC, "group {} with {} objects", i, scc.Count);
log_info_fmt (LOG_GC, "group %zu with %zu objects", i, scc.Count);
for (size_t j = 0; j < scc.Count; ++j) {
log_handle_context (env, scc.Contexts [j]);
}
Expand All @@ -104,7 +120,7 @@ void GCBridge::log_mark_cross_references_args_if_enabled (MarkCrossReferencesArg
for (size_t i = 0; i < args->CrossReferenceCount; ++i) {
size_t source_index = args->CrossReferences [i].SourceGroupIndex;
size_t dest_index = args->CrossReferences [i].DestinationGroupIndex;
log_info_nocheck_fmt (LOG_GC, "xref [{}] {} -> {}", i, source_index, dest_index);
log_info_fmt (LOG_GC, "xref [%zu] %zu -> %zu", i, source_index, dest_index);
}
}

Expand All @@ -118,9 +134,9 @@ void GCBridge::log_handle_context (JNIEnv *env, HandleContext *ctx) noexcept
jclass java_class = env->GetObjectClass (handle);
if (java_class != nullptr) {
char *class_name = Host::get_java_class_name_for_TypeManager (java_class);
log_info (LOG_GC, "gref {:#x} [{}]", reinterpret_cast<intptr_t> (handle), class_name);
log_info_fmt (LOG_GC, "gref %p [%s]", reinterpret_cast<void*>(handle), optional_string (class_name));
free (class_name);
} else {
log_info (LOG_GC, "gref {:#x} [unknown class]", reinterpret_cast<intptr_t> (handle));
log_info_fmt (LOG_GC, "gref %p [unknown class]", reinterpret_cast<void*>(handle));
}
}
8 changes: 5 additions & 3 deletions src/native/clr/host/host-shared.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#include <cstdio>

#include <host/host.hh>
#include <host/os-bridge.hh>

Expand All @@ -12,13 +14,13 @@ auto HostCommon::get_java_class_name_for_TypeManager (jclass klass) noexcept ->
JNIEnv *env = OSBridge::ensure_jnienv ();
jstring name = reinterpret_cast<jstring> (env->CallObjectMethod (klass, Class_getName));
if (name == nullptr) {
log_error (LOG_DEFAULT, "Failed to obtain Java class name for object at {:p}", reinterpret_cast<void*>(klass));
log_write_fmt (LOG_DEFAULT, LogLevel::Error, "Failed to obtain Java class name for object at %p", reinterpret_cast<void*>(klass));
return nullptr;
}

const char *mutf8 = env->GetStringUTFChars (name, nullptr);
if (mutf8 == nullptr) {
log_error (LOG_DEFAULT, "Failed to convert Java class name to UTF8 (out of memory?)"sv);
log_write (LOG_DEFAULT, LogLevel::Error, "Failed to convert Java class name to UTF8 (out of memory?)");
env->DeleteLocalRef (name);
return nullptr;
}
Expand All @@ -34,4 +36,4 @@ auto HostCommon::get_java_class_name_for_TypeManager (jclass klass) noexcept ->
}

return ret;
}
}
Loading