diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/GC.cs b/src/coreclr/src/System.Private.CoreLib/src/System/GC.cs
index 01fa70d2653c16..80a6121f836563 100644
--- a/src/coreclr/src/System.Private.CoreLib/src/System/GC.cs
+++ b/src/coreclr/src/System.Private.CoreLib/src/System/GC.cs
@@ -78,6 +78,47 @@ public static GCMemoryInfo GetGCMemoryInfo()
fragmentedBytes: (long)(ulong)lastRecordedFragmentationBytes);
}
+ [DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Ansi)]
+ private static extern void GetGCConfigurationVariable(string name, StringHandleOnStack retString);
+
+ [MethodImpl(MethodImplOptions.InternalCall)]
+ private static extern string[] GetGCConfigurationVariables();
+
+ ///
+ /// Given the name of a GC configuration variable, get the set or default value.
+ ///
+ ///
+ /// Returns `null` for an invalid name.
+ /// For a list of possible names, see the keys of `GetConfigurationVariables`.
+ ///
+ /// Numeric are in decimal even though environment variables are specified using hexadecimal.
+ ///
+ /// This reflects actually used values, not just explicit settings.
+ /// For example, if you set `System.GC.HeapCount` but not `System.GC.Server`,
+ /// the result for "HeapCount" would be "1" as workstation GC only uses 1 heap.
+ /// Similarly, "NoAffinitize" may be true if "HeapHardLimit" is set and HeapAffinitizeRanges is not.
+ ///
+ public static string? GetConfigurationVariable(string name)
+ {
+ string result = "";
+ GetGCConfigurationVariable(name, new StringHandleOnStack(ref result));
+ return result == "" ? null : result;
+ }
+
+ ///
+ /// Returns all possible GC configuration variables and their values.
+ ///
+ ///
+ /// See `GetConfigurationVariable` for description of how we get values.
+ ///
+ public static IEnumerable> GetConfigurationVariables()
+ {
+ foreach (string v in GetGCConfigurationVariables())
+ {
+ yield return new KeyValuePair(v, GetConfigurationVariable(v)!);
+ }
+ }
+
[DllImport(RuntimeHelpers.QCall, CharSet = CharSet.Unicode)]
internal static extern int _StartNoGCRegion(long totalSize, bool lohSizeKnown, long lohSize, bool disallowFullBlockingGC);
diff --git a/src/coreclr/src/gc/gc.cpp b/src/coreclr/src/gc/gc.cpp
index c79e1de5a6741b..b283d2d851b22b 100644
--- a/src/coreclr/src/gc/gc.cpp
+++ b/src/coreclr/src/gc/gc.cpp
@@ -38815,6 +38815,121 @@ void GCHeap::GetMemoryInfo(uint64_t* highMemLoadThresholdBytes,
*lastRecordedFragmentationBytes = gc_heap::last_gc_fragmentation;
}
+static const char* bool_to_str(const bool value)
+{
+ const char* content = value ? "true" : "false";
+ const size_t len = strlen(content);
+ // + 1 for the '\0'
+ char* out = new (nothrow) char[len + 1];
+ if (out != nullptr)
+ {
+ strncpy(out, content, len + 1);
+ assert(out[len] == '\0' && strcmp(out, content) == 0);
+ }
+ return out;
+}
+
+static const char* int64_t_to_str(const int64_t value)
+{
+ // 2**64 in base ten has 20 characters at most, + 1 for the '\0'
+ const size_t max_size = 21;
+ char* out = new (nothrow) char[max_size];
+ if (out != nullptr)
+ {
+ int n = _snprintf_s (out, max_size, _TRUNCATE, "%llu", (long long) value);
+ // -1 because n does not include the '\0'
+ assert (n <= max_size - 1);
+ }
+ return out;
+}
+
+static char const* const configuration_variables[11] =
+{
+ "CpuGroup",
+ "HeapAffinitizeMask",
+ "HeapAffinitizeRanges",
+ "HeapCount",
+ "HeapHardLimit",
+ "HeapHardLimitPercent",
+ "HighMemoryPercent",
+ "LargePages",
+ "LOHThreshold",
+ "NoAffinitize",
+ "Server"
+};
+
+slice GCHeap::GetGCConfigurationVariables()
+{
+ return slice::FromArray<11>(configuration_variables);
+}
+
+const char* GCHeap::GetGCConfigurationVariable(const char* key)
+{
+ if (strcmp(key, "CpuGroup") == 0)
+ {
+ return bool_to_str(GCConfig::GetGCCpuGroup());
+ }
+ else if (strcmp(key, "HeapAffinitizeMask") == 0)
+ {
+ return int64_t_to_str(GCConfig::GetGCHeapAffinitizeMask());
+ }
+ else if (strcmp(key, "HeapAffinitizeRanges") == 0)
+ {
+ // GetGCHeapAffinitizeRanges returns a GCConfigStringHolder which will delete the string, but not if we Extract() it.
+ return GCConfig::GetGCHeapAffinitizeRanges().Extract();
+ }
+ else if (strcmp(key, "HeapCount") == 0)
+ {
+#ifdef MULTIPLE_HEAPS
+ const size_t heapCount = gc_heap::n_heaps;
+#else
+ const size_t heapCount = 1;
+#endif
+ return int64_t_to_str(heapCount);
+ }
+ else if (strcmp(key, "HeapHardLimit") == 0)
+ {
+ return int64_t_to_str(gc_heap::heap_hard_limit);
+ }
+ else if (strcmp(key, "HeapHardLimitPercent") == 0)
+ {
+ return int64_t_to_str(GCConfig::GetGCHeapHardLimitPercent());
+ }
+ else if (strcmp(key, "HighMemoryPercent") == 0)
+ {
+ return int64_t_to_str(gc_heap::high_memory_load_th);
+ }
+ else if (strcmp(key, "LargePages") == 0)
+ {
+ return bool_to_str(gc_heap::use_large_pages_p);
+ }
+ else if (strcmp(key, "LOHThreshold") == 0)
+ {
+ return int64_t_to_str(loh_size_threshold);
+ }
+ else if (strcmp(key, "NoAffinitize") == 0)
+ {
+#ifdef MULTIPLE_HEAPS
+ const bool noAffinitize = gc_heap::gc_thread_no_affinitize_p;
+#else
+ const bool noAffinitize = false;
+#endif
+ return bool_to_str(noAffinitize);
+ }
+ else if (strcmp(key, "Server") == 0)
+ {
+#ifdef MULTIPLE_HEAPS
+ return bool_to_str(true);
+#else
+ return bool_to_str(false);
+#endif
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
int GCHeap::GetGcLatencyMode()
{
return (int)(pGenGCHeap->settings.pause_mode);
diff --git a/src/coreclr/src/gc/gcconfig.h b/src/coreclr/src/gc/gcconfig.h
index ba8db0f77af911..8c3390bda6967d 100644
--- a/src/coreclr/src/gc/gcconfig.h
+++ b/src/coreclr/src/gc/gcconfig.h
@@ -48,6 +48,14 @@ class GCConfigStringHolder
// Retrieves the wrapped config string.
const char* Get() const { return m_str; }
+
+ // Returns the wrapped config string and sets to null.
+ // Now the caller is responsible for freeing it.
+ const char* Extract() {
+ const char* result = m_str;
+ m_str = nullptr;
+ return result;
+ }
};
// Note that the configs starting BGCFLTuningEnabled ending BGCG2RatioStep are for BGC servo
diff --git a/src/coreclr/src/gc/gcimpl.h b/src/coreclr/src/gc/gcimpl.h
index c9fcdb8fed5b5f..34b67ed71e800e 100644
--- a/src/coreclr/src/gc/gcimpl.h
+++ b/src/coreclr/src/gc/gcimpl.h
@@ -177,6 +177,9 @@ class GCHeap : public IGCHeapInternal
uint32_t* lastRecordedMemLoadPct,
size_t* lastRecordedHeapSizeBytes,
size_t* lastRecordedFragmentationBytes);
+
+ const char* GetGCConfigurationVariable(const char* key);
+ slice GetGCConfigurationVariables();
int GetGcLatencyMode();
int SetGcLatencyMode(int newLatencyMode);
diff --git a/src/coreclr/src/gc/gcinterface.h b/src/coreclr/src/gc/gcinterface.h
index 7d22d30d5b0738..c266fde93cb7b6 100644
--- a/src/coreclr/src/gc/gcinterface.h
+++ b/src/coreclr/src/gc/gcinterface.h
@@ -14,6 +14,32 @@
// mismatches can still interopate correctly, with some care.
#define GC_INTERFACE_MINOR_VERSION 1
+template
+class slice {
+ T* _begin;
+ size_t _length;
+
+ slice(T* begin, size_t length) : _begin{begin}, _length{length} {}
+
+public:
+ template
+ static slice FromArray(T data[size])
+ {
+ return slice { data, size };
+ }
+
+ size_t size() const
+ {
+ return _length;
+ }
+
+ const T& operator[](size_t index) const
+ {
+ assert(index < _length);
+ return _begin[index];
+ }
+};
+
struct ScanContext;
struct gc_alloc_context;
class CrawlFrame;
@@ -612,6 +638,9 @@ class IGCHeap {
size_t* lastRecordedHeapSizeBytes,
size_t* lastRecordedFragmentationBytes) = 0;
+ virtual const char* GetGCConfigurationVariable(const char* key) = 0;
+ virtual slice GetGCConfigurationVariables() = 0;
+
// Gets the current GC latency mode.
virtual int GetGcLatencyMode() = 0;
diff --git a/src/coreclr/src/inc/clrconfigvalues.h b/src/coreclr/src/inc/clrconfigvalues.h
index e0ce6f39052b18..643c19000634be 100644
--- a/src/coreclr/src/inc/clrconfigvalues.h
+++ b/src/coreclr/src/inc/clrconfigvalues.h
@@ -315,7 +315,7 @@ RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCName, W("GCName"), "")
RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(EXTERNAL_GCHeapHardLimit, W("GCHeapHardLimit"), "Specifies the maximum commit size for the GC heap")
RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(EXTERNAL_GCHeapHardLimitPercent, W("GCHeapHardLimitPercent"), "Specifies the GC heap usage as a percentage of the total memory")
RETAIL_CONFIG_STRING_INFO(EXTERNAL_GCHeapAffinitizeRanges, W("GCHeapAffinitizeRanges"), "Specifies list of processors for Server GC threads. The format is a comma separated list of processor numbers or ranges of processor numbers. Example: 1,3,5,7-9,12")
-RETAIL_CONFIG_DWORD_INFO_DIRECT_ACCESS(EXTERNAL_GCLargePages, W("GCLargePages"), "Specifies whether large pages should be used when a heap hard limit is set")
+RETAIL_CONFIG_DWORD_INFO(EXTERNAL_GCLargePages, W("GCLargePages"), 0, "Specifies whether large pages should be used when a heap hard limit is set")
///
/// IBC
diff --git a/src/coreclr/src/inc/holder.h b/src/coreclr/src/inc/holder.h
index f49f342bd34e3b..d390ff4b7cdee3 100644
--- a/src/coreclr/src/inc/holder.h
+++ b/src/coreclr/src/inc/holder.h
@@ -1028,6 +1028,7 @@ FORCEINLINE void DeleteArray(TYPE *value)
NEW_WRAPPER_TEMPLATE1(NewArrayHolder, DeleteArray<_TYPE>);
typedef NewArrayHolder AStringHolder;
+typedef NewArrayHolder AConstStringHolder;
typedef NewArrayHolder WStringHolder;
//-----------------------------------------------------------------------------
diff --git a/src/coreclr/src/utilcode/util.cpp b/src/coreclr/src/utilcode/util.cpp
index 80cd00fd0e4e81..c4c76a8b32dd08 100644
--- a/src/coreclr/src/utilcode/util.cpp
+++ b/src/coreclr/src/utilcode/util.cpp
@@ -20,6 +20,8 @@
#include "corinfo.h"
#include "volatile.h"
+#include "../inc/configuration.h"
+
#ifndef DACCESS_COMPILE
UINT32 g_nClrInstanceId = 0;
#endif //!DACCESS_COMPILE
@@ -989,7 +991,7 @@ DWORD LCM(DWORD u, DWORD v)
CONTRACTL_END;
#if !defined(FEATURE_REDHAWK) && (defined(_TARGET_AMD64_) || defined(_TARGET_ARM64_))
- BOOL enableGCCPUGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_GCCpuGroup) != 0;
+ BOOL enableGCCPUGroups = Configuration::GetKnobBooleanValue(W("System.GC.CpuGroup"), CLRConfig::EXTERNAL_GCCpuGroup);
BOOL threadUseAllCpuGroups = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Thread_UseAllCpuGroups) != 0;
if (!enableGCCPUGroups)
diff --git a/src/coreclr/src/vm/comutilnative.cpp b/src/coreclr/src/vm/comutilnative.cpp
index fa0d8a28b7a54f..9dcda70f3d2f92 100644
--- a/src/coreclr/src/vm/comutilnative.cpp
+++ b/src/coreclr/src/vm/comutilnative.cpp
@@ -696,6 +696,43 @@ FCIMPL6(void, GCInterface::GetMemoryInfo, UINT64* highMemLoadThreshold, UINT64*
}
FCIMPLEND
+void QCALLTYPE GCInterface::GetGCConfigurationVariable(const char* key, QCall::StringHandleOnStack result)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ AConstStringHolder value;
+ value = GCHeapUtilities::GetGCHeap()->GetGCConfigurationVariable(key);
+ if (value)
+ {
+ result.Set(value);
+ }
+
+ END_QCALL;
+}
+
+FCIMPL0(Object*, GCInterface::GetGCConfigurationVariables)
+{
+ FCALL_CONTRACT;
+
+ slice configVariables = GCHeapUtilities::GetGCHeap()->GetGCConfigurationVariables();
+
+ PTRARRAYREF strArray = NULL;
+ HELPER_METHOD_FRAME_BEGIN_RET_1(strArray);
+ strArray = (PTRARRAYREF) AllocateObjectArray(static_cast(configVariables.size()), g_pStringClass);
+ for (unsigned int i = 0; i < configVariables.size(); i++)
+ {
+ STRINGREF str = StringObject::NewString(configVariables[i]); // This copies it
+ STRINGREF * destData = ((STRINGREF*)(strArray->GetDataPtr())) + i;
+ SetObjectReference((OBJECTREF*)destData, (OBJECTREF)str);
+ }
+
+ HELPER_METHOD_FRAME_END();
+ return OBJECTREFToObject(strArray);
+}
+FCIMPLEND
+
FCIMPL0(int, GCInterface::GetGcLatencyMode)
{
FCALL_CONTRACT;
diff --git a/src/coreclr/src/vm/comutilnative.h b/src/coreclr/src/vm/comutilnative.h
index 17442d91431c96..c5a74c85a02a4f 100644
--- a/src/coreclr/src/vm/comutilnative.h
+++ b/src/coreclr/src/vm/comutilnative.h
@@ -97,6 +97,9 @@ class GCInterface {
static FORCEINLINE UINT64 InterlockedSub(UINT64 *pMinuend, UINT64 subtrahend);
static FCDECL6(void, GetMemoryInfo, UINT64* highMemLoadThresholdBytes, UINT64* totalAvailableMemoryBytes, UINT64* lastRecordedMemLoadBytes, UINT32* lastRecordedMemLoadPct, size_t* lastRecordedHeapSizBytes, size_t* lastRecordedFragmentationBytes);
+ static void QCALLTYPE GetGCConfigurationVariable(const char* key, QCall::StringHandleOnStack result);
+ // The object will be a string[]
+ static FCDECL0(Object*, GetGCConfigurationVariables);
static FCDECL0(int, GetGcLatencyMode);
static FCDECL1(int, SetGcLatencyMode, int newLatencyMode);
static FCDECL0(int, GetLOHCompactionMode);
diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h
index b591f5c627f993..2e149774d7427f 100644
--- a/src/coreclr/src/vm/ecalllist.h
+++ b/src/coreclr/src/vm/ecalllist.h
@@ -741,6 +741,8 @@ FCFuncStart(gGCInterfaceFuncs)
QCFuncElement("_StartNoGCRegion", GCInterface::StartNoGCRegion)
QCFuncElement("_EndNoGCRegion", GCInterface::EndNoGCRegion)
FCFuncElement("GetSegmentSize", GCInterface::GetSegmentSize)
+ QCFuncElement("GetGCConfigurationVariable", GCInterface::GetGCConfigurationVariable)
+ FCFuncElement("GetGCConfigurationVariables", GCInterface::GetGCConfigurationVariables)
FCFuncElement("GetLastGCPercentTimeInGC", GCInterface::GetLastGCPercentTimeInGC)
FCFuncElement("GetGenerationSize", GCInterface::GetGenerationSize)
QCFuncElement("_AddMemoryPressure", GCInterface::_AddMemoryPressure)
diff --git a/src/coreclr/src/vm/eeconfig.cpp b/src/coreclr/src/vm/eeconfig.cpp
index a942add909fc57..c0aea823e04e42 100644
--- a/src/coreclr/src/vm/eeconfig.cpp
+++ b/src/coreclr/src/vm/eeconfig.cpp
@@ -814,12 +814,18 @@ fTrackDynamicMethodDebugInfo = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_
if (!iGCgen0size) iGCgen0size = GetConfigDWORD_DontUse_(CLRConfig::UNSUPPORTED_GCgen0size, iGCgen0size);
#endif //BIT64
- const ULONGLONG ullHeapHardLimit = Configuration::GetKnobULONGLONGValue(W("System.GC.HeapHardLimit"));
+ ULONGLONG ullHeapHardLimit = Configuration::GetKnobULONGLONGValue(W("System.GC.HeapHardLimit"));
+ if (!ullHeapHardLimit) ullHeapHardLimit = GetConfigULONGLONG_DontUse_(CLRConfig::EXTERNAL_GCHeapHardLimit, 0);
iGCHeapHardLimit = FitsIn(ullHeapHardLimit)
? static_cast(ullHeapHardLimit)
: ClrSafeInt::MaxInt();
iGCHeapHardLimitPercent = Configuration::GetKnobDWORDValue(W("System.GC.HeapHardLimitPercent"), 0);
+ iGCCpuGroup = Configuration::GetKnobBooleanValue(W("System.GC.CpuGroup"), CLRConfig::EXTERNAL_GCCpuGroup);
+ iGCHighMemoryPercent = Configuration::GetKnobDWORDValue(W("System.GC.HighMemoryPercent"), CLRConfig::EXTERNAL_GCHighMemPercent);
+ fGCLargePages = Configuration::GetKnobBooleanValue(W("System.GC.LargePages"), CLRConfig::EXTERNAL_GCLargePages);
+ lpszGCHeapAffinitizeRanges = Configuration::GetKnobStringValue(W("System.GC.HeapAffinitizeRanges"), CLRConfig::EXTERNAL_GCHeapAffinitizeRanges);
+
if (g_IGCHoardVM)
iGCHoardVM = g_IGCHoardVM;
else
diff --git a/src/coreclr/src/vm/eeconfig.h b/src/coreclr/src/vm/eeconfig.h
index ceecfbfe4c6c0a..17596bfeac0295 100644
--- a/src/coreclr/src/vm/eeconfig.h
+++ b/src/coreclr/src/vm/eeconfig.h
@@ -612,8 +612,12 @@ class EEConfig
int GetGCHeapCount() const {LIMITED_METHOD_CONTRACT; return iGCHeapCount;}
int GetGCNoAffinitize () const {LIMITED_METHOD_CONTRACT; return iGCNoAffinitize;}
size_t GetGCAffinityMask() const {LIMITED_METHOD_CONTRACT; return iGCAffinityMask;}
+ LPCWSTR GetGCHeapAffinitizeRanges() const {LIMITED_METHOD_CONTRACT; return lpszGCHeapAffinitizeRanges;}
+ int GetGCHighMemoryPercent() const {LIMITED_METHOD_CONTRACT; return iGCHighMemoryPercent;}
size_t GetGCHeapHardLimit() const {LIMITED_METHOD_CONTRACT; return iGCHeapHardLimit;}
int GetGCHeapHardLimitPercent() const {LIMITED_METHOD_CONTRACT; return iGCHeapHardLimitPercent;}
+ bool GetGCLargePages() const {LIMITED_METHOD_CONTRACT; return fGCLargePages;}
+ bool GetGCCpuGroup() const {LIMITED_METHOD_CONTRACT; return iGCCpuGroup;}
#ifdef GCTRIMCOMMIT
@@ -917,8 +921,13 @@ class EEConfig
int iGCHeapCount;
int iGCNoAffinitize;
size_t iGCAffinityMask;
+ bool iGCCpuGroup;
+ // this points to a string from GetKnobStringValue and should not be freed.
+ LPCWSTR lpszGCHeapAffinitizeRanges;
+ int iGCHighMemoryPercent;
size_t iGCHeapHardLimit;
int iGCHeapHardLimitPercent;
+ bool fGCLargePages;
#ifdef GCTRIMCOMMIT
diff --git a/src/coreclr/src/vm/gcenv.ee.cpp b/src/coreclr/src/vm/gcenv.ee.cpp
index 34a1ec6994780a..8b34a69c6599d9 100644
--- a/src/coreclr/src/vm/gcenv.ee.cpp
+++ b/src/coreclr/src/vm/gcenv.ee.cpp
@@ -1057,6 +1057,24 @@ bool GCToEEInterface::GetBooleanConfigValue(const char* key, bool* value)
return true;
}
+ if (strcmp(key, "GCCpuGroup") == 0)
+ {
+ *value = g_pConfig->GetGCCpuGroup();
+ return true;
+ }
+
+ if (strcmp(key, "GCLargePages") == 0)
+ {
+ *value = g_pConfig->GetGCLargePages();
+ return true;
+ }
+
+ if (strcmp(key, "GCNoAffinitize") == 0)
+ {
+ *value = g_pConfig->GetGCNoAffinitize();
+ return true;
+ }
+
WCHAR configKey[MaxConfigKeyLength];
if (MultiByteToWideChar(CP_ACP, 0, key, -1 /* key is null-terminated */, configKey, MaxConfigKeyLength) == 0)
{
@@ -1082,15 +1100,21 @@ bool GCToEEInterface::GetIntConfigValue(const char* key, int64_t* value)
GC_NOTRIGGER;
} CONTRACTL_END;
- if (strcmp(key, "GCSegmentSize") == 0)
+ if (strcmp(key, "GCgen0size") == 0)
{
- *value = g_pConfig->GetSegmentSize();
+ *value = g_pConfig->GetGCgen0size();
return true;
}
- if (strcmp(key, "GCgen0size") == 0)
+ if (strcmp(key, "GCHeapAffinitizeMask") == 0)
{
- *value = g_pConfig->GetGCgen0size();
+ *value = g_pConfig->GetGCAffinityMask();
+ return true;
+ }
+
+ if (strcmp(key, "GCHeapCount") == 0)
+ {
+ *value = g_pConfig->GetGCHeapCount();
return true;
}
@@ -1106,12 +1130,24 @@ bool GCToEEInterface::GetIntConfigValue(const char* key, int64_t* value)
return true;
}
+ if (strcmp(key, "GCHighMemPercent") == 0)
+ {
+ *value = g_pConfig->GetGCHighMemoryPercent();
+ return true;
+ }
+
if (strcmp(key, "GCLOHThreshold") == 0)
{
*value = g_pConfig->GetGCLOHThreshold();
return true;
}
+ if (strcmp(key, "GCSegmentSize") == 0)
+ {
+ *value = g_pConfig->GetSegmentSize();
+ return true;
+ }
+
WCHAR configKey[MaxConfigKeyLength];
if (MultiByteToWideChar(CP_ACP, 0, key, -1 /* key is null-terminated */, configKey, MaxConfigKeyLength) == 0)
{
@@ -1152,34 +1188,13 @@ bool GCToEEInterface::GetIntConfigValue(const char* key, int64_t* value)
return false;
}
-bool GCToEEInterface::GetStringConfigValue(const char* key, const char** value)
+static bool GetStringConfigValueHelper(LPCWSTR out, const char** value)
{
- CONTRACTL {
- NOTHROW;
- GC_NOTRIGGER;
- } CONTRACTL_END;
-
- WCHAR configKey[MaxConfigKeyLength];
- if (MultiByteToWideChar(CP_ACP, 0, key, -1 /* key is null-terminated */, configKey, MaxConfigKeyLength) == 0)
- {
- // whatever this is... it's not something we care about. (It was too long, wasn't unicode, etc.)
- return false;
- }
-
- CLRConfig::ConfigStringInfo info { configKey, CLRConfig::EEConfig_default };
- LPWSTR out = CLRConfig::GetConfigValue(info);
- if (!out)
- {
- // config not found
- return false;
- }
-
int charCount = WideCharToMultiByte(CP_ACP, 0, out, -1 /* out is null-terminated */, NULL, 0, nullptr, nullptr);
if (charCount == 0)
{
// this should only happen if the config subsystem gives us a string that's not valid
// unicode.
- CLRConfig::FreeConfigString(out);
return false;
}
@@ -1187,7 +1202,6 @@ bool GCToEEInterface::GetStringConfigValue(const char* key, const char** value)
AStringHolder configResult = new (nothrow) char[charCount];
if (!configResult)
{
- CLRConfig::FreeConfigString(out);
return false;
}
@@ -1197,15 +1211,49 @@ bool GCToEEInterface::GetStringConfigValue(const char* key, const char** value)
// this should never happen, the previous call to WideCharToMultiByte that computed the charCount should
// have caught all issues.
assert(false);
- CLRConfig::FreeConfigString(out);
return false;
}
*value = configResult.Extract();
- CLRConfig::FreeConfigString(out);
return true;
}
+bool GCToEEInterface::GetStringConfigValue(const char* key, const char** value)
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
+
+ WCHAR configKey[MaxConfigKeyLength];
+ if (MultiByteToWideChar(CP_ACP, 0, key, -1 /* key is null-terminated */, configKey, MaxConfigKeyLength) == 0)
+ {
+ // whatever this is... it's not something we care about. (It was too long, wasn't unicode, etc.)
+ return false;
+ }
+
+ CLRConfig::ConfigStringInfo info { configKey, CLRConfig::EEConfig_default };
+
+ if (strcmp(key, "GCHeapAffinitizeRanges") == 0)
+ {
+ // Do not free, the one we get from eeconfig is just a reference to its own string and not a copy
+ LPCWSTR out = g_pConfig->GetGCHeapAffinitizeRanges();
+ return out != nullptr && GetStringConfigValueHelper(out, value);
+ }
+ else
+ {
+ LPWSTR out = CLRConfig::GetConfigValue(info);
+ if (!out)
+ {
+ // config not found
+ return false;
+ }
+ bool result = GetStringConfigValueHelper(out, value);
+ CLRConfig::FreeConfigString(out);
+ return result;
+ }
+}
+
void GCToEEInterface::FreeStringConfigValue(const char* value)
{
delete [] value;
diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_common.cs b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_common.cs
new file mode 100644
index 00000000000000..df72b094ece120
--- /dev/null
+++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_common.cs
@@ -0,0 +1,62 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+public static class GetConfigurationVariables_common
+{
+ public static int AssertConfigurationVariables(IReadOnlyList> expected)
+ {
+ // TODO: shouldn't need reflection once this is a public API
+ Type gcType = typeof(GC);
+ MethodInfo getConfigurationVariables = GetMethod(typeof(GC), "GetConfigurationVariables");
+ // TODO: GC.GetConfigurationVariables().ToList();
+ IReadOnlyList> actual = ((IEnumerable>) getConfigurationVariables.Invoke(null, new object[] { })!).ToList();
+
+ if (!ArraysEqual(expected, actual, new KeyValuePairComparer(EqualityComparer.Default, EqualityComparer.Default)))
+ {
+ // Expected
+ // Actual
+ Console.Error.WriteLine($"Expected : {ShowPairs(expected)}\nActual : {ShowPairs(actual)}");
+ return 1;
+ }
+
+ return 100;
+ }
+
+ public static KeyValuePair Pair(K k, V v) =>
+ new KeyValuePair(k, v);
+
+ private static string ShowPairs(IEnumerable> pairs) =>
+ string.Join(", ", from pair in pairs select $"{pair.Key} => {pair.Value}");
+
+ private static MethodInfo GetMethod(Type t, string name) =>
+ t.GetMethod(name, BindingFlags.Public | BindingFlags.Static)
+ ?? throw new Exception($"Type {t} has no public static method {name}");
+
+ private static bool ArraysEqual(IReadOnlyList a, IReadOnlyList b, IEqualityComparer cmp) =>
+ a.Count == b.Count
+ && a.Zip(b).All(ab => cmp.Equals(ab.First, ab.Second));
+
+ private class KeyValuePairComparer : IEqualityComparer>
+ {
+ private IEqualityComparer compareK;
+ private IEqualityComparer compareV;
+
+ public KeyValuePairComparer(IEqualityComparer compareK, IEqualityComparer compareV)
+ {
+ this.compareK = compareK;
+ this.compareV = compareV;
+ }
+
+ bool IEqualityComparer>.Equals(KeyValuePair x, KeyValuePair y) =>
+ compareK.Equals(x.Key, y.Key) && compareV.Equals(x.Value, y.Value);
+
+ int IEqualityComparer>.GetHashCode(KeyValuePair obj) =>
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.cs b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.cs
new file mode 100644
index 00000000000000..3f8be07bc45369
--- /dev/null
+++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.cs
@@ -0,0 +1,21 @@
+using System.Collections.Generic;
+
+using static GetConfigurationVariables_common;
+
+public static class GetConfigurationVariables_defaults
+{
+ public static int Main() =>
+ AssertConfigurationVariables(new KeyValuePair[]
+ {
+ Pair("CpuGroup", "false"),
+ Pair("HeapAffinitizeMask", "0"),
+ Pair("HeapCount", "1"),
+ Pair("HeapHardLimit", "0"),
+ Pair("HeapHardLimitPercent", "0"),
+ Pair("HighMemoryPercent", "90"),
+ Pair("LargePages", "false"),
+ Pair("LOHThreshold", "85000"),
+ Pair("NoAffinitize", "false"),
+ Pair("Server", "false"),
+ });
+}
diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.csproj b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.csproj
new file mode 100644
index 00000000000000..783913c8d2b7ec
--- /dev/null
+++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_defaults.csproj
@@ -0,0 +1,11 @@
+
+
+ Exe
+
+ true
+
+
+
+
+
+
diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.cs b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.cs
new file mode 100644
index 00000000000000..75e950ded9fabe
--- /dev/null
+++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.cs
@@ -0,0 +1,23 @@
+using System.Collections.Generic;
+
+using static GetConfigurationVariables_common;
+
+public static class GetConfigurationVariables_withEnvVars
+{
+ public static int Main() =>
+ AssertConfigurationVariables(new KeyValuePair[]
+ {
+ Pair("CpuGroup", "false"),
+ Pair("HeapAffinitizeMask", "0"),
+ Pair("HeapCount", "1"),
+ Pair("HeapHardLimit", "123456789"),
+ Pair("HeapHardLimitPercent", "0"),
+ Pair("HighMemoryPercent", "90"),
+ Pair("LargePages", "false"),
+ Pair("LOHThreshold", "85000"),
+ // We did not explicitly set this, but due to the HeapHardLimit this is true.
+ // See gc.cpp: `gc_heap::gc_thread_no_affinitize_p = (gc_heap::heap_hard_limit ? !affinity_config_specified_p : (GCConfig::GetNoAffinitize() != 0));`
+ Pair("NoAffinitize", "true"),
+ Pair("Server", "true"),
+ });
+}
diff --git a/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.csproj b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.csproj
new file mode 100644
index 00000000000000..bcf34fdf12d01e
--- /dev/null
+++ b/src/coreclr/tests/src/GC/API/Configuration/GetConfigurationVariables_withEnvVars.csproj
@@ -0,0 +1,26 @@
+
+
+ Exe
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+