From 97b70c19c9290c3f7aa62e325e233643f7ee1dbb Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 01:38:09 +0200 Subject: [PATCH 1/5] [NativeAOT] Reduce owning C++ standard library state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 187b207a-083b-461e-9071-e9aab61c9d07 --- src/native/CMakeLists.txt | 7 +- src/native/clr/host/bridge-processing.cc | 14 +- .../include/host/bridge-processing-shared.hh | 18 ++- .../include/runtime-base/android-system.hh | 41 ++++++ src/native/clr/runtime-base/logger.cc | 58 ++++++-- src/native/common/include/shared/cpp-util.hh | 138 +++++++++++++----- 6 files changed, 215 insertions(+), 61 deletions(-) diff --git a/src/native/CMakeLists.txt b/src/native/CMakeLists.txt index e2e44a7baec..88d7e2fead8 100644 --- a/src/native/CMakeLists.txt +++ b/src/native/CMakeLists.txt @@ -225,10 +225,7 @@ file(REAL_PATH "../../" REPO_ROOT_DIR) set(EXTERNAL_DIR "${REPO_ROOT_DIR}/external") set(JAVA_INTEROP_SRC_PATH "${EXTERNAL_DIR}/Java.Interop/src/java-interop") set(LIBUNWIND_SOURCE_DIR "${EXTERNAL_DIR}/libunwind") - -if(IS_MONO_RUNTIME) - set(ROBIN_MAP_DIR "${EXTERNAL_DIR}/robin-map") -endif() +set(ROBIN_MAP_DIR "${EXTERNAL_DIR}/robin-map") # # Include directories @@ -262,6 +259,7 @@ if(IS_CLR_RUNTIME) ${TARGET} PRIVATE ${XA_RUNTIME_INCLUDE_DIR} + ${ROBIN_MAP_DIR}/include ${RUNTIME_INCLUDE_DIR} ) endmacro() @@ -276,6 +274,7 @@ elseif(IS_NAOT_RUNTIME) ${XA_RUNTIME_INCLUDE_DIR} ${RUNTIME_INCLUDE_DIR} ${CLR_INCLUDE_DIR} + ${ROBIN_MAP_DIR}/include ) endmacro() else() diff --git a/src/native/clr/host/bridge-processing.cc b/src/native/clr/host/bridge-processing.cc index 91b772c25dd..9092a977c12 100644 --- a/src/native/clr/host/bridge-processing.cc +++ b/src/native/clr/host/bridge-processing.cc @@ -93,9 +93,10 @@ 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); + for (const auto &entry : temporary_peers) { + env->DeleteLocalRef (entry.second); } + temporary_peers.clear (); // Switch global to weak references for (size_t i = 0; i < cross_refs->ComponentCount; i++) { @@ -113,7 +114,11 @@ 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); + jobject temporary_peer = env->NewObject (GCUserPeer_class, GCUserPeer_ctor); + abort_unless (temporary_peer != nullptr, "Failed to create GC bridge temporary peer"); + + bool inserted = temporary_peers.emplace (scc_index, temporary_peer).second; + abort_unless (inserted, "Temporary peer must not already exist"); return; } @@ -133,7 +138,8 @@ CrossReferenceTarget BridgeProcessingShared::select_cross_reference_target (size 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"); + abort_unless (temporary_peer != temporary_peers.end (), "Temporary peer must be found in the map"); + abort_unless (temporary_peer->second != nullptr, "Temporary peer must not be null"); return { .is_temporary_peer = true, .temporary_peer = temporary_peer->second }; } diff --git a/src/native/clr/include/host/bridge-processing-shared.hh b/src/native/clr/include/host/bridge-processing-shared.hh index 70bef1ca4fc..86f418349bc 100644 --- a/src/native/clr/include/host/bridge-processing-shared.hh +++ b/src/native/clr/include/host/bridge-processing-shared.hh @@ -1,8 +1,20 @@ #pragma once +#include #include #include -#include + +// robin_map's no-exceptions fallback leaves its numeric_cast error message unused. +#if defined (__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#endif // __clang__ + +#include + +#if defined (__clang__) +#pragma clang diagnostic pop +#endif // __clang__ #include #include @@ -23,6 +35,8 @@ struct CrossReferenceTarget class BridgeProcessingShared { + using temporary_peer_map = tsl::robin_map; + public: explicit BridgeProcessingShared (MarkCrossReferencesArgs *args) noexcept; static void initialize_on_runtime_init (JNIEnv *jniEnv, jclass runtimeClass) noexcept; @@ -30,7 +44,7 @@ public: private: JNIEnv* env; MarkCrossReferencesArgs *cross_refs; - std::unordered_map temporary_peers; + temporary_peer_map temporary_peers; static inline jclass GCUserPeer_class = nullptr; static inline jmethodID GCUserPeer_ctor = nullptr; diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index b60871a93c4..092b96d79eb 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include #include #include @@ -18,6 +20,7 @@ struct BundledProperty; namespace xamarin::android { class AndroidSystem { +#if !defined (XA_HOST_NATIVEAOT) // This optimizes things a little bit. The array is allocated at build time, so we pay no cost for its // allocation and at run time it allows us to skip dynamic memory allocation. inline static std::array single_app_lib_directory{}; @@ -35,6 +38,7 @@ namespace xamarin::android { std::string_view { "x86_64" }, // CPU_KIND_X86_64 std::string_view { "riscv" }, // CPU_KIND_RISCV }; +#endif public: static auto get_gref_gc_threshold () noexcept -> long @@ -60,16 +64,33 @@ namespace xamarin::android { running_in_emulator = yesno; } +#if defined (XA_HOST_NATIVEAOT) + static auto get_primary_override_dir () noexcept -> const char* + { + return primary_override_dir; + } +#else static auto get_primary_override_dir () noexcept -> std::string const& { return primary_override_dir; } +#endif static void set_primary_override_dir (jstring_wrapper& home) noexcept { +#if defined (XA_HOST_NATIVEAOT) + determine_primary_override_dir (home, primary_override_dir, sizeof (primary_override_dir)); +#else primary_override_dir = determine_primary_override_dir (home); +#endif } +#if defined (XA_HOST_NATIVEAOT) + static auto get_native_libraries_dir () noexcept -> const char* + { + return native_libraries_dir; + } +#else static auto get_native_libraries_dir () noexcept -> std::string const& { return native_libraries_dir; @@ -94,6 +115,7 @@ namespace xamarin::android { log_debug (LOG_DEFAULT, "Creating public update directory: `{}`", override_dir); Util::create_public_directory (override_dir); } +#endif static auto is_embedded_dso_mode_enabled () noexcept -> bool { @@ -129,6 +151,19 @@ namespace xamarin::android { embedded_dso_mode_enabled = yesno; } +#if defined (XA_HOST_NATIVEAOT) + static void determine_primary_override_dir (jstring_wrapper &home, char *buffer, size_t buffer_size) noexcept + { + dynamic_local_string name { home.get_cstr () }; + name.append ("/") + .append (Constants::OVERRIDE_DIRECTORY_NAME) + .append ("/") + .append (Constants::android_lib_abi); + + abort_unless (name.length () < buffer_size, "Primary override directory path is too long"); + memcpy (buffer, name.get (), name.length () + 1); + } +#else static auto determine_primary_override_dir (jstring_wrapper &home) noexcept -> std::string { dynamic_local_string name { home.get_cstr () }; @@ -139,16 +174,22 @@ namespace xamarin::android { return {name.get (), name.length ()}; } +#endif private: static inline long max_gref_count = 0; static inline bool running_in_emulator = false; static inline bool embedded_dso_mode_enabled = false; +#if defined (XA_HOST_NATIVEAOT) + static inline char primary_override_dir[Constants::SENSIBLE_PATH_MAX] {}; + static inline char native_libraries_dir[Constants::SENSIBLE_PATH_MAX] {}; +#else static inline std::string primary_override_dir; static inline std::string native_libraries_dir; #if defined (DEBUG) static inline std::unordered_map bundled_properties; +#endif #endif }; } diff --git a/src/native/clr/runtime-base/logger.cc b/src/native/clr/runtime-base/logger.cc index a61b68d4a76..97e4d87d633 100644 --- a/src/native/clr/runtime-base/logger.cc +++ b/src/native/clr/runtime-base/logger.cc @@ -4,7 +4,6 @@ #include #include #include -#include #include #include @@ -22,10 +21,26 @@ using namespace xamarin::android; using std::operator""sv; namespace { - std::string gref_file{}; - std::string lref_file{}; + char *gref_file = nullptr; + char *lref_file = nullptr; bool light_gref = false; bool light_lref = false; + + void set_log_file (char *&log_file, std::string_view path) noexcept + { + char *new_log_file = nullptr; + if (!path.empty ()) { + size_t allocation_size = Helpers::add_with_overflow_check (path.length (), 1uz); + new_log_file = static_cast (std::malloc (allocation_size)); + abort_unless (new_log_file != nullptr, "Failed to allocate reference log file path"); + + memcpy (new_log_file, path.data (), path.length ()); + new_log_file [path.length ()] = '\0'; + } + + std::free (log_file); + log_file = new_log_file; + } } [[gnu::always_inline]] @@ -62,28 +77,39 @@ auto Logger::open_file (LogCategories category, std::string_view const& custom_p return log_and_return (ret, custom_path); } - std::string p{}; Util::create_public_directory (override_dir); - p.assign (override_dir); - p.append ("/"); - p.append (fallback_filename); + dynamic_local_string p; + p.append (override_dir) + .append ("/") + .append (fallback_filename); - return log_and_return (open_file (p), p); + std::string_view path = p.as_string_view (); + return log_and_return (open_file (path), path); } void Logger::init_reference_logging (std::string_view const& override_dir) noexcept { if ((log_categories & LOG_GREF) != 0 && !light_gref) { - _gref_log = open_file (LOG_GREF, gref_file, override_dir, "grefs.txt"sv); + _gref_log = open_file ( + LOG_GREF, + gref_file == nullptr ? std::string_view {} : std::string_view { gref_file }, + override_dir, + "grefs.txt"sv + ); } if ((log_categories & LOG_LREF) != 0 && !light_lref) { // if both lref & gref have files specified, and they're the same path, reuse the FILE*. - if (!lref_file.empty () && strcmp (lref_file.c_str (), !gref_file.empty () ? gref_file.c_str () : "") == 0) { + if (lref_file != nullptr && strcmp (lref_file, gref_file != nullptr ? gref_file : "") == 0) { _lref_log = _gref_log; } else { - _lref_log = open_file (LOG_LREF, lref_file, override_dir, "lrefs.txt"sv); + _lref_log = open_file ( + LOG_LREF, + lref_file == nullptr ? std::string_view {} : std::string_view { lref_file }, + override_dir, + "lrefs.txt"sv + ); } } } @@ -158,20 +184,20 @@ Logger::init_logging_categories () noexcept continue; } - auto get_log_file_name = [](std::string_view const& file_kind, string_segment const& segment, size_t offset) -> const char* { + auto get_log_file_name = [](std::string_view const& file_kind, string_segment const& segment, size_t offset) -> std::string_view { auto file_name = segment.at (offset); if (!file_name.has_value ()) { log_warn (LOG_DEFAULT, "Unable to set path to {} log file: {}", file_kind, to_string (file_name.error ())); - return nullptr; + return {}; } - return file_name.value (); + return { file_name.value (), segment.length () - offset }; }; constexpr std::string_view CAT_GREF_EQUALS { "gref=" }; if (set_category (CAT_GREF_EQUALS, param, LOG_GREF, true /* arg_starts_with_name */)) { - gref_file = get_log_file_name ("gref"sv, param, CAT_GREF_EQUALS.length ()); + set_log_file (gref_file, get_log_file_name ("gref"sv, param, CAT_GREF_EQUALS.length ())); continue; } @@ -187,7 +213,7 @@ Logger::init_logging_categories () noexcept constexpr std::string_view CAT_LREF_EQUALS { "lref=" }; if (set_category (CAT_LREF_EQUALS, param, LOG_LREF, true /* arg_starts_with_name */)) { - lref_file = get_log_file_name ("lref"sv, param, CAT_LREF_EQUALS.length ()); + set_log_file (lref_file, get_log_file_name ("lref"sv, param, CAT_LREF_EQUALS.length ())); continue; } diff --git a/src/native/common/include/shared/cpp-util.hh b/src/native/common/include/shared/cpp-util.hh index 6693ac69793..eb6458c7dd0 100644 --- a/src/native/common/include/shared/cpp-util.hh +++ b/src/native/common/include/shared/cpp-util.hh @@ -6,12 +6,9 @@ #include #include #include -#include #include -#include #include #include -#include #include #include @@ -33,48 +30,115 @@ namespace xamarin::android::detail { return ret == -1 ? "Out of memory" : message; } - [[gnu::always_inline]] - static inline std::string get_function_name (const char *signature) + static inline constexpr size_t find_function_parameter_list (std::string_view signature) noexcept { using std::operator""sv; - std::string_view sig { signature }; - if (sig.length () == 0) { - return ""; + size_t search_end = signature.find (" ["sv); + if (search_end == std::string_view::npos) { + search_end = signature.length (); + } + + if (search_end == 0) { + return std::string_view::npos; + } + + size_t close_pos = signature.rfind (')', search_end - 1); + if (close_pos == std::string_view::npos) { + return std::string_view::npos; + } + + size_t depth = 0; + for (size_t pos = close_pos + 1; pos > 0;) { + char ch = signature [--pos]; + if (ch == ')') { + depth++; + } else if (ch == '(') { + if (depth == 0) [[unlikely]] { + return std::string_view::npos; + } + + depth--; + if (depth == 0) { + return pos; + } + } + } + + return std::string_view::npos; + } + + static inline constexpr size_t find_function_component_start (std::string_view signature, size_t component_end) noexcept + { + size_t nesting_depth = 0; + + for (size_t pos = component_end; pos > 0;) { + char ch = signature [--pos]; + if (ch == ')' || ch == '>' || ch == ']') { + nesting_depth++; + } else if (ch == '(' || ch == '<' || ch == '[') { + if (nesting_depth > 0) { + nesting_depth--; + } + } else if (ch == ' ' && nesting_depth == 0) { + return pos + 1; + } } - auto splitSignature = sig | std::views::split ("::"sv) | std::ranges::to> (); + return 0; + } + + static inline constexpr std::string_view get_function_name (const char *signature) noexcept + { + using std::operator""sv; + + if (signature == nullptr || *signature == '\0') { + return ""sv; + } - std::string ret; - if (splitSignature.size () > 1) { - ret.append (splitSignature [splitSignature.size () - 2]); - ret.append ("::"sv); + std::string_view sig { signature }; + size_t name_end = find_function_parameter_list (sig); + if (name_end == std::string_view::npos) { + name_end = sig.length (); } - std::string_view func_name { splitSignature[splitSignature.size () - 1] }; - std::string_view::size_type args_pos = func_name.find ('('); - std::string_view::size_type name_start_pos = func_name.find (' '); - - if (name_start_pos == std::string_view::npos) { - name_start_pos = 0; - } else { - name_start_pos++; // point to after the space which separates return type from name - if (name_start_pos >= func_name.length ()) [[unlikely]] { - name_start_pos = 0; + + size_t operator_pos = sig.rfind ("operator"sv, name_end); + if (operator_pos != std::string_view::npos) { + auto is_identifier_character = [](char ch) constexpr { + return (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '_'; + }; + + size_t operator_end = operator_pos + "operator"sv.length (); + if ((operator_pos > 0 && is_identifier_character (sig [operator_pos - 1])) || + (operator_end < name_end && is_identifier_character (sig [operator_end]))) { + operator_pos = std::string_view::npos; } } - if (args_pos == std::string_view::npos) { - ret.append (func_name.substr (name_start_pos)); - } else { - // If there's a snafu with positions, start from 0 - if (name_start_pos >= args_pos || name_start_pos > func_name.length ()) [[unlikely]] { - name_start_pos = 0; + size_t scope_pos = sig.rfind ("::"sv, operator_pos == std::string_view::npos ? name_end : operator_pos); + bool have_scoped_operator = operator_pos != std::string_view::npos && + scope_pos != std::string_view::npos && + scope_pos + 2 == operator_pos; + + size_t qualified_name_start = find_function_component_start (sig, have_scoped_operator ? scope_pos : name_end); + size_t name_start = qualified_name_start; + if (operator_pos != std::string_view::npos && !have_scoped_operator) { + name_start = operator_pos; + } else if (scope_pos != std::string_view::npos && scope_pos >= qualified_name_start) { + size_t previous_scope_pos = scope_pos == 0 ? std::string_view::npos : sig.rfind ("::"sv, scope_pos - 1); + if (previous_scope_pos != std::string_view::npos && previous_scope_pos >= qualified_name_start) { + name_start = previous_scope_pos + 2; } + } - ret.append (func_name.substr (name_start_pos, args_pos - name_start_pos)); + if (name_start >= name_end || name_start > sig.length ()) [[unlikely]] { + name_start = 0; } - return ret; + return sig.substr (name_start, name_end - name_start); } } @@ -110,9 +174,11 @@ abort_if_invalid_pointer_argument (T *ptr, const char *ptr_name, std::source_loc abort_unless ( ptr != nullptr, [&ptr_name, &sloc] { + std::string_view function_name = xamarin::android::detail::get_function_name (sloc.function_name ()); return xamarin::android::detail::_format_message ( - "%s: parameter '%s' must be a valid pointer", - xamarin::android::detail::get_function_name (sloc.function_name ()).c_str (), + "%.*s: parameter '%s' must be a valid pointer", + static_cast(function_name.length ()), + function_name.data (), ptr_name ); }, @@ -127,9 +193,11 @@ abort_if_negative_integer_argument (int arg, const char *arg_name, std::source_l abort_unless ( arg > 0, [&arg_name, &sloc] { + std::string_view function_name = xamarin::android::detail::get_function_name (sloc.function_name ()); return xamarin::android::detail::_format_message ( - "%s: parameter '%s' must be a positive integer", - xamarin::android::detail::get_function_name (sloc.function_name ()).c_str (), + "%.*s: parameter '%s' must be a positive integer", + static_cast(function_name.length ()), + function_name.data (), arg_name ); }, From 01b33aa8d98161ea25e7cb427156c4c670d392e4 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 09:03:00 +0200 Subject: [PATCH 2/5] [NativeAOT] Replace robin map temporary peer storage Use the negative-index TemporaryPeerMap approach from #11311 so CoreCLR and NativeAOT no longer need robin-map for GC bridge processing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- src/native/CMakeLists.txt | 7 +- src/native/clr/host/bridge-processing.cc | 199 +++++++++++++----- .../include/host/bridge-processing-shared.hh | 57 +++-- 3 files changed, 183 insertions(+), 80 deletions(-) diff --git a/src/native/CMakeLists.txt b/src/native/CMakeLists.txt index 88d7e2fead8..e2e44a7baec 100644 --- a/src/native/CMakeLists.txt +++ b/src/native/CMakeLists.txt @@ -225,7 +225,10 @@ file(REAL_PATH "../../" REPO_ROOT_DIR) set(EXTERNAL_DIR "${REPO_ROOT_DIR}/external") set(JAVA_INTEROP_SRC_PATH "${EXTERNAL_DIR}/Java.Interop/src/java-interop") set(LIBUNWIND_SOURCE_DIR "${EXTERNAL_DIR}/libunwind") -set(ROBIN_MAP_DIR "${EXTERNAL_DIR}/robin-map") + +if(IS_MONO_RUNTIME) + set(ROBIN_MAP_DIR "${EXTERNAL_DIR}/robin-map") +endif() # # Include directories @@ -259,7 +262,6 @@ if(IS_CLR_RUNTIME) ${TARGET} PRIVATE ${XA_RUNTIME_INCLUDE_DIR} - ${ROBIN_MAP_DIR}/include ${RUNTIME_INCLUDE_DIR} ) endmacro() @@ -274,7 +276,6 @@ elseif(IS_NAOT_RUNTIME) ${XA_RUNTIME_INCLUDE_DIR} ${RUNTIME_INCLUDE_DIR} ${CLR_INCLUDE_DIR} - ${ROBIN_MAP_DIR}/include ) endmacro() else() diff --git a/src/native/clr/host/bridge-processing.cc b/src/native/clr/host/bridge-processing.cc index 9092a977c12..ad7bb02639b 100644 --- a/src/native/clr/host/bridge-processing.cc +++ b/src/native/clr/host/bridge-processing.cc @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -6,15 +8,126 @@ using namespace xamarin::android; -void BridgeProcessingShared::initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept +TemporaryPeerMap::TemporaryPeerMap (JNIEnv *jni_env, MarkCrossReferencesArgs *args) noexcept + : env{ jni_env }, + cross_refs{ args } +{ + size_t map_capacity = 0; + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + const StronglyConnectedComponent &scc = cross_refs->Components [i]; + abort_unless (!is_temporary_peer_index (scc.Count), "SCC count must not use the temporary peer marker bit"); + if (scc.Count == 0) { + map_capacity = Helpers::add_with_overflow_check (map_capacity, 1); + } + } + + if (map_capacity == 0) { + return; + } + + constexpr size_t local_ref_slack = 16; + constexpr size_t max_jint = static_cast (0x7fffffff); + size_t desired_capacity = Helpers::add_with_overflow_check (map_capacity, local_ref_slack); + jint requested_capacity = static_cast (desired_capacity > max_jint ? max_jint : desired_capacity); + + if (env->EnsureLocalCapacity (requested_capacity) != JNI_OK) [[unlikely]] { + env->ExceptionClear (); + log_warn (LOG_GC, "Failed to reserve JNI local reference capacity for {} temporary peers", map_capacity); + } + + capacity = map_capacity; + peers = static_cast (std::calloc (capacity, sizeof (jobject))); + abort_unless (peers != nullptr, "Failed to allocate GC bridge temporary peer map"); +} + +TemporaryPeerMap::~TemporaryPeerMap () noexcept +{ + if (peers == nullptr) { + return; + } + + for (size_t i = 0; i < count; i++) { + jobject temporary_peer = peers [i]; + if (temporary_peer != nullptr) { + env->DeleteLocalRef (temporary_peer); + peers [i] = nullptr; + } + } + + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + StronglyConnectedComponent &scc = cross_refs->Components [i]; + if (is_temporary_peer_index (scc.Count)) { + scc.Count = 0; + } + } + + count = 0; + std::free (peers); + peers = nullptr; + capacity = 0; +} + +void TemporaryPeerMap::initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept { abort_if_invalid_pointer_argument (env, "env"); abort_if_invalid_pointer_argument (runtimeClass, "runtimeClass"); - GCUserPeer_class = RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "mono_android_GCUserPeer", true); - GCUserPeer_ctor = env->GetMethodID (GCUserPeer_class, "", "()V"); + peer_class = RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "mono_android_GCUserPeer", true); + abort_unless (peer_class != nullptr, "Failed to load mono.android.GCUserPeer!"); + + peer_ctor = env->GetMethodID (peer_class, "", "()V"); + abort_unless (peer_ctor != nullptr, "Failed to load mono.android.GCUserPeer constructor!"); +} + +void TemporaryPeerMap::add (StronglyConnectedComponent &scc) noexcept +{ + abort_unless (peers != nullptr, "Temporary peer map must not be null"); + abort_unless (count < capacity, "Temporary peer map must not be full"); + + jobject temporary_peer = env->NewObject (peer_class, peer_ctor); + abort_unless (temporary_peer != nullptr, "Failed to create GC bridge temporary peer"); + + size_t temporary_peer_index = count++; + peers [temporary_peer_index] = temporary_peer; + scc.Count = encode_temporary_peer_index (temporary_peer_index); +} + +bool TemporaryPeerMap::has_temporary_peer (const StronglyConnectedComponent &scc) const noexcept +{ + return is_temporary_peer_index (scc.Count); +} + +jobject TemporaryPeerMap::get (const StronglyConnectedComponent &scc) const noexcept +{ + size_t temporary_peer_index = decode_temporary_peer_index (scc.Count); + abort_unless (temporary_peer_index < count, "Temporary peer index must be in range"); + + return peers [temporary_peer_index]; +} + +bool TemporaryPeerMap::is_temporary_peer_index (size_t count) noexcept +{ + return (count & temporary_peer_index_sign_bit) != 0; +} + +size_t TemporaryPeerMap::encode_temporary_peer_index (size_t index) noexcept +{ + abort_unless (!is_temporary_peer_index (index), "Temporary peer index is too large"); + return ~index; +} + +size_t TemporaryPeerMap::decode_temporary_peer_index (size_t count) noexcept +{ + abort_unless (is_temporary_peer_index (count), "Temporary peer index must be negative"); + return ~count; +} + +void BridgeProcessingShared::initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept +{ + abort_if_invalid_pointer_argument (env, "env"); + abort_if_invalid_pointer_argument (runtimeClass, "runtimeClass"); - abort_unless (GCUserPeer_class != nullptr && GCUserPeer_ctor != nullptr, "Failed to load mono.android.GCUserPeer!"); + TemporaryPeerMap::initialize_on_runtime_init (env, runtimeClass); // Cache the IGCUserPeer interface method IDs once, instead of resolving them per reference edge. IGCUserPeer_class = RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "mono_android_IGCUserPeer", true); @@ -55,70 +168,45 @@ void BridgeProcessingShared::process () noexcept void BridgeProcessingShared::prepare_for_java_collection () noexcept { - // Each SCC with no IGCUserPeers is represented by a temporary peer held as a JNI local - // reference that must stay alive until every cross reference has been added. Reserve enough - // local reference capacity up front so that a large number of such SCCs cannot overflow the - // JNI local reference table (which only guarantees 16 slots by default). - size_t temporary_peer_count = 0; - for (size_t i = 0; i < cross_refs->ComponentCount; i++) { - if (cross_refs->Components [i].Count == 0) { - temporary_peer_count = Helpers::add_with_overflow_check (temporary_peer_count, 1); - } - } + prepare_sccs_and_cross_references_for_java_collection (); - if (temporary_peer_count > 0) { - constexpr size_t local_ref_slack = 16; - constexpr size_t max_jint = static_cast (0x7fffffff); - size_t desired_capacity = Helpers::add_with_overflow_check (temporary_peer_count, local_ref_slack); - jint requested_capacity = static_cast (desired_capacity > max_jint ? max_jint : desired_capacity); + // Temporary peer indexes have been reset, so SCC counts are safe to use normally again. + // Switch global to weak references + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + const StronglyConnectedComponent &scc = cross_refs->Components [i]; + for (size_t j = 0; j < scc.Count; j++) { + const HandleContext *context = scc.Contexts [j]; + abort_unless (context != nullptr, "Context must not be null"); - if (env->EnsureLocalCapacity (requested_capacity) != JNI_OK) [[unlikely]] { - env->ExceptionClear (); - log_warn (LOG_GC, "Failed to reserve JNI local reference capacity for {} temporary peers", temporary_peer_count); + take_weak_global_ref (*context); } } +} + +void BridgeProcessingShared::prepare_sccs_and_cross_references_for_java_collection () noexcept +{ + TemporaryPeerMap temporary_peers { env, cross_refs }; // Before looking at xrefs, scan the SCCs. During collection, an SCC has to behave like a // single object. If the number of objects in the SCC is anything other than 1, the SCC // must be doctored to mimic that one-object nature. for (size_t i = 0; i < cross_refs->ComponentCount; i++) { const StronglyConnectedComponent &scc = cross_refs->Components [i]; - prepare_scc_for_java_collection (i, scc); + prepare_scc_for_java_collection (i, scc, temporary_peers); } // Add the cross scc refs for (size_t i = 0; i < cross_refs->CrossReferenceCount; i++) { const ComponentCrossReference &xref = cross_refs->CrossReferences [i]; - add_cross_reference (xref.SourceGroupIndex, xref.DestinationGroupIndex); - } - - // With cross references processed, the temporary peer list can be released - for (const auto &entry : temporary_peers) { - env->DeleteLocalRef (entry.second); - } - temporary_peers.clear (); - - // Switch global to weak references - for (size_t i = 0; i < cross_refs->ComponentCount; i++) { - const StronglyConnectedComponent &scc = cross_refs->Components [i]; - for (size_t j = 0; j < scc.Count; j++) { - const HandleContext *context = scc.Contexts [j]; - abort_unless (context != nullptr, "Context must not be null"); - - take_weak_global_ref (*context); - } + add_cross_reference (xref.SourceGroupIndex, xref.DestinationGroupIndex, temporary_peers); } } -void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc) noexcept +void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc, TemporaryPeerMap &temporary_peers) noexcept { // Count == 0 case: Some SCCs might have no IGCUserPeers associated with them, so we must create one if (scc.Count == 0) { - jobject temporary_peer = env->NewObject (GCUserPeer_class, GCUserPeer_ctor); - abort_unless (temporary_peer != nullptr, "Failed to create GC bridge temporary peer"); - - bool inserted = temporary_peers.emplace (scc_index, temporary_peer).second; - abort_unless (inserted, "Temporary peer must not already exist"); + temporary_peers.add (cross_refs->Components [scc_index]); return; } @@ -132,15 +220,14 @@ void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, add_circular_references (scc); } -CrossReferenceTarget BridgeProcessingShared::select_cross_reference_target (size_t scc_index) noexcept +CrossReferenceTarget BridgeProcessingShared::select_cross_reference_target (size_t scc_index, TemporaryPeerMap &temporary_peers) noexcept { 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"); - abort_unless (temporary_peer->second != nullptr, "Temporary peer must not be null"); - return { .is_temporary_peer = true, .temporary_peer = temporary_peer->second }; + if (temporary_peers.has_temporary_peer (scc)) { + jobject temporary_peer = temporary_peers.get (scc); + abort_unless (temporary_peer != nullptr, "Temporary peer must not be null"); + return { .is_temporary_peer = true, .temporary_peer = temporary_peer }; } abort_unless (scc.Contexts [0] != nullptr, "SCC must have at least one context"); @@ -182,10 +269,10 @@ void BridgeProcessingShared::add_circular_references (const StronglyConnectedCom } } -void BridgeProcessingShared::add_cross_reference (size_t source_index, size_t dest_index) noexcept +void BridgeProcessingShared::add_cross_reference (size_t source_index, size_t dest_index, TemporaryPeerMap &temporary_peers) noexcept { - CrossReferenceTarget from = select_cross_reference_target (source_index); - CrossReferenceTarget to = select_cross_reference_target (dest_index); + CrossReferenceTarget from = select_cross_reference_target (source_index, temporary_peers); + CrossReferenceTarget to = select_cross_reference_target (dest_index, temporary_peers); if (add_reference (from.get_handle(), to.get_handle())) { from.mark_refs_added_if_needed (); diff --git a/src/native/clr/include/host/bridge-processing-shared.hh b/src/native/clr/include/host/bridge-processing-shared.hh index 86f418349bc..0ee2c1dafbf 100644 --- a/src/native/clr/include/host/bridge-processing-shared.hh +++ b/src/native/clr/include/host/bridge-processing-shared.hh @@ -4,18 +4,6 @@ #include #include -// robin_map's no-exceptions fallback leaves its numeric_cast error message unused. -#if defined (__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-parameter" -#endif // __clang__ - -#include - -#if defined (__clang__) -#pragma clang diagnostic pop -#endif // __clang__ - #include #include #include @@ -33,10 +21,40 @@ struct CrossReferenceTarget void mark_refs_added_if_needed () noexcept; }; -class BridgeProcessingShared +class TemporaryPeerMap { - using temporary_peer_map = tsl::robin_map; +public: + explicit TemporaryPeerMap (JNIEnv *env, MarkCrossReferencesArgs *cross_refs) noexcept; + ~TemporaryPeerMap () noexcept; + + static void initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept; + + void add (StronglyConnectedComponent &scc) noexcept; + bool has_temporary_peer (const StronglyConnectedComponent &scc) const noexcept; + jobject get (const StronglyConnectedComponent &scc) const noexcept; + +private: + // Count is unsigned, so encode the temporary peer index as ~index. This stores the same bit + // pattern as -(index + 1), giving us a sign bit marker while preserving index 0. + // The destructor resets every marker before returning cross_refs to the runtime. + static constexpr size_t temporary_peer_index_sign_bit = ~(~size_t { 0 } >> 1); + static bool is_temporary_peer_index (size_t count) noexcept; + static size_t encode_temporary_peer_index (size_t index) noexcept; + static size_t decode_temporary_peer_index (size_t count) noexcept; + + static inline jclass peer_class = nullptr; + static inline jmethodID peer_ctor = nullptr; + + JNIEnv *env; + MarkCrossReferencesArgs *cross_refs; + jobject *peers {}; + size_t count {}; + size_t capacity {}; +}; + +class BridgeProcessingShared +{ public: explicit BridgeProcessingShared (MarkCrossReferencesArgs *args) noexcept; static void initialize_on_runtime_init (JNIEnv *jniEnv, jclass runtimeClass) noexcept; @@ -44,10 +62,6 @@ public: private: JNIEnv* env; MarkCrossReferencesArgs *cross_refs; - temporary_peer_map temporary_peers; - - static inline jclass GCUserPeer_class = nullptr; - static inline jmethodID GCUserPeer_ctor = nullptr; // Cached `mono.android.IGCUserPeer` interface and its methods. The method IDs are looked up // once from the interface class and are valid for virtual dispatch on every implementing peer, @@ -57,12 +71,13 @@ private: static inline jmethodID IGCUserPeer_monodroidClearReferences = nullptr; void prepare_for_java_collection () noexcept; - void prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc) noexcept; + void prepare_sccs_and_cross_references_for_java_collection () noexcept; + void prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc, TemporaryPeerMap &temporary_peers) noexcept; void take_weak_global_ref (const HandleContext &context) noexcept; void add_circular_references (const StronglyConnectedComponent &scc) noexcept; - void add_cross_reference (size_t source_index, size_t dest_index) noexcept; - CrossReferenceTarget select_cross_reference_target (size_t scc_index) noexcept; + void add_cross_reference (size_t source_index, size_t dest_index, TemporaryPeerMap &temporary_peers) noexcept; + CrossReferenceTarget select_cross_reference_target (size_t scc_index, TemporaryPeerMap &temporary_peers) noexcept; bool add_reference (jobject from, jobject to) noexcept; void cleanup_after_java_collection () noexcept; From 3a7404650a3b8b7dc6e68fc9a8d622a80993ed02 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 09:23:26 +0200 Subject: [PATCH 3/5] [NativeAOT] Split GC bridge cleanup into separate PR Move the TemporaryPeerMap changes to #12145 so this branch only contains the remaining owning-state cleanup. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- src/native/clr/host/bridge-processing.cc | 193 +++++------------- .../include/host/bridge-processing-shared.hh | 45 +--- 2 files changed, 58 insertions(+), 180 deletions(-) diff --git a/src/native/clr/host/bridge-processing.cc b/src/native/clr/host/bridge-processing.cc index ad7bb02639b..91b772c25dd 100644 --- a/src/native/clr/host/bridge-processing.cc +++ b/src/native/clr/host/bridge-processing.cc @@ -1,5 +1,3 @@ -#include - #include #include #include @@ -8,126 +6,15 @@ using namespace xamarin::android; -TemporaryPeerMap::TemporaryPeerMap (JNIEnv *jni_env, MarkCrossReferencesArgs *args) noexcept - : env{ jni_env }, - cross_refs{ args } -{ - size_t map_capacity = 0; - for (size_t i = 0; i < cross_refs->ComponentCount; i++) { - const StronglyConnectedComponent &scc = cross_refs->Components [i]; - abort_unless (!is_temporary_peer_index (scc.Count), "SCC count must not use the temporary peer marker bit"); - if (scc.Count == 0) { - map_capacity = Helpers::add_with_overflow_check (map_capacity, 1); - } - } - - if (map_capacity == 0) { - return; - } - - constexpr size_t local_ref_slack = 16; - constexpr size_t max_jint = static_cast (0x7fffffff); - size_t desired_capacity = Helpers::add_with_overflow_check (map_capacity, local_ref_slack); - jint requested_capacity = static_cast (desired_capacity > max_jint ? max_jint : desired_capacity); - - if (env->EnsureLocalCapacity (requested_capacity) != JNI_OK) [[unlikely]] { - env->ExceptionClear (); - log_warn (LOG_GC, "Failed to reserve JNI local reference capacity for {} temporary peers", map_capacity); - } - - capacity = map_capacity; - peers = static_cast (std::calloc (capacity, sizeof (jobject))); - abort_unless (peers != nullptr, "Failed to allocate GC bridge temporary peer map"); -} - -TemporaryPeerMap::~TemporaryPeerMap () noexcept -{ - if (peers == nullptr) { - return; - } - - for (size_t i = 0; i < count; i++) { - jobject temporary_peer = peers [i]; - if (temporary_peer != nullptr) { - env->DeleteLocalRef (temporary_peer); - peers [i] = nullptr; - } - } - - for (size_t i = 0; i < cross_refs->ComponentCount; i++) { - StronglyConnectedComponent &scc = cross_refs->Components [i]; - if (is_temporary_peer_index (scc.Count)) { - scc.Count = 0; - } - } - - count = 0; - std::free (peers); - peers = nullptr; - capacity = 0; -} - -void TemporaryPeerMap::initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept -{ - abort_if_invalid_pointer_argument (env, "env"); - abort_if_invalid_pointer_argument (runtimeClass, "runtimeClass"); - - peer_class = RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "mono_android_GCUserPeer", true); - abort_unless (peer_class != nullptr, "Failed to load mono.android.GCUserPeer!"); - - peer_ctor = env->GetMethodID (peer_class, "", "()V"); - abort_unless (peer_ctor != nullptr, "Failed to load mono.android.GCUserPeer constructor!"); -} - -void TemporaryPeerMap::add (StronglyConnectedComponent &scc) noexcept -{ - abort_unless (peers != nullptr, "Temporary peer map must not be null"); - abort_unless (count < capacity, "Temporary peer map must not be full"); - - jobject temporary_peer = env->NewObject (peer_class, peer_ctor); - abort_unless (temporary_peer != nullptr, "Failed to create GC bridge temporary peer"); - - size_t temporary_peer_index = count++; - peers [temporary_peer_index] = temporary_peer; - scc.Count = encode_temporary_peer_index (temporary_peer_index); -} - -bool TemporaryPeerMap::has_temporary_peer (const StronglyConnectedComponent &scc) const noexcept -{ - return is_temporary_peer_index (scc.Count); -} - -jobject TemporaryPeerMap::get (const StronglyConnectedComponent &scc) const noexcept -{ - size_t temporary_peer_index = decode_temporary_peer_index (scc.Count); - abort_unless (temporary_peer_index < count, "Temporary peer index must be in range"); - - return peers [temporary_peer_index]; -} - -bool TemporaryPeerMap::is_temporary_peer_index (size_t count) noexcept -{ - return (count & temporary_peer_index_sign_bit) != 0; -} - -size_t TemporaryPeerMap::encode_temporary_peer_index (size_t index) noexcept -{ - abort_unless (!is_temporary_peer_index (index), "Temporary peer index is too large"); - return ~index; -} - -size_t TemporaryPeerMap::decode_temporary_peer_index (size_t count) noexcept -{ - abort_unless (is_temporary_peer_index (count), "Temporary peer index must be negative"); - return ~count; -} - void BridgeProcessingShared::initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept { abort_if_invalid_pointer_argument (env, "env"); abort_if_invalid_pointer_argument (runtimeClass, "runtimeClass"); - TemporaryPeerMap::initialize_on_runtime_init (env, runtimeClass); + GCUserPeer_class = RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "mono_android_GCUserPeer", true); + GCUserPeer_ctor = env->GetMethodID (GCUserPeer_class, "", "()V"); + + abort_unless (GCUserPeer_class != nullptr && GCUserPeer_ctor != nullptr, "Failed to load mono.android.GCUserPeer!"); // Cache the IGCUserPeer interface method IDs once, instead of resolving them per reference edge. IGCUserPeer_class = RuntimeUtil::get_class_from_runtime_field (env, runtimeClass, "mono_android_IGCUserPeer", true); @@ -168,45 +55,65 @@ void BridgeProcessingShared::process () noexcept void BridgeProcessingShared::prepare_for_java_collection () noexcept { - prepare_sccs_and_cross_references_for_java_collection (); - - // Temporary peer indexes have been reset, so SCC counts are safe to use normally again. - // Switch global to weak references + // Each SCC with no IGCUserPeers is represented by a temporary peer held as a JNI local + // reference that must stay alive until every cross reference has been added. Reserve enough + // local reference capacity up front so that a large number of such SCCs cannot overflow the + // JNI local reference table (which only guarantees 16 slots by default). + size_t temporary_peer_count = 0; for (size_t i = 0; i < cross_refs->ComponentCount; i++) { - const StronglyConnectedComponent &scc = cross_refs->Components [i]; - for (size_t j = 0; j < scc.Count; j++) { - const HandleContext *context = scc.Contexts [j]; - abort_unless (context != nullptr, "Context must not be null"); - - take_weak_global_ref (*context); + if (cross_refs->Components [i].Count == 0) { + temporary_peer_count = Helpers::add_with_overflow_check (temporary_peer_count, 1); } } -} -void BridgeProcessingShared::prepare_sccs_and_cross_references_for_java_collection () noexcept -{ - TemporaryPeerMap temporary_peers { env, cross_refs }; + if (temporary_peer_count > 0) { + constexpr size_t local_ref_slack = 16; + constexpr size_t max_jint = static_cast (0x7fffffff); + size_t desired_capacity = Helpers::add_with_overflow_check (temporary_peer_count, local_ref_slack); + jint requested_capacity = static_cast (desired_capacity > max_jint ? max_jint : desired_capacity); + + if (env->EnsureLocalCapacity (requested_capacity) != JNI_OK) [[unlikely]] { + env->ExceptionClear (); + log_warn (LOG_GC, "Failed to reserve JNI local reference capacity for {} temporary peers", temporary_peer_count); + } + } // Before looking at xrefs, scan the SCCs. During collection, an SCC has to behave like a // single object. If the number of objects in the SCC is anything other than 1, the SCC // must be doctored to mimic that one-object nature. for (size_t i = 0; i < cross_refs->ComponentCount; i++) { const StronglyConnectedComponent &scc = cross_refs->Components [i]; - prepare_scc_for_java_collection (i, scc, temporary_peers); + prepare_scc_for_java_collection (i, scc); } // Add the cross scc refs for (size_t i = 0; i < cross_refs->CrossReferenceCount; i++) { const ComponentCrossReference &xref = cross_refs->CrossReferences [i]; - add_cross_reference (xref.SourceGroupIndex, xref.DestinationGroupIndex, temporary_peers); + add_cross_reference (xref.SourceGroupIndex, xref.DestinationGroupIndex); + } + + // With cross references processed, the temporary peer list can be released + for (const auto& [scc, temporary_peer] : temporary_peers) { + env->DeleteLocalRef (temporary_peer); + } + + // Switch global to weak references + for (size_t i = 0; i < cross_refs->ComponentCount; i++) { + const StronglyConnectedComponent &scc = cross_refs->Components [i]; + for (size_t j = 0; j < scc.Count; j++) { + const HandleContext *context = scc.Contexts [j]; + abort_unless (context != nullptr, "Context must not be null"); + + take_weak_global_ref (*context); + } } } -void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc, TemporaryPeerMap &temporary_peers) noexcept +void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc) noexcept { // Count == 0 case: Some SCCs might have no IGCUserPeers associated with them, so we must create one if (scc.Count == 0) { - temporary_peers.add (cross_refs->Components [scc_index]); + temporary_peers [scc_index] = env->NewObject (GCUserPeer_class, GCUserPeer_ctor); return; } @@ -220,14 +127,14 @@ void BridgeProcessingShared::prepare_scc_for_java_collection (size_t scc_index, add_circular_references (scc); } -CrossReferenceTarget BridgeProcessingShared::select_cross_reference_target (size_t scc_index, TemporaryPeerMap &temporary_peers) noexcept +CrossReferenceTarget BridgeProcessingShared::select_cross_reference_target (size_t scc_index) noexcept { const StronglyConnectedComponent &scc = cross_refs->Components [scc_index]; - if (temporary_peers.has_temporary_peer (scc)) { - jobject temporary_peer = temporary_peers.get (scc); - abort_unless (temporary_peer != nullptr, "Temporary peer must not be null"); - return { .is_temporary_peer = true, .temporary_peer = temporary_peer }; + 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 }; } abort_unless (scc.Contexts [0] != nullptr, "SCC must have at least one context"); @@ -269,10 +176,10 @@ void BridgeProcessingShared::add_circular_references (const StronglyConnectedCom } } -void BridgeProcessingShared::add_cross_reference (size_t source_index, size_t dest_index, TemporaryPeerMap &temporary_peers) noexcept +void BridgeProcessingShared::add_cross_reference (size_t source_index, size_t dest_index) noexcept { - CrossReferenceTarget from = select_cross_reference_target (source_index, temporary_peers); - CrossReferenceTarget to = select_cross_reference_target (dest_index, temporary_peers); + CrossReferenceTarget from = select_cross_reference_target (source_index); + CrossReferenceTarget to = select_cross_reference_target (dest_index); if (add_reference (from.get_handle(), to.get_handle())) { from.mark_refs_added_if_needed (); diff --git a/src/native/clr/include/host/bridge-processing-shared.hh b/src/native/clr/include/host/bridge-processing-shared.hh index 0ee2c1dafbf..70bef1ca4fc 100644 --- a/src/native/clr/include/host/bridge-processing-shared.hh +++ b/src/native/clr/include/host/bridge-processing-shared.hh @@ -1,8 +1,8 @@ #pragma once -#include #include #include +#include #include #include @@ -21,38 +21,6 @@ struct CrossReferenceTarget void mark_refs_added_if_needed () noexcept; }; -class TemporaryPeerMap -{ -public: - explicit TemporaryPeerMap (JNIEnv *env, MarkCrossReferencesArgs *cross_refs) noexcept; - ~TemporaryPeerMap () noexcept; - - static void initialize_on_runtime_init (JNIEnv *env, jclass runtimeClass) noexcept; - - void add (StronglyConnectedComponent &scc) noexcept; - bool has_temporary_peer (const StronglyConnectedComponent &scc) const noexcept; - jobject get (const StronglyConnectedComponent &scc) const noexcept; - -private: - // Count is unsigned, so encode the temporary peer index as ~index. This stores the same bit - // pattern as -(index + 1), giving us a sign bit marker while preserving index 0. - // The destructor resets every marker before returning cross_refs to the runtime. - static constexpr size_t temporary_peer_index_sign_bit = ~(~size_t { 0 } >> 1); - - static bool is_temporary_peer_index (size_t count) noexcept; - static size_t encode_temporary_peer_index (size_t index) noexcept; - static size_t decode_temporary_peer_index (size_t count) noexcept; - - static inline jclass peer_class = nullptr; - static inline jmethodID peer_ctor = nullptr; - - JNIEnv *env; - MarkCrossReferencesArgs *cross_refs; - jobject *peers {}; - size_t count {}; - size_t capacity {}; -}; - class BridgeProcessingShared { public: @@ -62,6 +30,10 @@ public: private: JNIEnv* env; MarkCrossReferencesArgs *cross_refs; + std::unordered_map temporary_peers; + + static inline jclass GCUserPeer_class = nullptr; + static inline jmethodID GCUserPeer_ctor = nullptr; // Cached `mono.android.IGCUserPeer` interface and its methods. The method IDs are looked up // once from the interface class and are valid for virtual dispatch on every implementing peer, @@ -71,13 +43,12 @@ private: static inline jmethodID IGCUserPeer_monodroidClearReferences = nullptr; void prepare_for_java_collection () noexcept; - void prepare_sccs_and_cross_references_for_java_collection () noexcept; - void prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc, TemporaryPeerMap &temporary_peers) noexcept; + void prepare_scc_for_java_collection (size_t scc_index, const StronglyConnectedComponent &scc) noexcept; void take_weak_global_ref (const HandleContext &context) noexcept; void add_circular_references (const StronglyConnectedComponent &scc) noexcept; - void add_cross_reference (size_t source_index, size_t dest_index, TemporaryPeerMap &temporary_peers) noexcept; - CrossReferenceTarget select_cross_reference_target (size_t scc_index, TemporaryPeerMap &temporary_peers) noexcept; + void add_cross_reference (size_t source_index, size_t dest_index) noexcept; + CrossReferenceTarget select_cross_reference_target (size_t scc_index) noexcept; bool add_reference (jobject from, jobject to) noexcept; void cleanup_after_java_collection () noexcept; From 4412ea395dde01d2ebe8d695661cb20dfe1fe3ac Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Fri, 17 Jul 2026 10:08:11 +0200 Subject: [PATCH 4/5] Add compile-time function name coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 70f63eb7-6599-414c-a947-d860705aa0fa --- src/native/common/include/shared/cpp-util.hh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/native/common/include/shared/cpp-util.hh b/src/native/common/include/shared/cpp-util.hh index eb6458c7dd0..19755db4cd0 100644 --- a/src/native/common/include/shared/cpp-util.hh +++ b/src/native/common/include/shared/cpp-util.hh @@ -140,6 +140,17 @@ namespace xamarin::android::detail { return sig.substr (name_start, name_end - name_start); } + + static_assert (get_function_name ("void ordinary_function()") == std::string_view { "ordinary_function" }); + static_assert (get_function_name ("void example::Widget::method(int)") == std::string_view { "Widget::method" }); + static_assert (get_function_name ("void example::Widget::method()") == std::string_view { "Widget::method" }); + static_assert (get_function_name ("void example::Widget::method(U) [with T = int; U = long int]") == std::string_view { "Widget::method" }); + static_assert (get_function_name ("void example::Widget::method()::::operator()(int) const") == std::string_view { "::operator()" }); + static_assert (get_function_name ("bool operator==(const Value&, const Value&)") == std::string_view { "operator==" }); + static_assert (get_function_name ("bool example::Value::operator==(const Value&) const") == std::string_view { "Value::operator==" }); + static_assert (get_function_name ("void malformed(") == std::string_view { "malformed(" }); + static_assert (get_function_name (nullptr) == std::string_view { "" }); + static_assert (get_function_name ("") == std::string_view { "" }); } template F> From aed124383a5486e2abc73efff5006d49ab566cdb Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 20 Jul 2026 08:33:54 -0500 Subject: [PATCH 5/5] [NativeAOT] Address owning state review feedback Remove the unused NativeAOT native libraries path storage and release temporary reference logging path buffers after initialization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1378b7cc-f45b-47c6-bb09-ac55670c55a3 --- src/native/clr/include/runtime-base/android-system.hh | 8 +------- src/native/clr/runtime-base/logger.cc | 5 +++++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/native/clr/include/runtime-base/android-system.hh b/src/native/clr/include/runtime-base/android-system.hh index 9417538a507..3b4c0dedfb7 100644 --- a/src/native/clr/include/runtime-base/android-system.hh +++ b/src/native/clr/include/runtime-base/android-system.hh @@ -85,12 +85,7 @@ namespace xamarin::android { #endif } -#if defined (XA_HOST_NATIVEAOT) - static auto get_native_libraries_dir () noexcept -> const char* - { - return native_libraries_dir; - } -#else +#if !defined (XA_HOST_NATIVEAOT) static auto get_app_code_cache_dir () noexcept -> std::string const& { return app_code_cache_dir; @@ -192,7 +187,6 @@ namespace xamarin::android { static inline bool embedded_dso_mode_enabled = false; #if defined (XA_HOST_NATIVEAOT) static inline char primary_override_dir[Constants::SENSIBLE_PATH_MAX] {}; - static inline char native_libraries_dir[Constants::SENSIBLE_PATH_MAX] {}; #else static inline std::string primary_override_dir; static inline std::string native_libraries_dir; diff --git a/src/native/clr/runtime-base/logger.cc b/src/native/clr/runtime-base/logger.cc index 97e4d87d633..07edfa25d8d 100644 --- a/src/native/clr/runtime-base/logger.cc +++ b/src/native/clr/runtime-base/logger.cc @@ -112,6 +112,11 @@ Logger::init_reference_logging (std::string_view const& override_dir) noexcept ); } } + + std::free (gref_file); + gref_file = nullptr; + std::free (lref_file); + lref_file = nullptr; } [[gnu::always_inline]] bool